vue2源码学习1
1.vue源码解读流程
 new Vue 调用的是 Vue.prototype._init 从该函数开始 
 经过 $options 参数合并之后 
 initLifecycle 初始化生命周期标志 初始化事件,
 初始化渲染函数。
 初始化状态就是数据。
 把数据添加到观察者中实现双数据绑定。
2.双数据绑定原理是
obersve()方法判断value没有没有__ob___属性并且是不是Obersve实例化的, 
value是不是Vonde实例化的,如果不是则调用Obersve 去把数据添加到观察者中,为数据添加__ob__属性, 
Obersve 则调用defineReactive方法,该方法是连接Dep和wacther方法的一个通道,
利用Object.definpropty() 中的get和set方法 监听数据。
get方法中是new Dep调用depend()。
为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 
然后set方法就是调用dep中的notify 方法调用wacther中的run 更新视图 
3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板
就是获取到html,然后通过paseHTML这个方法转义成ast模板 ,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html, 然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后, 在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数, 编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过diff算法变成正真正的dom元素。
4.vue.js异步更新DOM策略
watcher队列:
   当某个响应式数据发生变化时,他的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有watch对象。触发watch
对象的update实现。
   update(){
     if(this.lazy){
       this.dirty = true
     }else if(this.sync){
       this.run();//同步执行run直接渲染视图
     }else{
       queueWatcher(this);//异步推送到观察者队列中,下一个tick时调用
     }
   }
   //异步执行DOM更新
   export function queueWatcher(watcher: Watcher){
     const id = watcher.id;
     if(has[id]==null){ //检验id是否存在,已经存在则直接跳过,不存在则标记哈希,用于下次检验
       has[id] = true
       if(!flushing){
         queue.push(watcher);//如果没有flush掉,直接push到队列中即可
       }else{
         //Watch对象并不是立即更新视图,而是被push进一个队列queue,此时状态处于waiting的状态,
         这时候会继续有watch对象被push进这个队列queue,等到下一个tick运行时,这些Watch对象才会被遍历取出
         更新视图。同时,id重复的Watch不会被多次加入到queue中。
         let i = queue.length -1;
         while(i>=0 && queue[i].id > watcher.id){
           i--
         }
         queue.splice(Math.max(i, index)+1,0,watcher)
       }
       if(!waiting){
         waiting = true
         nextTick(flushSchedulerQueue);// flushSchedulerQueue是下一个tick时的回调函数,主要目的是执行Watcher的run函数,
         用来更新视图。
       }
     }
   }
nextTick函数:执行目的是在 微任务或者task中推入一个function,在当前栈执行完毕以后执行nextTick传入的function
一共有promise, mutationObserver以及setTimeout三种尝试得到timerFunc的方法,优先使用promise,在promise不存在
的情况下使用mutationObserver,这两个方法都会在微任务中执行,会比setTimeout更早执行,所以优先使用。
如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。
nextTick目的是产生一个回调函数加入task或者微任务中,当前栈执行完以后调用该回调函数,起到异步触发(即下一个tick时触发)的目的。
为什么要异步更新视图?
<template>
  <div>
    <div>{{test}}</div>
  </div>
</template>
<script>
export default{
  data(){ return { test: 0 } },
  mounted(){
   for(let i=0;i<1000;i++){
    this.test++;
   }
  }
}
</script>
回答:现在有这样的一种情况,mounted的时候test的值会被++循环执行1000次。
每次++时,都会根据响应式触发setter->Dep->Watcher->update->patch。
如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。 
所以Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run。
同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。
最终更新视图只会直接将test对应的DOM的0变成1000。 
保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick的时候调用,大大优化了性能。
参考链接:
https://github.com/answershuto/learnVue/blob/master/docs/Vue.js%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0DOM%E7%AD%96%E7%95%A5%E5%8F%8AnextTick.MarkDown
5.vue组件间通信
组件:可以扩展HTML元素,封装可重用的代码。
vue组件间通信:
   父组件向子组件通信:
     方法一:props
     方法二:使用$children, 使用$children可以在父组件中访问子组件。
   子组件向父组件通信:
     方法一:使用$emit事件
     方法二:使用$parent, 使用$parent访问父组件的数据
   非父子组件,兄弟组件之间的数据传递:
     使用一个Vue实例作为中央事件总线。
     Vue内部有一个事件机制,$on方法用来监听一个事件,$emit用来触发一个事件。
     //新建一个vue实例作为中央事件总线
     let event = new Vue();
     //监听事件
     event.$on('eventName', (val)=>{
       //do something
     })
     //触发事件
     event.$emit('eventName','this is a message')
   复杂的单页应用数据管理使用vuex。
