有一段时间没有更新技术博文了,由于这段时间埋下头来看Vue源码了。本文咱们一块儿经过学习双向绑定原理来分析Vue源码。预计接下来会围绕Vue源码来整理一些文章,以下。javascript
这些文章统一放在个人git仓库:https://github.com/yzsunlei/javascript-series-code-analyzing。以为有用记得star收藏。html
咱们先来看一个简单的应用示例:vue
<div id="app"> <input id="input" type="text" v-model="text"> <div id="text">输入的值为:{{text}}</div> </div> <script> var vm = new Vue({ el: '#app', data: { text: 'hello world' } }) </script>
上面的示例具备的功能就是初始时,'hello world'字符串会显示在input输入框中和div文本中,当手动输入值后,div文本的值也相应的改变。java
咱们来简单理一下实现思路:react
Vue.js是经过数据劫持以及结合发布者-订阅者来实现双向绑定的,数据劫持是利用ES5的Object.defineProperty(obj, key, val)来劫持各个属性的的setter以及getter,在数据变更时发布消息给订阅者,从而触发相应的回调来更新视图。git
双向数据绑定,简单点来讲分为三个部分:github
下面咱们来一步步的实现双向数据绑定。算法
function Observer(obj, key, value) { var dep = new Dep(); if (Object.prototype.toString.call(value) == '[object Object]') { Object.keys(value).forEach(function(key) { new Observer(value, key, value[key]) }) }; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) { dep.addSub(Dep.target); }; return value; }, set: function(newVal) { value = newVal; dep.notify(); } }) }
递归的为对象obj的每一个属性添加getter和setter。在getter中,咱们把watcher添加到dep中。在setter中,触发watcher执行回调。express
function Watcher(fn) { this.update = function() { Dep.target = this; fn(); Dep.target = null; } this.update(); }
fn是数据变化后要执行的回调函数,通常是获取数据渲染模板。默认执行一遍update方法是为了在渲染模板过程当中,调用数据对象的getter时创建二者之间的关系。由于同一时刻只有一个watcher处于激活状态,把当前watcher绑定在Dep.target(方便在Observer内获取)。回调结束后,销毁Dep.target。segmentfault
function Dep() { this.subs = []; this.addSub = function (watcher) { this.subs.push(watcher); } this.notify = function() { this.subs.forEach(function(watcher) { watcher.update(); }); } }
内部一个存放watcher的数组subs。addSub用于向数组中添加watcher(getter时)。notify用于触发watcher的更新(setter时)。
以上咱们就完成了简易的双向绑定的功能,咱们用一下看是否是能达到上面简单应用一样的效果。
<div id="app"> <input id="input" type="text" v-model="text"> <div id="text">输入的值为:{{text}}</div> </div> <script type="text/javascript"> var obj = { text: 'hello world' } Object.keys(obj).forEach(function(key){ new Observer(obj, key, obj[key]) }); new Watcher(function(){ document.querySelector("#text").innerHTML = "输入的值为:" + obj.text; }) document.querySelector("#input").addEventListener('input', function(e) { obj.text = e.target.value; }) </script>
固然上面这是最简单的双向绑定功能,Vue中还实现了对数组、对象的双向绑定,下面咱们来看看Vue中的实现。
看Vue的实现源码前,咱们先来看下下面这张图,经典的Vue双向绑定原理示意图(图片来自于网络):
简单解析以下:
首先是Observer对象,源码位置src/core/observer/index.js
export class Observer { value: any; dep: Dep; vmCount: number; constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 添加__ob__来标示value有对应的Observer def(value, '__ob__', this) if (Array.isArray(value)) { // 处理数组 if (hasProto) { // 实现是'__proto__' in {} protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { // 处理对象 this.walk(value) } } // 给对象每一个属性添加getter/setters walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) // 重点 } } // 循环观察数组的每一项 observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) // 重点 } } }
总体上,value分为对象或数组两种状况来处理。这里咱们先来看看defineReactive和observe这两个比较重要的函数。
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) // 带有不可配置的属性直接跳过 if (property && property.configurable === false) { return } // cater for pre-defined getter/setters // 保存对象属性上自有的getter和setter const getter = property && property.get const setter = property && property.set // 若是属性上以前没有定义getter,而且没有传入初始val值,就把属性原有的值赋值给val if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // 给属性设置getter const value = getter ? getter.call(obj) : val if (Dep.target) { // 给每一个属性建立一个dep dep.depend() if (childOb) { childOb.dep.depend() // 若是是数组,就递归建立 if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { // 给属性设置setter const value = getter ? getter.call(obj) : val // 值未变化,就跳过 if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() // 非生产环境自定义调试用,这里忽略 } if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 值发生变化进行通知 dep.notify() } }) }
defineReactive这个方法里面,是具体的为对象的属性添加getter、setter的地方。它会为每一个值建立一个dep,若是用户为这个值传入getter和setter,则暂时保存。以后经过Object.defineProperty,从新添加装饰器。在getter中,dep.depend其实作了两件事,一是向Dep.target内部的deps添加dep,二是将Dep.target添加到dep内部的subs,也就是创建它们之间的联系。在setter中,若是新旧值相同,直接返回,不一样则调用dep.notify来更新与之相关的watcher。
export function observe (value: any, asRootData: ?boolean): Observer | void { // 若是不是对象就跳过 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 若是已有observer,就直接返回,上面讲到过会用`__ob__`属性来记录 ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 若是没有,就建立一个 ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
observe
这个方法用于观察一个对象,返回与对象相关的Observer对象,若是没有则为value建立一个对应的Observer。
好的,咱们再回到Observer,若是传入的是对象,咱们就调用walk,该方法就是遍历对象,对每一个值执行defineReactive。
对于传入的对象是数组的状况,其实会有一些特殊的处理,由于数组自己只引用了一个地址,因此对数组进行push、splice、sort等操做,咱们是没法监听的。因此,Vue中改写value的__proto__(若是有),或在value上从新定义这些方法。augment在环境支持__proto__时是protoAugment,不支持时是copyAugment。
// augment在环境支持__proto__时 function protoAugment (target, src: Object) { target.__proto__ = src } // augment在环境不支持__proto__时 function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
augment
在环境支持__proto__
时,就很简单,调用protoAugment
其实就是执行了value.__proto__ = arrayMethods
。augment
在环境支持__proto__
时,调用copyAugment
中循环把arrayMethods
上的arrayKeys
方法添加到value
上。
那这里咱们就要看看arrayMethods
方法了。arrayMethods
实际上是改写了数组方法的新对象。arrayKeys
是arrayMethods
中的方法列表。
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // 是push、unshift、splice时,从新观察数组,由于这三个方法都是像数组中添加新的元素 if (inserted) ob.observeArray(inserted) // 通知变化 ob.dep.notify() return result }) })
实际上仍是调用数组相应的方法来操做value,只不过操做以后,添加了相关watcher的更新。调用push
、unshift
、splice
三个方法参数大于2时,要从新调用ob.observeArray,由于这三种状况都是像数组中添加新的元素,因此须要从新观察每一个子元素。最后在通知变化。
Vue中的Observer就讲到这里了。实际上还有两个函数set
、del
没有讲解,其实就是在添加或删除数组元素、对象属性时进行getter、setter的绑定以及通知变化,具体能够去看源码。
看完Vue中的Observer,而后咱们来看看Vue中Dep,源码位置:src/core/observer/dep.js
。
let uid = 0 export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } // 添加订阅者 addSub (sub: Watcher) { this.subs.push(sub) } // 移除订阅者 removeSub (sub: Watcher) { remove(this.subs, sub) } // 添加到订阅管理器 depend () { if (Dep.target) { Dep.target.addDep(this) } } // 通知变化 notify () { const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { subs.sort((a, b) => a.id - b.id) } // 遍历全部的订阅者,通知更新 for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
Dep类就比较简单,内部有一个id和一个subs,id用于做为dep对象的惟一标识,subs就是保存watcher的数组。相比于上面咱们本身实现的demo应用,这里多了removeSub和depend。removeSub是从数组中移除某个watcher,depend是调用了watcher的addDep。
好,Vue中的Dep只能说这么多了。
最后咱们再来看看Vue中的Watcher,源码位置:src/core/observer/watcher.js
。
// 注,我删除了源码中一些不过重要或与双向绑定关系不太大的逻辑,删除的代码用// ... 表示 let uid = 0 export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // ... this.cb = cb this.id = ++uid // ... this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } get () { pushTarget(this) let value const vm = this.vm // ... if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value } addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } cleanupDeps () { // ... } update () { // 更新三种模式吧,lazy延迟更新,sync同步更新直接执行,默认异步更新添加处处理队列 if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } run () { // 触发更新,在这里调用cb函数 if (this.active) { const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { const oldValue = 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) } } } } evaluate () { // ... } depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } teardown () { // ... } }
建立Watcher对象时,有两个比较重要的参数,一个是expOrFn,一个是cb。
在Watcher建立时,会调用this.get,里面会执行根据expOrFn解析出来的getter。在这个getter中,咱们或渲染页面,或获取某个数据的值。总之,会调用相关data的getter,来创建数据的双向绑定。
当相关的数据改变时,会调用watcher的update方法,进而调用run方法。咱们看到,run中还会调用this.get来获取修改以后的value值。
其实Watcher有两种主要用途:一种是更新模板,另外一种就是监听某个值的变化。
模板更新的状况:在Vue声明周期挂载元素时,咱们是经过建立Watcher对象,而后调用updateComponent来更新渲染模板的。
vm._watcher = new Watcher(vm, updateComponent, noop)
在建立Watcher会调用this.get,也就是这里的updateComponent。在render的过程当中,会调用data的getter方法,以此来创建数据的双向绑定,当数据改变时,会从新触发updateComponent。
数据监听的状况:另外一个用途就是咱们的computed、watch等,即监听数据的变化来执行响应的操做。此时this.get返回的是要监听数据的值。初始化过程当中,调用this.get会拿到初始值保存为this.value,监听的数据改变后,会再次调用this.get并拿到修改以后的值,将旧值和新值传给cb并执行响应的回调。
好,Vue中的Watcher就说这么多了。其实上面注释的代码中还有cleanupDeps
清除依赖逻辑、teardown
销毁Watcher逻辑等,留给你们本身去看源码吧。
Vue中双向绑定,简单来讲就是Observer、Watcher、Dep三部分。下面咱们再梳理一下整个过程:
首先咱们为每一个vue属性用Object.defineProperty()实现数据劫持,为每一个属性分配一个订阅者集合的管理数组dep;
而后在编译的时候在该属性的数组dep中添加订阅者,Vue中的v-model会添加一个订阅者,{{}}也会,v-bind也会;
最后修改值就会为该属性赋值,触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。