Vue.js源码的背后.docx_第1页
Vue.js源码的背后.docx_第2页
Vue.js源码的背后.docx_第3页
Vue.js源码的背后.docx_第4页
Vue.js源码的背后.docx_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

下面的代码会在页面上输出Hello World,但是在这个new Vue()到页面渲染之间,到底发生了什么。这篇文章希望通过最简单的例子,去了解Vue源码过程。这里分析的源码版本是Vue.version = 1.0.20messagevar vm = new Vue( el: #mountNode, data: function () return message: Hello World ; );这篇文章将要解决几个问题:1. new Vue()的过程中,内部到底有哪些步骤2. 如何收集依赖3. 如何计算表达式4. 如何表达式的值如何反应在DOM上的简单来说过程是这样的:1. observe: 把message: Hello World变成是reactive的2. compile: compileTextNode message,解析出指令(directive = v-text)和表达式(expression = message),创建fragment(new TextNode)准备替换3. link:实例化directive,将创建的fragment和directive链接起来,将fragment替换在DOM上4. bind: 通过directive对应的watcher获取依赖(message)的值(Hello World),v-text去update值到fragment上详细过程,接着往下看。构造函数文件路径:src/instance/vue.jsfunction Vue (options) this._init(options)初始化这里只拿对例子理解最关键的步骤分析。文件路径:src/instance/internal/init.jsVtotype._init = function (options) . / merge options. options = this.$options = mergeOptions( this.constructor.options, options, this ) . / initialize data observation and scope inheritance. this._initState() . / if el option is passed, start compilation. if (options.el) this.$mount(options.el) merge optionsmergeOptions()定义在src/util/options.js文件中,这里主要定义options中各种属性的合并(merge),例如:props, methods, computed, watch等。另外,这里还定义了每种属性merge的默认算法(strategy),这些strategy都可以配置的,参考Custom Option Merge Strategy在本文的例子中,主要是data选项的merge,在merge之后,放到$options.data中,基本相当于下面这样:vm.$options.data = function mergedInstanceDataFn () var parentVal = undefined / 这里就是在我们定义的options中的data var childVal = function () return message: Hello World / data function绑定vm实例后执行,执行结果: message: Hello World var instanceData = childVal.call(vm) / 对象之间的merge,类似$.extend,结果肯定就是:message: Hello World return mergeData(instanceData, parentVal)init data_initData()发生在_initState()中,主要做了两件事:1. 代理data中的属性2. observe data文件路径:src/instance/internal/state.jsVtotype._initState = function () this._initProps() this._initMeta() this._initMethods() this._initData() / 这里 this._initComputed() 属性代理(proxy)把data的结果赋值给内部属性:文件路径:src/instance/internal/state.jsvar dataFn = this.$options.data / 上面我们得到的mergedInstanceDataFn函数var data = this._data = dataFn ? dataFn() : 代理(proxy)data中的属性到_data,使得vm.message = vm._data.message:文件路径:src/instance/internal/state.js/* * Proxy a property, so that * p = vm._p */Vtotype._proxy = function (key) if (!isReserved(key) var self = this Object.defineProperty(self, key, configurable: true, enumerable: true, get: function proxyGetter () return self._datakey , set: function proxySetter (val) self._datakey = val ) observe这里是我们的第一个重点,observe过程。在_initData()最后,调用了observe(data, this)对数据进行observe。在hello world例子里,observe()函数主要是针对message: Hello World创建了Observer对象。文件路径:src/observer/index.jsvar ob = new Observer(value) / value = data = message:Hello World在observe()函数中还做了些能否observe的条件判断,这些条件有:_ob_toString.call(ob) = object Objectobj._isVue != trueObject.isExtensible(obj) = trueObserver官网的Reactivity in Depth上有这么句话:When you pass a plain JavaScript object to a Vue instance as its data option, Vue.js will walk through all of its properties and convert them to getter/settersThe getter/setters are invisible to the user, but under the hood they enable Vue.js to perform dependency-tracking and change-notification when properties are accessed or modifiedObserver就是干这个事情的,使data变成“发布者”,watcher是订阅者,订阅data的变化。在例子中,创建observer的过程是:1. new Observer(message: Hello World)2. 实例化一个Dep对象,用来收集依赖3. walk(Ototype.walk())数据的每一个属性,这里只有message4. 将属性变成reactive的(Otoype.convert()convert()里调用了defineReactive(),给data的message属性添加reactiveGetter和reactiveSetter文件路径:src/observer/index.jsexport function defineReactive (obj, key, value) . Object.defineProperty(obj, key, enumerable: true, configurable: true, get: function reactiveGetter () . if (Dep.target) dep.depend() / 这里是收集依赖 . return value , set: function reactiveSetter (newVal) . if (setter) setter.call(obj, newVal) else val = newVal . dep.notify() / 这里是notify观察这个数据的依赖(watcher) )关于依赖收集和notify,主要是Dep类文件路径:src/observer/dep.jsexport default function Dep () this.id = uid+ this.subs = 这里的subs是保存着订阅者(即watcher)的数组,当被观察数据发生变化时,即被调用setter,那么dep.notify()就循环这里的订阅者,分别调用他们的update方法。但是在getter收集依赖的代码里,并没有看到watcher被添加到subs中,什么时候添加进去的呢?这个问题在讲到Watcher的时候再回答。mount node按照生命周期图上,observe data和一些init之后,就是$mount了,最主要的就是_compile。文件路径:src/instance/api/lifecycle.jsVtotype.$mount = function (el) . this._compile(el) . _compile里分两步:compile和linkcompilecompile过程是分析给定元素(el)或者模版(template),提取指令(directive)和创建对应离线的DOM元素(document fragment)。文件路径:src/instance/internal/lifecycle.jsVtotype._compile = function (el) . var rootLinker = compileRoot(el, options, contextOptions) . var rootUnlinkFn = rootLinker(this, el, this._scope) . var contentUnlinkFn = compile(el, options)(this, el) .例子中compile #mountNode元素,大致过程如下:1. compileRoot:由于root node()本身没有任何指令,所以这里compile不出什么东西2. compileChildNode:mountNode的子node,即内容为message的TextNode3. compileTextNode:3.1 parseText:其实就是tokenization(标记化:从字符串中提取符号,语句等有意义的元素),得到的结果是tokens3.2 processTextToken:从tokens中分析出指令类型,表达式和过滤器,并创建新的空的TextNode3.3 创建fragment,将新的TextNode append进去parseText的时候,通过正则表达式(/(.+?)|(.+?)/g)匹配字符串message,得出的token包含这些信息:“这是个tag,而且是文本(text)而非HTML的tag,不是一次性的插值(one-time interpolation),tag的内容是message”。这里用来做匹配的正则表达式是会根据delimiters和unsafeDelimiters的配置动态生成的。processTextToken之后,其实就得到了创建指令需要的所有信息:指令类型v-text,表达式message,过滤器无,并且该指令负责跟进的DOM是新创建的TextNode。接下来就是实例化指令了。link每个compile函数之后都会返回一个link function(linkFn)。linkFn就是去实例化指令,将指令和新建的元素link在一起,然后将元素替换到DOM tree中去。每个linkFn函数都会返回一个unlink function(unlinkFn)。unlinkFn是在vm销毁的时候用的,这里不介绍。实例化directive:new Directive(description, vm, el)description是compile结果token中保存的信息,内容如下:description = name: text, / text指令 expression: message, filters: undefined, def: vTextDefinitiondef属性上的是text指令的定义(definition),和Custome Directive一样,text指令也有bind和update方法,其定义如下:文件路径:src/directives/public/text.jsexport default bind () this.attr = this.el.nodeType = 3 ? data : textContent , update (value) this.elthis.attr = _toString(value) new Directive()构造函数里面只是一些内部属性的赋值,真正的绑定过程还需要调用Dtotype._bind,它是在Vue实例方法_bindDir()中被调用的。在_bind里面,会创建watcher,并第一次通过watcher去获得表达式message的计算值,更新到之前新建的TextNode中去,完成在页面上渲染Hello World。watcherFor every directive / data binding in the template, there will be a corresponding watcher object, which records any properties “touched” during its evaluation as dependencies. Later on when a dependencys setter is called, it triggers the watcher to re-evaluate, and in turn causes its associated directive to perform DOM updates.每个与数据绑定的directive都有一个watcher,帮它监听表达式的值,如果发生变化,则通知它update自己负责的DOM。一直说的dependency collection就在这里发生。Dtotype._bind()里面,会new Watcher(expression, update),把表达式和directive的update方法传进去。Watcher会去parseExpression:文件路径:src/parsers/expression.jsexport function parseExpression (exp, needSet) exp = exp.trim() / try cache var hit = expressionCache.get(exp) if (hit) if (needSet & !hit.set) hit.set = compileSetter(hit.exp) return hit var res = exp: exp res.ge

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论