Vue源码(下篇)

上一篇是mount之前的添加一些方法,包括全局方法gloal-api,XXXMixin,initXXX,然后一切准备就绪,来到了mount阶段,这个阶段主要是

  • 解析template
  • 创建watcher并存入Dep
  • 更新数据时更新视图

Vue源码里有两个mount

  • 第一个
// src/platform/web/runtime/index.js

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 核心方法,往下看
  return mountComponent(this, el, hydrating)
}
  • 第二个
// src/platforms/web/entry-runtime-with-compiler.js

// 把上面的第一个取出来,在最后一行执行
const mount = Vue.prototype.$mount
// 把原本的替换掉,这就是第二个mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  const options = this.$options

  if (!options.render) {
    let template = getOuterHTML(el)
    if (template) {
      // compileToFunctions,来自 compiler/index.js
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
  }
  // 把第一个mount执行
  return mount.call(this, el, hydrating)
}
// compiler/index.js

function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = options || {}

  // 编译,核心内容
  const compiled = compile(template, options)
}

第二个mount才是在init里真正被执行的,也就是在第一个mount之前先被执行,叫做compiler阶段

compiler阶段,整个阶段全程只执行一次,目的就是生成render表达式

  • parse,将templat转成AST模型树
  • optimize,标注静态节点,就是选出没有参数的固定内容的html标签
  • generate,生成render表达式
<div id="el">Hello {{name}}</div>
// compile方法就是把 html 转为 AST,效果如下
{
    type: 1,
    div: 'div',
    attrsList: [{
        name: 'id',
        value: ''el
    }],
    attrs: [{
        name: 'id',
        value: ''el
    }],
    attrsMap: {
        id: 'el'
    },
    plain: false,
    static: false,
    staticRoot: false,
    children: [
        type: 2,
        expression: '"hello "+ _s(name)',
        text: 'Hello {{name}}',
        static: false
    ]
}
// 由上面的AST生成 render表达式,
with (this) {
    return _c(
        "div",
        {
            attrs: {id: 'el'}
        },
        [
            _v("Hello "+_s(name))
        ]
    )
}
// render-helpers 下 index.js

export function installRenderHelpers (target) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
}
// 怎么没有_c,_c在上篇笔记的initRender方法里
// _c对应元素节点、_v对应文本节点、_s对应动态文本

到这里执行第一个mount,也就是mountComponent方法

// instance/lifecycle.js

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 挂载前,执行beforMount
  callHook(vm, 'beforeMount')
  let updateComponent
  // 定义updateComponent,vm._render将render表达式转化为vnode,vm._update将vnode渲染成实际的dom节点
  updateComponent = () => {
      // 核心内容,理解为
      // var A = vm._render(),生成vDom
      // vm._update(A, hydrating),生成真实dom
      vm._update(vm._render(), hydrating)
  }
  // 首次渲染,并监听数据变化,并实现dom的更新
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false
  // 挂载完成,回调mount函数
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

看看watch构造函数

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    // 上面的函数传了true
    if (isRenderWatcher) {
      vm._watcher = this
    }
    // 当数据发生改变,_watchers会被循环更新,也就是视图更新
    vm._watchers.push(this)
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      // 把expOrFn执行了,启动了初次渲染
      this.value = this.get()
    }
  }
  get () {
    return this.getter.call(vm, vm)
  }
}

最值得研究的patch,这个函数特别的长,下面是超简略版

// src/core/vdom/patch.js

export function createPatchFunction (backend) {
  ...
  // Vue.prototype.__path__ = createPatchFunction()
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 对比
    console.log(oldVnode)
    console.log(vnode)
    // 最后返回的就是真实的可以用的dom
    return vnode.elm
  }
}

不管是初始化渲染还是数据更新,都是把整个页面的render表达式重新渲染生成全部的vdom,进行新旧的对比,这个方法里还有最牛逼的domDiff算法,这里就不研究了,百度很多大佬解析

上面出现的几个重要的方法

  • _render函数主要执行==compiler阶段==,最后返回vDom
  • patch,在core/vdom/patch.js里,主要功能将对比新旧vDom转换为dom节点,最后返回的就是dom
  • _update主要是当数据改变时调用了patch函数

vue的整个实现流程

  • 给Vue函数添加很多的方法【Global-api,XXXMixin】
  • 对参数进行解析和监听【initXXX】
  • 启动mount阶段
  • _render函数把模版解析成AST,再解析成vnode
  • 初次渲染,执行_update,实际执行的是patch方法,patch将vDom渲染成DOM,初次渲染完成
  • data属性变化,_render通过AST再次生成新的vDom,通过_update里的patch进行对比,渲染到html中

