MVVM MVVM是Model-View-ViewModel 的简写,是MVC的改进版.
在双向绑定中,Model和View之间没有耦合,通过操作Model,利用ViewModel提供的机制,自动实现ViewModel的更新。
 
Object.defineProperty Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
1 Object.defineProperty(obj, prop, descriptor) 
 
obj:要在其上定义属性的对象 
prop:要定义或修改的属性的名称 
descriptor:将被定义或修改的属性描述符。 
 
get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined 
set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function  Log ( ) {    var  val = null ;     var  arr = [];     var  id = 0 ;     Object .defineProperty (this , 'name' , {         get : function ( ) {             return  val;         },         set : function (newVal ) {           arr.push ({id :id++,oldVal :val,newVal :newVal});           val = newVal;         }     });     this .getLog  = function ( ) { return  arr; }; } var  lg = new  Log ();lg.name ="小明" ; lg.name ="小华" ; lg.name ="小刚" ; lg.getLog (); 
 
发布/订阅者模式 订阅者把自己想订阅的事件注册到调度中心,
当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
1 2             发布                  订阅 发布者       ->      调度中心       <-      订阅者(多个) 
 
vue双向绑定的实现Dep、Observer、Watcher:
Dep(调度中心):存放收集Watcher,Watcher的实例就是订阅者。 
Observer(发布者):是一个类(class),每个Observer都附有被观察的对象,观察的对象,一旦连接,观察者就转换目标对象的属性键成为getter/setters,收集依赖关系并发送更新 
Watcher(订阅者):观察者分析表达式,收集依赖关系,并在表达式值更改时触发回调。 
 
