You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionVue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)}
Vue.prototype._init=function(options){initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm,'beforeCreate');// 注意:beforeCreate阶段完成后,我们的options被merge到了vm.$options属性上,此时是获取不到this.xxx数据的,如果我们要获取data数据需要this.$options.data()来获取 initInjections(vm);// 对data进行了一些Observe 执行了defineReactive 标记1initState();initProvide(vm);// resolve provide after data/propscallHook(vm,'created');/* istanbul ignore if */if("development"!=='production'&&config.performance&&mark){vm._name=formatComponentName(vm,false);mark(endTag);measure(("vue "+(vm._name)+" init"),startTag,endTag);}if(vm.$options.el){// 对模板进行了compile 生成了render function, 调用render function 生成了vmdomvm.$mount(vm.$options.el);}}
阶段一 initState()
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 /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
Vue.prototype._init=function(options){initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm,'beforeCreate');initInjections(vm);// resolve injections before data/propsinitState(vm);initProvide(vm);// resolve provide after data/propscallHook(vm,'created');/* istanbul ignore if */if("development"!=='production'&&config.performance&&mark){vm._name=formatComponentName(vm,false);mark(endTag);measure(("vue "+(vm._name)+" init"),startTag,endTag);}if(vm.$options.el){vm.$mount(vm.$options.el);}}
varcreateCompiler=createCompilerCreator(functionbaseCompile(template,options){varast=parse(template.trim(),options);if(options.optimize!==false){optimize(ast,options);}varcode=generate(ast,options);return{ast: ast,render: code.render,staticRenderFns: code.staticRenderFns}});varref$1=createCompiler(baseOptions);varcompileToFunctions=ref$1.compileToFunctions;functioncreateCompileToFunctionFn(compile){varcache=Object.create(null);returnfunctioncompileToFunctions(template,options,vm){options=extend({},options);varwarn$$1=options.warn||warn;deleteoptions.warn;
....// 走到了这里varkey=options.delimiters
? String(options.delimiters)+template
: template;if(cache[key]){returncache[key]}// 进入这里开始compile, 我们先来分析这个compilevarcompiled=compile(template,options);
...
varres={};varfnGenErrors=[];res.render=createFunction(compiled.render,fnGenErrors);res.staticRenderFns=compiled.staticRenderFns.map(function(code){returncreateFunction(code,fnGenErrors)});// check function generation errors.// this should only happen if there is a bug in the compiler itself.// mostly for codegen development use/* istanbul ignore if */{if((!compiled.errors||!compiled.errors.length)&&fnGenErrors.length){warn$$1("Failed to generate render function:\n\n"+fnGenErrors.map(function(ref){varerr=ref.err;varcode=ref.code;return((err.toString())+" in\n\n"+code+"\n");}).join('\n'),vm);}}return(cache[key]=res)}}
functionmarkStatic(node: ASTNode){// 通过isStatic 方法来判断node结点是否为静态结点 node.static=isStatic(node)if(node.type===1){// do not make component slot content static. this avoids// 1. components not able to mutate slot nodes// 2. static slot content fails for hot-reloadingif(!isPlatformReservedTag(node.tag)&&node.tag!=='slot'&&node.attrsMap['inline-template']==null){return}for(leti=0,l=node.children.length;i<l;i++){constchild=node.children[i]markStatic(child)if(!child.static){node.static=false}}}}
看一下isStatic方法
functionisStatic(node: ASTNode): boolean{if(node.type===2){// expressionreturnfalse// 表达式肯定不是静态结点}if(node.type===3){// textreturntrue// 文本肯定是静态结点}return!!(node.pre// v-pre 指令,此时子节点是不做编译的||(!node.hasBindings&&// no dynamic bindings!node.if&&!node.for&&// not v-if or v-for or v-else!isBuiltInTag(node.tag)&&// not a built-in 内置标签包括slot 和 componentisPlatformReservedTag(node.tag)&&// 是平台保留标签html和svg标签!isDirectChildOfTemplateFor(node)&&Object.keys(node).every(isStaticKey)// 不是template标签的直接子元素且没有包含在for循环中))}
然后compile结束后,我们得到render function 之后,开始执行$mount的公用方法(源码位置src/platforms/web/runtime/index.js) 其实就是调用了mountComponent方法
// 接下来进入这个方法returnmount.call(this,el,hydrating)// public mount methodVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{el=el&&inBrowser ? query(el) : undefined// 本质是进入这个方法 returnmountComponent(this,el,hydrating)}
阶段四 执行renderFunction 得到VNode
exportfunctionmountComponent(vm: Component,el: ?Element,hydrating?: boolean): Component{
...
callHook(vm,'beforeMount')letupdateComponent/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){
...
}else{updateComponent=()=>{// 重点看这里执行了render()方法生成了vnodevm._update(vm._render(),hydrating)}}// 入口:下面执行了new Watcher, 这是我们的重点入口[1]newWatcher(vm,updateComponent,noop,null,true/* isRenderWatcher */)hydrating=false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif(vm.$vnode==null){vm._isMounted=truecallHook(vm,'mounted')}returnvm}
阶段五 进入Watcher 类
varWatcher=functionWatcher(vm,expOrFn,cb,options,isRenderWatcher){
...
if(typeofexpOrFn==='function'){this.getter=expOrFn;}else{this.getter=parsePath(expOrFn);if(!this.getter){this.getter=function(){};"development"!=='production'&&warn("Failed watching path: \""+expOrFn+"\" "+'Watcher only accepts simple dot-delimited paths. '+'For full control, use a function instead.',vm);}}// 重点看下这里,调用了Watcher的get方法,我们看下get方法this.value=this.lazy
? undefined
: this.get();};Watcher.prototype.get=functionget(){// 这里有个关键点pushTarget(this);varvalue;varvm=this.vm;try{value=this.getter.call(vm,vm);}catch(e){if(this.user){handleError(e,vm,("getter for watcher \""+(this.expression)+"\""));}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value);}popTarget();this.cleanupDeps();}returnvalue};
Watcher.prototype.get=functionget(){pushTarget(this);varvalue;varvm=this.vm;try{//getter对应new Watcher时我们传入的第二个参数 new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)即updateComponentvalue=this.getter.call(vm,vm);}catch(e){if(this.user){handleError(e,vm,("getter for watcher \""+(this.expression)+"\""));}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value);}popTarget();this.cleanupDeps();}returnvalue};functionpushTarget(_target){if(Dep.target){targetStack.push(Dep.target);}Dep.target=_target;}updateComponent=function(){vm._update(vm._render(),hydrating);};
Vue.prototype._render=function(){varvm=this;varref=vm.$options;varrender=ref.render;var_parentVnode=ref._parentVnode;
...
vm.$vnode=_parentVnode;// render selfvarvnode;try{// 重点看这里,其实就是执行了我们的render functionvnode=render.call(vm._renderProxy,vm.$createElement);}catch(e){
...
}// return empty vnode in case the render function errored outif(!(vnodeinstanceofVNode)){if("development"!=='production'&&Array.isArray(vnode)){warn('Multiple root nodes returned from render function. Render function '+'should return a single root node.',vm);}vnode=createEmptyVNode();}// set parentvnode.parent=_parentVnode;returnvnode};}
vnode = render.call(vm._renderProxy, vm.$createElement);这个方法的调用,相当于执行了我们之前得到的render function。并且我们知道render function 里面有with(this) 此时我们把this指向了vm, 所以按照我们的例子, s(a+b) 在执行的时候会读取到vm.a 和 vm.b 就分别进入了a 和 b 的getter。再回头看下getter函数。
所以注意这里就是Watcher 与 Dep 关联的地方,可以认为compile 是Watcher 与 Dep之间的桥梁。
Vue.prototype._update=function(vnode,hydrating){
...
vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */,vm.$options._parentElm,vm.$options._refElm);}// patch方法内部通过调用createElm 来生成nodecreateElm(vnode,insertedVnodeQueue,// extremely rare edge case: do not insert if old element is in a// leaving transition. Only happens when combining transition +// keep-alive + HOCs. (#4590)oldElm._leaveCb ? null : parentElm$1,nodeOps.nextSibling(oldElm));// createElm 中主要通过下面这个方法来创建domvnode.elm=vnode.ns
? nodeOps.createElementNS(vnode.ns,tag)
: nodeOps.createElement(tag,vnode)//nodeOps是什么,是一些创建dom相关的方法import{namespaceMap}from'web/util/index'exportfunctioncreateElement(tagName: string,vnode: VNode): Element{constelm=document.createElement(tagName)if(tagName!=='select'){returnelm}// false or null will remove the attribute but undefined will notif(vnode.data&&vnode.data.attrs&&vnode.data.attrs.multiple!==undefined){elm.setAttribute('multiple','multiple')}returnelm}exportfunctioncreateElementNS(namespace: string,tagName: string): Element{returndocument.createElementNS(namespaceMap[namespace],tagName)}
...
set: functionreactiveSetter(newVal){varvalue=getter ? getter.call(obj) : val;/* eslint-disable no-self-compare */if(newVal===value||(newVal!==newVal&&value!==value)){return}/* eslint-enable no-self-compare */if("development"!=='production'&&customSetter){customSetter();}if(setter){setter.call(obj,newVal);}else{val=newVal;}childOb=!shallow&&observe(newVal);// 进入dep.notify 通知watcherdep.notify();}Dep.prototype.notify=functionnotify(){// stabilize the subscriber list firstvarsubs=this.subs.slice();for(vari=0,l=subs.length;i<l;i++){subs[i].update();}};Watcher.prototype.update=functionupdate(){/* istanbul ignore else */if(this.lazy){this.dirty=true;}elseif(this.sync){this.run();}else{// 进入这里 这个方法最终会进入watcher.prototype.run 方法 queueWatcher(this);}};Watcher.prototype.run=functionrun(){if(this.active){// 这里会调用this.get, 之前有讲过this.get里面会调用updateComponent,所以又会走到// vm._update(vm._render(), hydrating);这个方法重新更新视图了varvalue=this.get();if(value!==this.value||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value)||this.deep){// set new valuevaroldValue=this.value;this.value=value;if(this.user){try{this.cb.call(this.vm,value,oldValue);}catch(e){handleError(e,this.vm,("callback for watcher \""+(this.expression)+"\""));}}else{this.cb.call(this.vm,value,oldValue);}}}};Watcher.prototype.get=functionget(){// 这里有个关键点pushTarget(this);varvalue;varvm=this.vm;try{// 注意this.getter = expFunction value=this.getter.call(vm,vm);}catch(e){if(this.user){handleError(e,vm,("getter for watcher \""+(this.expression)+"\""));}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value);}popTarget();this.cleanupDeps();}returnvalue};
Vue2中Dep, Observer 与Watcher 之间的关系(不含patch部分)
最简单的理解
为什么要引入依赖收集
text3在实际模板中并没有被用到,然而当text3的数据被修改的时候(this.text3 = 'test')的时候,同样会触发text3的setter,按照原先想法,这会导致重新执行渲染,这显然不正确。
当globalData.a 发生变化或者当globalData.b发生变化时,我们的视图都需要更新,所以我们要收集这个视图依赖于数据a 和 数据b。因此我们需要依赖收集。
前置知识
vue官网在线模板编译
https://cn.vuejs.org/v2/guide/render-function.html#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91
ASTNode 类型(Abstract Syntax Tree)
render 函数一些函数定义
VNode 结构
真实DOM有什么问题,为什么要去使用虚拟DOM
每个DOM上的属性多达 228 个,而这些属性有 90% 多对我们来说都是无用的。VNode 就是简化版的真实 DOM 元素,保留了我们要的属性,并新增了一些在 diff 过程中需要使用的属性,例如 isStatic。
【总结】Virtual DOM 就是一个js对象,用它来更轻量地描述DOM
入口文件查找
然后--> /src/core/index.js --> /src/core/instance/index.js
最终在instance/index.js里面找到Vue的构造函数
设置情景分析
假设我们的实际场景为下面的脚本, 我们下面来分析一下
_init 入口函数
阶段一 initState()
initState方法里面有调用initData,在initData方法里面最后调用了observe(data, true)。那我们来看下observe 方法
阶段二 进入$mount() 这个方法里面比较重要
initState() 阶段完成以后,后面会执行到mount() 方法,这个方法比较关键我们一起来看下。
我们先来看一下$mount方法,我们发现一开始有一段赋值,其实就是先存下来Vue上的公共mount方法,然后又重写了公共的mount方法。
compileToFunctions方法做了什么事情呢,我先大体的介绍一下。它的最终目的是让template字符串模板——>render function 函数。compile这个编译过程在Vue2会经历3个阶段:
compile(template, options);会进入baseCompile方法, 我们来看下baseCompile方法里面的细节
阶段二(一) 生成ast
大致理解一下html-parser吧, html-parser 会按照下面几步进行html的解析
2.对ast进行预处理(preTransforms)
对ast的预处理在weex中才会有,我们直接跳过。
3、 解析v-pre、v-if、v-for、v-once、slot、key、ref等指令。
4、 对ast的class 和 style中的属性进行处理
5、 解析v-bind、v-on以及普通属性
6、 根节点或v-else块等处理
7、 模板元素父子关系的建立
8、 对ast后处理(postTransforms)
最终生成的ast长下面这样:
阶段二 optimize() 静态结点标记
源码位置: src/compiler/optimizer.js
看一下isStatic方法
然后我们的ast 会变成下面这样,标记了是否为静态结点和是否为静态根结点, 增加了一个static属性
阶段二(三) generate() 生成render function
源码位置:src/compiler/codegen/index.js
拿到ast结构以后,进入generate函数 var code = generate(ast, options);
重点看下genElement 方法
最后生成的就是下面这个对象
之后调用了 createFunction 方法其实就是new Function('string')。所以其实最终我们得到的是 render function
然后compile结束后,我们得到render function 之后,开始执行$mount的公用方法(源码位置src/platforms/web/runtime/index.js) 其实就是调用了mountComponent方法
阶段四 执行renderFunction 得到VNode
阶段五 进入Watcher 类
watcher 构造函数最后调用了this.get() , 首先调用了pushTarget(this)方法。这个方法把Dep.target设为this(即当前watcher实例)。然后执行了this.getter.call(vm, vm);这个this.getter 就是 this.getter = expOrFn; 即我们传入new Watcher 里面的第二个参数,updateComponent。updateComponent做了什么呢?它先执行了vm.render,然后执行了 vm. update
阶段六 调用render()生成VDom
下面进入了updateComponent方法,会先执行vm._render(), 我们来看下vm._render()调用后得到了什么。
vnode = render.call(vm._renderProxy, vm.$createElement);这个方法的调用,相当于执行了我们之前得到的render function。并且我们知道render function 里面有with(this) 此时我们把this指向了vm, 所以按照我们的例子, s(a+b) 在执行的时候会读取到vm.a 和 vm.b 就分别进入了a 和 b 的getter。再回头看下getter函数。
所以注意这里就是Watcher 与 Dep 关联的地方,可以认为compile 是Watcher 与 Dep之间的桥梁。
vm.render()方法最终执行完会生成VNode, 即完成了从render function —> VNode 的过程,我们看下此时vnode的样子, 大概像下面这样
得到VNode 之后,调用vm.update 方法从VNode 生成DOM。update方法内部重点调用了patch方法,看下面。因为patch内容也比较复杂,所以此次并不讲解内部相关的具体流程,会大致看下dom创建的过程。patch方法内部大致会涉及下面3部分的处理:
最后我们的结点生成并挂载了vnode.elm 上,打出来看下
接下来就会触发 insert (parent, elm, ref$$1) parent 是body, elm是上面得到的dom, 第三个参数没研究,此时elm就插入到了parent中。接下来又会回到mountComponent 接下去的方法
当data更新
当我们data的值发生了变化的时候,会进入setter函数。
所以我们此时就理解了整个Observer,Dep 与Watcher之间的关系
参考文献
1.Vue2.0 源码阅读:模板渲染
2.compile—优化静态内容
3.深入vue2.0底层思想——模板渲染
4.Vue2 源码漫游(二)
5.Vitual DOM 的内部工作原理
The text was updated successfully, but these errors were encountered: