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 (); } };