Watcher(订阅者) 解析对象路径函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function  parsePath  (path) {    var  bailRE = /[^\w.$]/ ;     if  (bailRE.test (path)) {         return      }     var  segments = path.split ('.' );     return  function  (obj ) {         for  (var  i = 0 ; i < segments.length ; i++) {             if  (!obj) { return  }             obj = obj[segments[i]];         }         return  obj     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var  Watcher  = function  Watcher (vm,expOrFn,cb ){    this .vm  = vm;     this .cb  = cb;     this .expression  = expOrFn;     this .getter  = parsePath (expOrFn);     this .value  = this .get () } Watcher .prototype  .get  = function ( ){    Dep .target  = this ;     var  data = this .vm .data ;     var  value = this .getter .call (data, data);          return  value; } Watcher .prototype  .update  = function  ( ){         var  value = this .get ()     var  oldVal = this .value ;     if (value!==oldVal){         this .value  = value          this .cb .call (this .vm ,value,oldVal);     } } Watcher .prototype  .addDep  = function  addDep  (dep) {    dep.addSub (this ); }; 
 
Dep(调度中心) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 var  Dep  = function  Dep  () {    this .subs  = [] }; Dep .target  = null ;Dep .prototype  .addSub  = function  addSub  (sub) {    this .subs .push (sub); }; Dep .prototype  .removeSub  = function  removeSub  (sub) {    remove (this .subs , sub); }; Dep .prototype  .notify  = function  notify  () {         var  subs = this .subs .slice ();     console .log ('notify' ,subs)     for  (var  i = 0 , l = subs.length ; i < l; i++) {                  subs[i].update ();     } }; Dep .prototype  .depend  = function  depend  () {    if  (Dep .target ) {         Dep .target .addDep (this );     } }; 
 
Observer(发布者) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 var  Observer  = function  Observer  (data) {    this .data  = data;     this .dep  = new  Dep ();     this .walk (data); }; Observer .prototype  .walk  = function  walk  (obj) {    var  keys = Object .keys (obj);     for  (var  i = 0 ; i < keys.length ; i++) {         this .defineReactive (obj, keys[i], obj[keys[i]]);     } }; Observer .prototype  .defineReactive  = function ( obj, key,val ){         var  _this = this ;     var  dep = new  Dep ();     observe (val);     Object .defineProperty (obj, key, {         enumerable : true ,         configurable : true ,         get : function  reactiveGetter  () {                          if (Dep .target ){                                                   dep.addSub (Dep .target )             }             return  val;         },         set : function  reactiveSetter  (newVal) {                                       if  (newVal === val) {                 return              }             val = newVal;             observe (newVal);                          dep.notify ();                      }     }); } Observer .prototype  .$watcher  = function (expOrFn,cb ){    new  Watcher (this ,expOrFn,cb); } function  observe  (val,vm){    if (Object .prototype  .toString .call (val) ==="[object Object]" ){         new  Observer (val);     } } return  Observer ;
 
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var  obj = {name :'小明' ,other :20 ,power :{        p1 :'有钱' ,     }, }; var  app = new  Observer (obj);app.$watcher('name' , function (newVal,oldVal ) {     console .log ('打印name' ,newVal,oldVal) }); app.$watcher('power.p1' , function (newVal,oldVal ) {     console .log ('power.p1' ,newVal,oldVal) }); setTimeout (function ( ){    obj.name  = "小刚" ;     obj.power .p1  = '会打王者' ; },1500 ); 
 
抽象语法树AST(abstract syntax tree) AST是指抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式。
Vue.js v2.5.13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 var  createCompiler = createCompilerCreator (function  baseCompile  (  template,   options ) {   var  ast = parse (template.trim (), options);   if  (options.optimize  !== false ) {     optimize (ast, options);   }   var  code = generate (ast, options);   return  {     ast : ast,     render : code.render ,     staticRenderFns : code.staticRenderFns    } }); function  parse  (  template,   options ) {     var  root;          parseHTML (template, {         warn : warn$2,                  start : function  start  (tag, attrs, unary) {}         end : function  end  () {},         chars : function  chars  (text) {},         comment : function  comment  (text){}     })     return  root } 
 
vue将模板字符串给parse函数,最终返回一个抽象语法树(AST)
parse函数内部会调用一个parseHTML,来完成对模板字符串解析,最终返回个js语法的树形结构。
执行generate函数返回code中有个render函数和staticRenderFns函数
虚拟DOM(VNode) 文档对象模型(Document Object Model,简称DOM)。用jquery在网页中直接操作dom结构,相对于原生API无疑方便了很多。
为了追求性能、和更方便的对DOM维护,于是就引入了虚拟DOM。
用JS表示DOM结构,操作DOM慢,而javascript很快,用javascript对象可以很容易地表示DOM节点。DOM节点包括标签、属性和子节点.
Vue.js v2.5.13(VNode.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 /* @flow */ export  default class VNode {  tag: string | void;/* 标签类型 */   data: VNodeData | void; /* 节点上的class,attribute,style 事件 */   children: ?Array<VNode>;/* 存放的是子虚拟节点 */   text: string | void;/* 表明文本节点内容 */   elm: Node | void;/* 虚拟节点对应的真实DOM节点 */   ns: string | void;/* html命名空间 */   context: Component | void; /* 在此组件的作用域 */   key: string | number | void; /* 是vnode的标记 */   componentOptions: VNodeComponentOptions | void;/* 组件参数 */   componentInstance: Component | void; /* 组件实例 */   parent: VNode | void; /*  */   /*  严格内部 */   raw: boolean; /* 包含原始HTML? (仅限服务器) */   isStatic: boolean; /* 悬挂静态节点 */   isRootInsert: boolean; /* 进入转换检查所必需的 */   isComment: boolean; /* 空注释占位符? */   isCloned: boolean; /*是否是一个克隆节点?  */   isOnce: boolean; /* 是否是一次性节点? */   asyncFactory: Function | void; /* 异步组件 factory function  */   asyncMeta: Object | void;   isAsyncPlaceholder: boolean;   ssrContext: Object | void;   fnContext: Component | void; /* 真实的上下文虚拟功能节点 */   fnOptions: ?ComponentOptions; /* 用于SSR缓存 */   fnScopeId: ?string; /*  功能范围id 支持 */   constructor (     tag?: string,     data?: VNodeData,     children?: ?Array<VNode>,     text?: string,     elm?: Node,     context?: Component,     componentOptions?: VNodeComponentOptions,     asyncFactory?: Function   ) {     this.tag = tag     this.data = data     this.children = children     this.text = text     this.elm = elm     this.ns = undefined     this.context = context     this.fnContext = undefined     this.fnOptions = undefined     this.fnScopeId = undefined     this.key = data && data.key     this.componentOptions = componentOptions     this.componentInstance = undefined     this.parent = undefined     this.raw = false      this.isStatic = false      this.isRootInsert = true      this.isComment = false      this.isCloned = false      this.isOnce = false      this.asyncFactory = asyncFactory     this.asyncMeta = undefined     this.isAsyncPlaceholder = false    }   /**    * 弃用: 别名 为组件实例 向后兼容.    * istanbul ignore next    */   get child (): Component | void {     return  this.componentInstance   } } /* 创建一个空的vnode实例 */ export  const createEmptyVNode = (text: string = '' ) => {  const node = new VNode()   node.text = text   node.isComment = true    return  node } /* 创建一个文本vnode */ export  function  createTextVNode (val: string | number) {  return  new VNode(undefined, undefined, undefined, String(val)) } /**  * 优化的浅层克隆  * 用于静态节点和插槽节点,因为它们可能会被重复使用  * 多重渲染,克隆它们可以避免DOM操作依赖时的错误  * 克隆一个vnode  */ export  function  cloneVNode (vnode: VNode): VNode {  const cloned = new VNode(     vnode.tag,     vnode.data,     vnode.children,     vnode.text,     vnode.elm,     vnode.context,     vnode.componentOptions,     vnode.asyncFactory   )   cloned.ns = vnode.ns   cloned.isStatic = vnode.isStatic   cloned.key = vnode.key   cloned.isComment = vnode.isComment   cloned.fnContext = vnode.fnContext   cloned.fnOptions = vnode.fnOptions   cloned.fnScopeId = vnode.fnScopeId   cloned.isCloned = true    return  cloned } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function  mountComponent  ( vm, el, hydrating) {  vm.$el  = el;   if  (!vm.$options .render ) {     vm.$options .render  = createEmptyVNode;        }   callHook (vm, 'beforeMount' );   var  updateComponent;   if  ("development"  !== 'production'  && config.performance  && mark) {     updateComponent = function  ( ) {       vm._update (vnode, hydrating);            };   } else  {     updateComponent = function  ( ) {       vm._update (vm._render (), hydrating);     };   }            new  Watcher (vm, updateComponent, noop, null , true  );   hydrating = false ;      return  vm } 
 
在初始化的时候执行mountComponent函数方法,其中的vm._render()函数返回虚拟Dom(在模板编译后生成的render方法挂载了实例上)
其中的updateComponent传给了Watcher,而updateComponent中有vm._update(vnode, hydrating)方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Vue .prototype  ._update  = function  (vnode, hydrating ) {  var  vm = this ;   if  (vm._isMounted ) {     callHook (vm, 'beforeUpdate' );   }   var  prevEl = vm.$el ;   var  prevVnode = vm._vnode ;   var  prevActiveInstance = activeInstance;   activeInstance = vm;   vm._vnode  = vnode;         if  (!prevVnode) {          vm.$el  = vm.__patch__ (       vm.$el , vnode, hydrating, false  ,       vm.$options ._parentElm ,       vm.$options ._refElm      );               vm.$options ._parentElm  = vm.$options ._refElm  = null ;   } else  {          vm.$el  = vm.__patch__ (prevVnode, vnode);   }    }; 
 
传入新的vnode与老的vnode进行diff,来进行dom更新操作,其中的vm.__patch__方法在入口处已经注入
定义patch补丁函数:
1 var  patch = createPatchFunction ({ nodeOps : nodeOps, modules : modules });
 
注入patch方法:
1 2 Vue .prototype  .__patch__  = inBrowser ? patch : noop;
 
函数createPatchFunction返回patch方法,其中的patch方法之后来完成vnode的diff过程和打补丁pacth过程,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function  sameVnode  (a, b) {     } function  createPatchFunction  (backend) {    function  emptyNodeAt  (elm) {return  new  VNode (nodeOps.tagName (elm).toLowerCase (), {}, [], undefined , elm)}     function  createRmCb  (childElm, listeners)}     function  removeNode  (el) {}          function  patchVnode  (oldVnode, vnode, insertedVnodeQueue, removeOnly){}     function  updateChildren  (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {}     function  invokeInsertHook  (vnode, queue, initial) {}          return  function  patch  (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {                  if  (isUndef (vnode)) {           if  (isDef (oldVnode)) { invokeDestroyHook (oldVnode); }           return          }                  if  (isUndef (oldVnode)) {                          isInitialPatch = true ;             createElm (vnode, insertedVnodeQueue, parentElm, refElm);         } else  {             var  isRealElement = isDef (oldVnode.nodeType );             if  (!isRealElement && sameVnode (oldVnode, vnode)) {                                  patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly);             } else  {                              }         }                  invokeInsertHook (vnode, insertedVnodeQueue, isInitialPatch);         return  vnode.elm      } } 
 
vue生命周期 生命周期钩子(Vue.js v2.5.13)
1 2 3 4 5 6 7 8 9 10 11 12 13 var  LIFECYCLE_HOOKS  = [  'beforeCreate' ,   'created' ,   'beforeMount' ,   'mounted' ,   'beforeUpdate' ,   'updated' ,   'beforeDestroy' ,   'destroyed' ,   'activated' ,   'deactivated' ,   'errorCaptured'  ]; 
 
版本:Vue.js v2.5.13
beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。 
created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。 
beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。(该钩子在服务器端渲染期间不被调用) 
mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。(该钩子在服务器端渲染期间不被调用。)
注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted 
 
 
beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。(该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。) 
updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子(该钩子在服务器端渲染期间不被调用) 
activated:keep-alive 组件激活时调用(该钩子在服务器端渲染期间不被调用) 
deactivated:keep-alive 组件停用时调用(该钩子在服务器端渲染期间不被调用) 
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。 
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁(该钩子在服务器端渲染期间不被调用) 
errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 
 
new Vue()调用构造函数,执行this._init(options)
构造函数Vue$3:
1 2 3 4 5 6 7 8 9 function  Vue$3  (options) {  if  ("development"  !== 'production'  &&     !(this  instanceof  Vue $3)   ) {     warn (`Vue是一个构造函数,应该用new关键字` );   }   this ._init (options); } return  Vue $3;
 
原型对象上的_init方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Vue .prototype  ._init  = function  (options ) {  var  vm = this ;      {     initProxy (vm);   }      initLifecycle (vm);      initEvents (vm);   initRender (vm);      callHook (vm, 'beforeCreate' );   initInjections (vm);       initState (vm);   initProvide (vm);       callHook (vm, 'created' );      if  (vm.$options .el ) {          vm.$mount(vm.$options .el );  } } 
 
初始化props、methods、data、computed、watch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function  initState  (vm) {  vm._watchers  = [];   var  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  );   }      if  (opts.computed ) { initComputed (vm, opts.computed ); }      if  (opts.watch  && opts.watch  !== nativeWatch) {     initWatch (vm, opts.watch );  } } 
 
初始化数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function  initData  (vm) {  var  data = vm.$options .data ;   data = vm._data  = typeof  data === 'function' ? getData (data, vm): data || {};      var  keys = Object .keys (data);   var  i = keys.length ;      while  (i--) {     var  key = keys[i];               proxy (vm, "_data" , key);   }      observe (data, true  ); } 
 
代理数据到实例上:
1 2 3 4 5 6 7 8 9 function  proxy  (target, sourceKey, key) {  sharedPropertyDefinition.get  = function  proxyGetter  () {     return  this [sourceKey][key]   };   sharedPropertyDefinition.set  = function  proxySetter  (val) {     this [sourceKey][key] = val;   };   Object .defineProperty (target, key, sharedPropertyDefinition); } 
 
尝试创建一个Observer实例,如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
1 2 3 4 5 6 7 8 9 function  observe  (value, asRootData) {  if  (!isObject (value) || value instanceof  VNode ) {     return    }   var  ob;      ob = new  Observer (value);   return  ob } 
 
挂载组件,并编译模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 var  createCompiler = createCompilerCreator (function  baseCompile  (  template,   options ) {   var  ast = parse (template.trim (), options);   if  (options.optimize  !== false ) {     optimize (ast, options);   }   var  code = generate (ast, options);   return  {     ast : ast,     render : code.render ,     staticRenderFns : code.staticRenderFns    } }); var  ref$1 = createCompiler (baseOptions);var  compileToFunctions = ref$1.compileToFunctions ;var  mount = Vue $3.prototype  .$mount ;Vue $3.prototype  .$mount  = function  (  el,   hydrating  ) {  el = el && query (el);      var  options = this .$options ;         if  (!options.render ) {     var  template = options.template ;     if  (template) {            } else  if  (el) {              template = getOuterHTML (el);     }     if  (template) {                     var  ref = compileToFunctions (template, {         shouldDecodeNewlines : shouldDecodeNewlines,         shouldDecodeNewlinesForHref : shouldDecodeNewlinesForHref,         delimiters : options.delimiters ,         comments : options.comments        }, this );       var  render = ref.render ;       var  staticRenderFns = ref.staticRenderFns ;       options.render  = render;       options.staticRenderFns  = staticRenderFns;            }   }      return  mount.call (this , el, hydrating) }; 
 
当模板编译了之后调用公共挂载方法:
1 2 3 4 5 6 7 8 Vue $3.prototype  .$mount  = function  (  el,   hydrating  ) {  el = el && inBrowser ? query (el) : undefined ;   return  mountComponent (this , el, hydrating) }; 
 
挂载组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function  mountComponent  (  vm,   el,   hydrating ) {   vm.$el  = el;   if  (!vm.$options .render ) {     vm.$options .render  = createEmptyVNode;        }   callHook (vm, 'beforeMount' );   var  updateComponent;   if  ("development"  !== 'production'  && config.performance  && mark) {     updateComponent = function  ( ) {              vm._update (vnode, hydrating);            };   } else  {     updateComponent = function  ( ) {       vm._update (vm._render (), hydrating);     };   }      new  Watcher (vm, updateComponent, noop, null , true  );   hydrating = false ;      if  (vm.$vnode  == null ) {     vm._isMounted  = true ;     callHook (vm, 'mounted' );   }   return  vm } 
 
强制更新:
1 2 3 4 5 6 Vue .prototype  .$forceUpdate  = function  ( ) {  var  vm = this ;   if  (vm._watcher ) {     vm._watcher .update ();   } };