6.事件机制
四个事件API,即 $on, $once, $off, $emit。
$on方法用来在vm实例上监听一个自定义事件,改事件可用$emit触发。
$once监听一个只能触发一次的事件,在触发以后会自动移除该事件。
$off用来移除自定义事件。
$emit用来触发指定的自定义事件。
7.VNode节点
虚拟DOM,即是真实DOM树的一层抽象,用属性描述真实DOM的各个特性。当发生变化的时候,就会去修改视图。
vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM,可以对这颗抽象树进行创建节点,
删除节点以及修改节点等操作,在这个过程中都不需要操作真实DOM,只需要操作JavaScript对象后只对差异修改,减少了很多不需要的DOM操作,大大提高了性能。
VNode基类:
export default class VNode{
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  context: Component | void;
  ...
  constructor(tag?:string, data?:VNodeData, children?:?Array<VNode>, 
  text?:string, elm?: Node, context?:Component,
  componentOptions?: VNodeComponentOptions){
     this.tag = tag; //当前节点的标签名
     this.data = data;// 当前节点对应的对象,包含了具体的一些数据信息
     this.children = children; //当前节点的子节点,是一个数组
     this.text = text; //当前节点的文本
     this.elm = elm;// 当前虚拟节点对应的真实dom节点
     this.componentOptions = componentOptions; //组件的option选项
     ...
  }
}
修改视图: vue通过数据绑定来修改视图,当某个数据被修改的时候,set方法会让闭包中的Dep调用notify通知所有订阅者Watcher,Watcher通过get方法执行
vm._update(vm._render,hydrating)。
patch:将新老VNode节点进行比对,然后根据两者的比较结果进行最小单位地修改视图。
sameVnode实现: 判断两个VNode节点是否是同一个节点,需要满足以下条件:
   key相同;
   tag(当前节点的标签名)相同;
   isComment(是否为注释节点)相同;
   是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型)都有定义
   当标签是<input>的时候,type必须相同。
patchVnode规则:
  1.如果新旧VNode都是静态的,同时他们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once,那么只需要替换elm以及componentInstance即可。
  2.新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心
  3.如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。
  4.当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点
  5.当新老节点都无子节点的时候,只是文本的替换。
8.从template到DOM
从 new 一个vue对象开始:
let vm = new Vue({
  el: '#app'
})
Vue构造类:
function Vue(options){
  //初始化
  this._init(options);
}
Vue.prototype._init = function(options?: Object){
  //初始化生命周期
  initLifecycle(vm)
  //初始化事件
  initEvents(vm);
  //初始化render
  initRender(vm);
  //调用beforeCreate钩子函数并且触发beforeCreate钩子事件
  callHook(vm, 'beforeCreate');
  initInjectiongs(vm);
  //初始化props, methods, data, compued, watch
  initState(vm);
  initProvide(vm);
  //调用created钩子函数并且触发created钩子事件
  callHook(vm, 'creatd');
  ...
  if(vm.$options.el){
    //挂载组件
    vm.$mount(vm.$options.el);
  }
}
_init主要做了这两件事:1.初始化(包括生命周期,事件,render函数,state等)和$mount组件。
在生命钩子beforeCreate与created之间会初始化state,在这个过程中,会一次初始化props,methods,data,computed与watch。
export function initState(vm: Component){
  vm.watchers = [];
  const opts = vm.$options;
  if(opts.props) initProps(vm, opts.props);
  if(opts.methods) initMethods(vm, opts.methods);
  if(opts.data){
    initData(vm)
  }else{
    observe(vm._data = {}, true); //该组件没有data的时候绑定一个对象
  }
  if(opts.computed) initComputed(vm, opts.computed)
  if(opts.watch) initWatch(vm, opts.watch)
}
双向绑定:
   observe会通过defineReactive对data中的对象进行双向绑定,最终通过Object.defineProperty对对象设置setter以及getter方法。getter方法主要用来进行