最好的调试方法是下载vue.js文件,不要压缩版的,不用脚手架,然后在js里打断点就行

原文地址:https://www.cnblogs.com/pengdt/p/12304007.html

时间: 2024-09-30 09:29:20

Vue源码(下篇)的相关文章

vue源码解读预热-0

vueJS的源码解读 vue源码总共包含约一万行代码量(包括注释)特别感谢作者Evan You开放的源代码,访问地址为Github 代码整体介绍与函数介绍预览 代码模块分析 代码整体思路 总体的分析 从图片中可以看出的为采用IIFE(Immediately-Invoked Function Expression)立即执行的函数表达式的形式进行的代码的编写 常见的几种插件方式: (function(,){}(,))或(function(,){})(,)或!function(){}()等等,其中必有

Vue源码后记-钩子函数

vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过一下~ 想到什么写什么了,这节就简单说说钩子函数吧! vue中的钩子函数主要包含初始化的beforeCreated/created,Virtual Dom更新期间的beforeUpdate/updated,页面渲染期间的beforeMount/mounted,组件销毁期间的beforeDestroy

VUE源码解析心得

解读vue源码比较好奇的几个点: 1.生命周期是如何实现的 2.如何时间数据监听,实现双向绑定的 =======================华丽的分割线======================================================== 1. 官方图解如https://cn.vuejs.org/v2/guide/instance.html#生命周期图示,beforeCreate -> 观察数据变化 + 事件初始化  -> created -> el tem

大白话Vue源码系列(01):万事开头难

阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的最对脾气的 MVVM 框架.之前也使用过 knockout,angular,react 这些框架,但都没有让咱产生 follow 的冲动.直到见到 Vue,简直是一见钟情啊. Vue 的官方文档已经对 Vue 如何使用提供了最好的教程,建议 Vue 新手直接去官网学习,而不要在网上找些质量参差不齐的

Vue源码阅读笔记,持续更新

/ / Vue.js v2.1.3 源码阅读记录 使用的文件为使用es2015的本地文件 2018年4月20日 14:06:30 */ 第一章,Vuejs的整体架构 1. 入口 入口处使用一个闭包(function (global,factory) {factory()})(this,factory): 其中factory是工厂的意思,它的内部实现是一个工厂函数,其中直接声明的function为私有成员. 2. 生命周期的理解 理解vue的生命周期对通读vue源码的效率有较好的帮助,它的生命周期

Vue源码思维导图-------------Vue 初始化

上一节看完<Vue源码思维导图-------------Vue 构造函数.原型.静态属性和方法>,这节将会以new Vue()为入口,大体看下 this._init()要做的事情. 1 function Vue (options) { 2 if (process.env.NODE_ENV !== 'production' && 3 !(this instanceof Vue) 4 ) { 5 warn('Vue is a constructor and should be ca

从vue源码看Vue.set()和this.$set()

前言 最近死磕了一段时间vue源码,想想觉得还是要输出点东西,我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的. Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对数组或者对象进行这样的骚操作操作,结果发现,咦~~,他喵的,怎么页面没有重新渲染. const vueInstance = new Vue({ data: { arr: [1, 2], obj1: { a: 3 } } }); vueInstance.$d

vue源码分析:响应式之依赖收集

今天记录下vue源码的响应式的源码学习,第一步,我们想下,为什么为们写一个data.然后里边的数据变化就能映射到dom的变化上.这里用到了Object.defineProperty(obj,key, {}),如果你还不了解建议去jsmdn上仔细了解下,这个函数的目的就是检测你得赋值和取值的过程用来定义自己的行为,比如obj[key]会拿到配置里边的get函数定义,obj[key] = 'xxx'会调用配置中的set.那么vue也是一样的,在get中做了依赖收集(也就是说哪些dom用到了我定义的这

vue源码构建代码分析

这是xue源码学习记录,如有错误请指出,谢谢!相互学习相互进步. vue源码目录为 vue ├── src #vue源码 ├── flow #flow定义的数据类型库(vue通过flow来检测数据类型是否正确) ├── examples #demo ├── scripts #vue构建命令 ├── ... vue内部代码模块比较清晰,这边主要分析scripts内部代码,讲解vue是如何进行构建的.首先你必须要懂一些rollup,vue内部是通过rollup来进行构建的,rollup是一款js的构