new Vue的具体流程

2020-06-19  本文已影响0人  0月

new Vue做了啥?

new Vue({
  el: '#app',
  render: h => h(App),
  data() {
    return {
      message: 'hello vue'
    }
  }
}).$mount('#app')

打开源码,先找到Vue的构造函数,在vue/src/core/instance/index.js里面,


function Vue

可以看到这个function Vue(){}很简单,几行代码,首先判断用户是否没有用 new Vue(); 是就警告用户必须new来实例化,不能直接

const app = Vue({
  ...
})

必须要new

const app = new Vue({
  ...
})

然后, 调用原型方法 this._init(options)

_init方法在哪里?
找一下,在上图的initMixin(Vue)里面,
打开initMixin方法来源 :vue/src/core/instance/init.js


initMixin.png

这里做了啥?其实就是合并传入的选项和一些初始化工作,主要包括:


initMixin2.png
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化状态
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 调用created钩子

最后就是mount挂载真实dom了

 if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }

这么多方法一排排下来,难道我要一个个去看?非也,看源码很忌讳这样子看,因为你一个个看的话,函数里面套函数,你就蒙了,函数调用栈这么深,你相当于深度遍历,吃力不讨好。所以最好带着目的看,回到开始的代码new Vue({...})

new Vue({
  el: '#app',
  render: h => h(App),
  data() {
    return {
      message: 'hello vue'
    }
  }
}).$mount('#app')

el是挂载dom,后面看,然后是data,所以看一下它对传入的data做了啥。嗯,说干就干,我们找到initState(vm) 初始化状态 看看它是如何处理data的,找到vue/src/core/instance/state.js

initState
又是初始化props,methods,data,computed,watch等,细心的同学发现这里的props,methods,data,computed,watch顺序是有讲究的,在实际项目开发中,后面的可以通过this.xxx访问前面的提供方法、属性。我们主要看initData如下图
initData.png

敲黑板:面试常考:为什么data推荐函数,而不是对象形式?
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}

源码告诉你这里可以是函数也可以对象,推荐函数是为了解决组件实例公用同一个data的问题。所以,就vue来说,如果是根组件,你可以直接用data:{key:val}形式,非根组件就应该用data(){return {key:val}}形式。

然后继续回到源码这里是 data = vm._data,data的结果挂到了vm._data上,_data就是用户定义的data了, 注意业内规范是下划线_ 开头的变量都是私有属性,作者自己用的,用户最好不用访问它。然后看getData函数做了啥?


getData.png

return data.call(vm, vm)

很简单其实就是执行了用户传入的选项中的data方法,这里用call来绑定this到本实例组件,还不懂call啥意思,可以参考我的另外一篇文章 js模拟实现call、apply、bind函数,所以说看源码要原生js基础牢固过得去才行。

接着往下看:

state.png
1处,拿data里面的key和props的key,methods的key,判断如果有三者之间有重名的就报错。所以,我们在定义数据的时候不能重名。再看keys循环里面的2处,这里调用方法 proxy(vm, _data, key),其实就是把data的数据代理到实例上,所以我们才能够在其他地方直接this.message访问data(){return {message:'hello, vue'}}的message。而不是this._data.message。
具体实现看proxy方法:
proxy.png
所以,当我们this.message时就是走了 return this[sourceKey][key], sourceKey是_data, key是message,看上面图提到2处的proxy(vm, _data, key)。从设计模式的角度来看,这里就是典型的代理模式,用户不直接访问this._data.message,而是访问this.message。访问时,通过Object.defineProperty(target, key, sharedPropertyDefinition)来进行拦截代理。vue响应式原理也是通过这里进行拦截, 这些以后再扯,至此,data这块算过了,接下来看mount。

$mount做了啥?

其实看$mount可以从runtime-only版本和runtime-compiler版本这两个方面入手,
入口文件分别在vue/src/platfoms/web/entry-runtime-with-compiler.js 和
vue/src/platfoms/web/entry-runtime.js
但是他们都是引入了同级下的runtime/index


runtime/index

runtime/index这里定义了mount方法,runtime-only版本直接使用它,而runtime-with-compiler就引入它再包装一层,这一层的目的就是编译template模板为render函数。看Vue.prototype.$mount干了啥?
还是调用了mountComponenth方法,在vue/core/instance/lifecycle里面,去找它,如下图


image.png

我们主要看let updateComponent这里,updateComponent根据不同环境赋值不同,但是它的核心作用就是一个更新组件的过程,内部调用了vm._update(vm._render(), hydrating),接着往下看,就是

new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

这是实例化一个渲染观察者,继续看Watcher是干嘛,找到它,其实它就是一个观察者模式,按照上面的参数传入的话,先做一些依赖收集的工作,最后赋值this.value就是执行updateComponent这个方法,如下图顺序123执行,1处的expOrFn就是updateComponent


Watcher
watcher get

而上面也提到updateComponent内部执行vm._update(vm._render(), hydrating),顾名思义其实就是:
1、vm._render()创建生成virtual dom;
2、vm._update把virtual dom渲染成真实dom

virtua dom 具体怎么生成真实dom的,继续看vm._update这个方法,在vue/src/core/instance/lifecycle.js里面,可以看到这个vue.protype._update不仅仅是初始化渲染,也可能是更新渲染,如图红框里面的if else


_update

继续看__patch__方法干了啥,如下图,这里又做了判断,浏览器端才赋值patch,否则就是noop,noop是一个空函数,啥也不干。


__patch__

我们继续找patch,如下图

patch

看到它又是一个动态生成的函数,靠createPatchFunction生成,这是一个高阶函数,这里传入参数{ nodeOps, modules }可以看出实际上createPatchFunction是可以生成不同的patch函数的,就目前来看,这里是传入web平台的参数,那么里面的返回的patch就是专门针对浏览器处理的,还有可能就是传入weex平台的,这里就不展开了。看一下createPatchFunction的实现,打开vdom/patch.js可以看到这个方法很大 【70 - 804行】,里面定义了很多辅助方法,最后return了一个patch方法。


image.png

实际上就是看这个patch方法的逻辑,里面的方法调方法,最后我们找到insert这个方法,这里就是生成真实dom


insert
image.png

里面的nodeOps.insertBefore(parent, elm, ref)、nodeOps.appendChild(parent, elm)把virtual dom 通过原始的 parentNode.insertBefore方法插入dom,我们看nodeOps里面怎么实现的,回到最开始的createPatchFunction({ nodeOps, modules })这里,顺藤摸瓜,找到了vue/src/platforms/web/runtime/node-ops.js,可以看到里面就是一些对原生dom操作的封装而已,很简单,至此我们就摸清了整个mount流程了。

nodeOps

总结一下

所以,new Vue做了啥?
1、对Vue进行_init初始化
2、$mount,里面做了compiler[可选]、生成render函数、render函数创建vnode、vnode里面再进行patch操作,patch操作里面就是更新virtual dom和插入真实dom了。

new vue流程

更多细节得自己看一遍才更有体会,看博客再多没意义,只有你看了源码,再对照别人的博客,互相证道才有更大的收获,谢谢观看。

上一篇 下一篇

猜你喜欢

热点阅读