依赖收集。setter方法会在对象被修改的时候触发,这时候setter会通知闭包中的Dep,Dep订阅了这个对象改变的watcher观察者对象,Dep会通知watcher对象更新视图。
https://github.com/answershuto/learnVue/blob/master/docs/%E4%BB%8Etemplate%E5%88%B0DOM(Vue.js%E6%BA%90%E7%A0%81%E8%A7%92%E5%BA%A6%E7%9C%8B%E5%86%85%E9%83%A8%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6).MarkDown
9.数据绑定
首先通过一次渲染操作触发Data的getter进行依赖收集,在data发生变化的时候会触发它的setter,setter通知watcher,watcher进行回调
通知组件重新渲染的函数,之后根据diff算法来决定是否发生视图的更新。
vue在初始化组件数据时,在生命周期的beforeCreate与created钩子函数之间实现了对data, props, computed,methods,events及
watch的处理。
initData 主要是初始化data中的数据,将数据进行Observer,监听数据的变化,其他的监视原理一致。
function initData(vm:Component){
  //得到data数据
  ...
  //判断是否是对象
  ...
  //遍历data对象
  const keys = Object.keys(data);
  const props = vm.$options.props;
  let i = keys.length
  //遍历data中的数据
  //observe data,开始对数据进行绑定,下面会进行递归observe进行深层对象绑定
  observe(data, true);
}
proxy代理
observe:创建一个Observer实例(__ob__),如果成功创建observer实例则返回新的observer实例,如果已有observer实例则返回现有的
observer实例。
export function observe(value: any, asRootData:?boolean):Observer|void{
  
}
Observer: 用来遍历对象的所有属性将其进行双向绑定。
export class {
  value: any;
  dep: Dep;
  vmCount: number;
  
  constructor(value: any){
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    
    def(value, '__ob__', this);//将observer实例绑定到data的__ob__属性上面去
    if(Array.isArray(value)){
      
    }else {
      this.walk(value);//如果是对象则直接walk进行绑定
    }
  }
  walk(obj: Object){
    const keys = Object.keys(obj);
    //walk方法会遍历对象的每一个属性进行defineReactive绑定
    for(let i=0;i<keys.length;i++){
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  }
  observeArray(items: Array<any>){
    //数组需要遍历每一个成员进行observe
    for(let i=0, l=items.length;i<l;i++){
      observe(items[i]);
    }
  }
}
Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,然后为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定
上方法。
watcher 是一个观察者对象。依赖收集以后watcher对象会被报错在Deps中,数据变动的时候会由Deps通知watcher实例,然后由watcher实例
回到cb进行视图的更新。
export default class Watcher {
}
Dep类,Dep就是一个发布者,可以订阅多个观察者,依赖收集之后Deps中会存在一个或读个watcher对象,在数据变更的时候通知所有的watcher。
export default class Dep{
  static target:?Watcher;
  id: number;
  subs: Array<Watcher>;
  constructor(){
    this.id = uid++;
    this.subs = [];
  }
  addSub(sub:Watcher){
    this.subs.push(sub);//添加一个观察者对象
  }
  removeSub(sub:Watcher){
    remove(this.subs, sub);//移除一个观察者对象
  }
  depend(){
    if(Dep.target){
      Dep.target.addDep(this);//依赖收集,当存在Dep.target的时候添加观察者对象
    }
  }
  //通知所有订阅者
  notify(){
    const subs = this.subs.slice();
    for(let i=0;i<subs.length;i++){
     subs[i].update();
    }
  }
}
function remove(arr,item){
  if(arr.length){
    const index = arr.indexOf(item);
    if(index>-1){
      return arr.splice(index, 1);
    }
  }
}
Dep.target = null; //依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。
defineReactive: 通过Object.defineProperty为数据定义上getter/setter方法,进行依赖收集后闭包中的Deps会存放Watcher对象。
触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行视图的更新。
https://github.com/answershuto/learnVue/blob/master/docs/%E4%BB%8E%E6%BA%90%E7%A0%81%E8%A7%92%E5%BA%A6%E5%86%8D%E7%9C%8B%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A.MarkDown
10.依赖收集
从Dep说起,见上文中Dep类,定义一个依赖收集类Dep。
   当对data上的对象进行修改值得时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以在最开始进行一次render,那么所有
   被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。
Watcher:订阅者,当依赖收集的时候会addSub到sub中,在修改data中的数据的时候会触发dep对象的notify,通知所有watcher对象去修改
对应视图。
   class Watcher {
      constructor(vm, expOrFn, cb, options){
        this.cb = cb;
        this.vm = vm;
        Dep.target = this;
        this.cb.call(this.vm);
      }
      update(){
        this.cb.call(this.vm);
      }
   }
开始依赖收集:
   class Vue{
   }
   function defineReactive(obj, key, val, cb){
     const dep = new Dep();
     Object.defineProperty(obj, key, {
       enumerable: true,
       configurable: true,
       get:()=>{
         if(Dep.target){
          dep.addSub(Dep.target);
         }
       },
       set: newVal =>{
         dep.notify();
       }
     })
   }
   Dep.target = null;
将观察者watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。
有Dep.target的对象会将watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的watcher实例的update方法
进行渲染。
    如果快乐太难,那祝你平安。
                    
                
                
            
        
浙公网安备 33010602011771号