您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 质量控制/管理 > Vuejs源码的背后
下面的代码会在页面上输出HelloWorld,但是在这个newVue()到页面渲染之间,到底发生了什么。这篇文章希望通过最简单的例子,去了解Vue源码过程。这里分析的源码版本是Vue.version='1.0.20'divid=mountNode{{message}}/divvarvm=newVue({el:'#mountNode',data:function(){return{message:'HelloWorld'};}});这篇文章将要解决几个问题:1.newVue()的过程中,内部到底有哪些步骤2.如何收集依赖3.如何计算表达式4.如何表达式的值如何反应在DOM上的简单来说过程是这样的:1.observe:把{message:'HelloWorld'}变成是reactive的2.compile:compileTextNode{{message}},解析出指令(directive=v-text)和表达式(expression=message),创建fragment(newTextNode)准备替换3.link:实例化directive,将创建的fragment和directive链接起来,将fragment替换在DOM上4.bind:通过directive对应的watcher获取依赖(message)的值(HelloWorld),v-text去update值到fragment上详细过程,接着往下看。构造函数文件路径:src/instance/vue.jsfunctionVue(options){this._init(options)}初始化这里只拿对例子理解最关键的步骤分析。文件路径:src/instance/internal/init.jsVue.prototype._init=function(options){...//mergeoptions.options=this.$options=mergeOptions(this.constructor.options,options,this)...//initializedataobservationandscopeinheritance.this._initState()...//if`el`optionispassed,startcompilation.if(options.el){this.$mount(options.el)}}mergeoptionsmergeOptions()定义在src/util/options.js文件中,这里主要定义options中各种属性的合并(merge),例如:props,methods,computed,watch等。另外,这里还定义了每种属性merge的默认算法(strategy),这些strategy都可以配置的,参考CustomOptionMergeStrategy在本文的例子中,主要是data选项的merge,在merge之后,放到$options.data中,基本相当于下面这样:vm.$options.data=functionmergedInstanceDataFn(){varparentVal=undefined//这里就是在我们定义的options中的datavarchildVal=function(){return{message:'HelloWorld'}}//datafunction绑定vm实例后执行,执行结果:{message:'HelloWorld'}varinstanceData=childVal.call(vm)//对象之间的merge,类似$.extend,结果肯定就是:{message:'HelloWorld'}returnmergeData(instanceData,parentVal)}initdata_initData()发生在_initState()中,主要做了两件事:1.代理data中的属性2.observedata文件路径:src/instance/internal/state.jsVue.prototype._initState=function(){this._initProps()this._initMeta()this._initMethods()this._initData()//这里this._initComputed()}属性代理(proxy)把data的结果赋值给内部属性:文件路径:src/instance/internal/state.jsvardataFn=this.$options.data//上面我们得到的mergedInstanceDataFn函数vardata=this._data=dataFn?dataFn():{}代理(proxy)data中的属性到_data,使得vm.message===vm._data.message:文件路径:src/instance/internal/state.js/***Proxyaproperty,sothat*vm.prop===vm._data.prop*/Vue.prototype._proxy=function(key){if(!isReserved(key)){varself=thisObject.defineProperty(self,key,{configurable:true,enumerable:true,get:functionproxyGetter(){returnself._data[key]},set:functionproxySetter(val){self._data[key]=val}})}}observe这里是我们的第一个重点,observe过程。在_initData()最后,调用了observe(data,this)对数据进行observe。在helloworld例子里,observe()函数主要是针对{message:'HelloWorld'}创建了Observer对象。文件路径:src/observer/index.jsvarob=newObserver(value)//value=data={message:'HelloWorld'}在observe()函数中还做了些能否observe的条件判断,这些条件有:__ob__toString.call(ob)===[objectObject]obj._isVue!==trueObject.isExtensible(obj)===trueObserver官网的ReactivityinDepth上有这么句话:WhenyoupassaplainJavaScriptobjecttoaVueinstanceasitsdataoption,Vue.jswillwalkthroughallofitspropertiesandconvertthemtogetter/settersThegetter/settersareinvisibletotheuser,butunderthehoodtheyenableVue.jstoperformdependency-trackingandchange-notificationwhenpropertiesareaccessedormodifiedObserver就是干这个事情的,使data变成“发布者”,watcher是订阅者,订阅data的变化。在例子中,创建observer的过程是:1.newObserver({message:'HelloWorld'})2.实例化一个Dep对象,用来收集依赖3.walk(Observer.prototype.walk())数据的每一个属性,这里只有message4.将属性变成reactive的(Observer.protoype.convert())convert()里调用了defineReactive(),给data的message属性添加reactiveGetter和reactiveSetter文件路径:src/observer/index.jsexportfunctiondefineReactive(obj,key,value){...Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){...if(Dep.target){dep.depend()//这里是收集依赖...}returnvalue},set:functionreactiveSetter(newVal){...if(setter){setter.call(obj,newVal)}else{val=newVal}...dep.notify()//这里是notify观察这个数据的依赖(watcher)}})}关于依赖收集和notify,主要是Dep类文件路径:src/observer/dep.jsexportdefaultfunctionDep(){this.id=uid++this.subs=[]}这里的subs是保存着订阅者(即watcher)的数组,当被观察数据发生变化时,即被调用setter,那么dep.notify()就循环这里的订阅者,分别调用他们的update方法。但是在getter收集依赖的代码里,并没有看到watcher被添加到subs中,什么时候添加进去的呢?这个问题在讲到Watcher的时候再回答。mountnode按照生命周期图上,observedata和一些init之后,就是$mount了,最主要的就是_compile。文件路径:src/instance/api/lifecycle.jsVue.prototype.$mount=function(el){...this._compile(el)...}_compile里分两步:compile和linkcompilecompile过程是分析给定元素(el)或者模版(template),提取指令(directive)和创建对应离线的DOM元素(documentfragment)。文件路径:src/instance/internal/lifecycle.jsVue.prototype._compile=function(el){...varrootLinker=compileRoot(el,options,contextOptions)...varrootUnlinkFn=rootLinker(this,el,this._scope)...varcontentUnlinkFn=compile(el,options)(this,el)...}例子中compile#mountNode元素,大致过程如下:1.compileRoot:由于rootnode(divid=mountNode/div)本身没有任何指令,所以这里compile不出什么东西2.compileChildNode:mountNode的子node,即内容为{{message}}的TextNode3.compileTextNode:3.1parseText:其实就是tokenization(标记化:从字符串中提取符号,语句等有意义的元素),得到的结果是tokens3.2processTextToken:从tokens中分析出指令类型,表达式和过滤器,并创建新的空的TextNode3.3创建fragment,将新的TextNodeappend进去parseText的时候,通过正则表达式(/\{\{\{(.+?)\}\}\}|\{\{(.+?)\}\}/g)匹配字符串{{message}},得出的token包含这些信息:“这是个tag,而且是文本(text)而非HTML的tag,不是一次性的插值(one-timeinterpolation),tag的内容是message”。这里用来做匹配的正则表达式是会根据delimit
本文标题:Vuejs源码的背后
链接地址:https://www.777doc.com/doc-2866815 .html