在Vue中咱们常常修改数据,而后视图就直接修改了,那么这些到底是怎么实现的呢?
其实Vue使用了E5的语法Object.defineProperty来实现的数据驱动。
那么Object.defineProperty到底是怎么实现的呢?
咱们先来看一下一个简单的demohtml
<template> <div class="hello"> {{test}} </div> </template> <script> export default { data () { test: '123' }, created () { console.log(this.test === this._data.test) // true } } </script>
在vue这段小代码中,this.test === this._data.test实际上是等价的。这是为何呢
其实就是经过Object.defineProperty作的一个小代理实现的。
原理以下:vue
var obj = { _data: { x: 123, y: 467 } } function proxy (target, sourceKey, key) { Object.defineProperty(target, key, { enumerable: true, configurable: true, get: function() { return this[sourceKey][key] }, set: function(val) { this[sourceKey][key] = val } }) } proxy(obj, '_data', 'x') proxy(obj, '_data', 'y') console.log(obj.x) // 123 console.log(obj.y) // 467 console.log(obj._data.x) // 123 console.log(obj._data.y) // 467
以上一个demo就是对obj对象的一个代理,经过访问obj.x直接代理到obj._data.x。 Object.defineProperty具体用法能够自行搜索。
那么其实数据双向绑定也是根据Object.defineProperty里面的get和set来实现的,经过set的时候去作一些视图更新的操做。react
接下来咱们就来看一下Vue源码吧。
双向数据绑定,将分为如下3个部分:jquery
1. Observer。这个模块是用于监听对象上的全部属性,即便用Object.defineProperty来实现get和set 2. Watcher。 这个模块是观察者,当监听的数据值被修改时,执行对象的回调函数。 3. Dep。 链接Observe和Watcher的桥梁,每一个Observer对应一个Dep,内部维护一个数组,保存与该Observer相关的Watcher
首先来看下oberser。
路径: src/core/observer/index.jsexpress
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 添加__ob__来标示value有对应的Observer def(value, '__ob__', this) // 对数组的处理 if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(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]) } } }
因此从源码能够看出数据类型主要分为2种,一种是数组,一种是对象。对应的处理方法分别树observe和defineReactive
那咱们再来看看这2个函数作了些什么呢?
这2个函数也是在这个文件内部数组
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 const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) // 这边做出get和set的动态响应的处理 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
// 这个方法具体是为对象的key添加get,set方法;若是用户传入本身自己传入get和set方法也会保留其方法。它会为每个值都建立一个Dep,在get函数中 dep.depend作了2件事,一是向Dep.target的内部添加dep,二是将dep。target添加到dep内部的subs数组中,也就是创建关系。
在set函数中,若是新值和旧值相同则不处理,若是不一样,则通知更新。
接下来看observeapp
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) { 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 }
对数组中的每一项进行检测,该方法用于观察一个对象,若是不是对象则直接返回,若是是对象则返回该对象Observer对象。
可是这样牢牢对数组中的每一项的对象进行了观察,若是数组自己的长度修改那么又如何触发呢。Vue专门对数组作了特出的处理。
回过头来看Observer的类中有这么一段,在处理数组的时候,处理了这些代码dom
const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value)
咱们来细看这些函数,见以下函数
function protoAugment (target, src: Object, keys: any) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-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]) } } const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original 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 } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
咱们能够看到protoAugment很简单,就是执行了一段value._proto_ = arrayMethods
copyAugment中循环把arrayMethods上的arrayKeys方法添加到value上。
arrayMethods又是重写了数组的操做方法['push','pop','shift','unshift','splice','sort','reverse']。
经过调用数组这些方法的时候,通知dep.notify。 至此Observer部分已经结束oop
Dep相对就简单点。
源码路径:src/core/observer/dep.js
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) } // 调用watcher depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
内部有个惟一的id标识,还有一个保存watcher的数组subs。
let uid = 0 export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm 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 = function () {} } } this.value = this.get() } get () { pushTarget(this) let value const vm = this.vm ... value = this.getter.call(vm, vm) ... popTarget() this.cleanupDeps() return value } ... update () { ... queueWatcher(this) } run () { if (this.active) { const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { // set new value 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) } } } } ... }
Watcher就是用于把变化放入观察,并通知其变化更新。
queueWatcher就是把变化者放入数组queue,而后经过nextTick去更换新数组queue中的变化。
在生命周期挂载元素时,就会经过建立Watcher,而后来更新创新模块。
vm._watcher = new Watcher(vm, updateComponent, noop)
这边数据双向绑定差很少就结束了。最后再附上一张简要的流程图来进一步清晰本身的思路。
下一章节经过数据绑定原理结合jquery来实现数据驱动更新的demo。之因此采用jquery操做dom是由于如今Vue源码还没到解析html模板那一步。
因此一步步来。等以后学完模板解析后。再去制做一个MVVM的简易demo。
若是对您有帮助,请点个赞,谢谢!