vue 源码运行流程解析

2019-02-15  本文已影响0人  拉面的无聊时光

框架只是工具,了解框架底层原理才能在框架基础上开发出更好的程序。 vue作为前端基础框架之一,更应该清楚其底层运行原理

mvvm模式

vue的mvvm模式如图所示 mvvm.png

内部基础对象

watcher 实例主要有三种情况
1. 主watcher, 每个vue实例都有一个主watcher,负责页面渲染
2. computed-watcher:computed选项生成的watcher,负责触发computed函数
3. watch-watcher:watch选项生成的watcher,负责触发自定义的watch函数

vue在mounted阶段 ,

//name属性对应一个dep对象,dep内部一个数组保存着收集的 wather
name -- {dep : [ watcher ] } 

//age属性对应一个dep对象,dep内部一个数组保存着收集的 wather
age  -- {dep : [ watcher ] } 
1.name 值变更 
2.name的setter 函数执行
3.name对应的dep触发dep.notify()
4.dep内部所有watcher 触发wather.update() 
// water.update()触发 1.页面更新  2.computed选项属性更新  3.wath选项属性更新

在vue初始化阶段,有一些重要的事情处理

vue初始化阶段

1.补全vue实例的相关属性,生命周期属性,Event事件注册,渲染函数相关初始化,处理option里面的数据等等。

//...省略
initLifecycle(vm); //给vue实例补充生命周期相关属性如,_isMounted,_isDestroyed等
initEvents(vm); 
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props

//重点: ininState这里处理了option里面的
//1. data属性(劫持getter,setter),
      //目前还未收集watche,此时model是如下

      name -- {dep : [   ] } 
      age -- {dep : [   ] } 

//2. computed属性(生成watcher),
//3. watch属性(生成watcher)
initState(vm);

initProvide(vm); //  resolve provide after data/props
callHook(vm, 'created');
//...省略
  1. 生成渲染函数 :源码地址
// 以下代码是我个人便于理解而写的,代码和源码不同,但是逻辑流程是一样的
var render 
if(option.render){
  render = option.render
}else if(option.template ){
   render = compile(option.template)
}else if(option.el) {
   rennder = compile(doucment.getElementById(option.el).outerHTML)
}else {
     throw new Error("...")
}

以上代码会将html模板转化为渲染函数即

<div id="app">
    <h2>{{name}}</h2>
     <h4>it is {{age}}</h4>
 </div>
//转化为
let _render = function () {
    with(this){
        return 
        _c('div',{attrs:{"id":"app"}},
            [
                _c('h2',[_v(_s(name))]),
                _c('h4',[_v("it is "+_s(age))])
            ]
        )
    }
}
vm.$option.render = render
//_c :返回vnode(虚拟dom节点)的函数
//_v: 返回vnode(虚拟文本节点)的函数
//_s: String()
转化过程就是:
1.html转astdom树
2.根据astdom树拼接 渲染函数字符串
3. new Function(`渲染函数字符串`)
所以说不写模板标签,直接写option里面写渲染函数性能会提高很多。但代价就是开发效率低,不够直观

3.生成主watcher ,代码如下

updateComponent = function () {
    vm._update(vm._render(), hydrating);
};

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


hydrating = false;
vm._isMounted = true;
callHook(vm, 'mounted');

new Watcher时第四个参数里面没有lazy属性 ,所以此watcher 会在构造函数内部执行watcher.getter(),也就是传入第二个参数updateComponent

看下watcher.getter执行流程

1.watcher.getter()
        ⬇
2.vm._update(vm._render(),hydrating) 
        ⬇
            1. vm._render()
                      ⬇
            2.vm.$option.render.call(vm,vm.$createElement) 
            //也就是以vm为上下文执行渲染函数(此函数在第二步中动态生成)           
                      ⬇
            3.执行 function () {
                        with(this){
                            return
                              _c('div',{attrs:{"id":"app"}},
                                [
                                  ```执行这里时,触发name的getter函数```
                                  _c('h2',[_v(_s(name))]), 
                                  ```执行这里时,触发age的getter函数```
                                  _c('h4',[_v("it is "+_s(age))]) 
                                ] 
                              )
                         }
                    }
                       ⬇
            4.执行name的get()和age的get()
                  function defineReactive$$1(obj,   key,val,) {
                    var dep = new Dep();
                    Object.defineProperty(obj, key, {
                      get: function reactiveGetter() {
                            `````````执行这里收集watcher`````
                             if (Dep.target) {dep.subs.push(Dep.target);}
                             return val
                           },
                       set: function reactiveSetter(newVal) {
                              if (newVal === val ) {return}
                              val = newVal;
                              dep.notify();
                           }
                    });
                  }
                             ⬇
             5.执行完get后 此时model是如下
                    ``` name -- {dep : [ watcher  ] } ```
                    ``` age -- {dep : [  watcher ] } ```
                            ⬇
             6 执行完vm._render后返回vnode,此时watcher收集完毕
        ⬇
3.vm._update(vnode,hydrating)
        ⬇
4.vm._update内部执行__patch__ ,
    if (!prevVnode) {
        //第一次渲染:__patch__根据vnode生成真实dom插入到$el中
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
        // 更新渲染:__patch__,使用diff算法比较prevVnode和vnode,最小化修正真实dom
        vm.$el = vm.__patch__(prevVnode, vnode);
    }      

以上代码执行完成,vue的初始化就已完成。

在mounted阶段
model此时数据如下:

name -- {dep : [ watcher ] } 
age  -- {dep : [ watcher ] } 

此时当 name的值变更时执行以下函数:

name 的setter() 执行
     ⬇
dep.notify()
     ⬇
watcher.update()
     ⬇
watcher.getter()
     ⬇
vm._update(vm._render(),hydrating) ) 

vm._update()执行流程 。上面已经详细的写过了,这里就不写了

在destroy阶段
vue组件
总结

vue运行总体流程大概就是这样,vue源码还有好多细节比如:

上一篇下一篇

猜你喜欢

热点阅读