其餘章節請看:javascript
vue 快速入門 系列html
本文將 vue 中與數據偵測相關的源碼摘了出來,配合上文(偵測數據的變化 - [基本實現]) 一塊兒來分析一下 vue 是如何實現數據偵測的。vue
Tip: 如下代碼出自 vue.esm.js,版本爲 v2.5.20。無關代碼有一些刪減。中文註釋都是筆者添加。java
/** * Define a property. * 定義屬性的方法 */ function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); } /** * Parse simple path. * 解析簡單路徑。好比 vm.$watch('a.b.c', function(){}) */ var bailRE = /[^\w.$]/; function parsePath (path) { if (bailRE.test(path)) { return } var segments = path.split('.'); return function (obj) { // 例如 a.b.c for (var i = 0; i < segments.length; i++) { if (!obj) { return } // 最後讀取到 c obj = obj[segments[i]]; } return obj } } /** * A dep is an observable that can have multiple * directives subscribing to it. * 依賴。對咱們的依賴列表 dependList 進行了封裝,這裏提取出來了一個類,用於存儲依賴(Watcher)。 */ var Dep = function Dep () { this.id = uid++; // subs 也就是咱們的依賴列表 dependList this.subs = []; }; Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub); }; // 收集依賴 Dep.prototype.depend = function depend () { // Dep.target 也就是咱們的全局變量(globalData),指向 Watcher。 if (Dep.target) { // 收集依賴 Watcher Dep.target.addDep(this); } }; // 通知依賴 Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. // 相似咱們的全局變量(globalData ),用於存儲 Watcher Dep.target = null; var targetStack = []; function pushTarget (target) { targetStack.push(target); Dep.target = target; } function popTarget () { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; } /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ // 接下來是偵測數組的變化 // 也就是經過攔截器來實現數組的偵測 var arrayProto = Array.prototype; // arrayMethods就是攔截器 var arrayMethods = Object.create(arrayProto); // 能改變數組的7個方法 var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; /** * Intercept mutating methods and emit events * 給攔截器(arrayMethods)定義以上7個方法 */ methodsToPatch.forEach(function (method) { // cache original method // 數組的原始方法 var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; // 調用攔截器中的方法,攔截器接着會去調用數組中對應的方法 var result = original.apply(this, args); // 數據變成響應式後,數據上就會掛載 __ob__(Observer 的實例) 屬性,裏面有數據的依賴 var ob = this.__ob__; // 只有 push、unshift、splice 這三個方法能增長數據,而增長的數據也須要轉爲響應式 var 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 }); }); /** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. * 1. 將數據轉爲響應式的主入口。 * 2. 在咱們的實現中是經過 defineReactive() 將數據轉爲響應式,沒有遞歸偵測全部的 key。好比 * data = {a: 1, b: {c:1}},咱們只偵測了數據的第一層(data.a、data.b),孩子節點若是是對象, * 也須要偵測 data.b.c。 * 3. 遞歸偵測調用順序:Observer -> walk -> defineReactive$$1 -> observe -> Observer * 4. 將對象和數組分別處理。 */ var Observer = function Observer (value) { this.value = value; // 定義依賴,用於存儲於數據有關的依賴 // 好比數據 let data = {a: [11,22]},某處使用了 data.a。當執行 data.a.push(33) 時, // data.a 就應該通知其依賴 this.dep = new Dep(); this.vmCount = 0; // 將 this 掛載到數據的 __ob__ 屬性上。Array 的攔截器就能夠經過數據取得 Observer 的 dep,從而通知依賴 def(value, '__ob__', this); if (Array.isArray(value)) { // 若是有原型,就經過更改原型的方式將攔截器掛載到數組上,不然就將攔截器中的方法依次拷貝到數組上 if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } // 數組中的每一項也須要轉爲響應式 this.observeArray(value); } else { // 依次遍歷對象中每一個 key,將其轉爲響應式 this.walk(value); } }; /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { // 經過 Object.defineProperty() 偵測對象 defineReactive$$1(obj, keys[i]); } }; /** * Observe a list of Array items. */ Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ * 經過更改原型來掛載攔截器,實現數組的偵測。 */ function protoAugment (target, src) { // 做用與 setPrototype 相同 target.__proto__ = src; } // 將攔截器中的方法拷貝到數組中,實現數組的偵測。 function copyAugment (target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); } } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. * 觀察數據。若是數據不是對象,直接返回;若是已是響應式,則返回 Observer 的實例;不然將值轉爲響應式 */ function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && // 不能是 vue 實例 !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob } /** * Define a reactive property on an Object. * 偵測數據變化。功能與咱們的 defineReactive() 方法相似。 */ function defineReactive$$1 ( obj, key, val, customSetter, shallow ) { // 每一個 key 都有一個 Dep 用於存儲依賴 // dep 就是咱們的依賴列表 var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } // 值若是是對象,也須要轉爲響應式 var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; // 若是有值 if (Dep.target) { // 收集依賴 dep.depend(); // 若是值(childOb)是對象,childOb也須要收集依賴 if (childOb) { // 可能主要針對數組? childOb.dep.depend(); // 數組中數據,若是須要也得收集依賴,由於裏面的數據若發生變化,應該通知外界 if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var 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 (customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } // 新的值也須要轉爲響應式 childOb = !shallow && observe(newVal); // 通知依賴 dep.notify(); } }); } /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. * Watcher 相對比較複雜,稍微分析一下 */ var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) { // vue 實例 this.vm = vm; if (isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if (options) { this.deep = !!options.deep; this.user = !!options.user; this.lazy = !!options.lazy; this.sync = !!options.sync; this.before = options.before; } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; this.id = ++uid$1; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // parse expression for getter // expOrFn 能夠是函數,也能夠是表達式,例如 a.b.c,統一爲 this.getter if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop; warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } // 經過 get() 方法讀取數據 this.value = this.lazy ? undefined : this.get(); }; /** * Evaluate the getter, and re-collect dependencies. * */ Watcher.prototype.get = function get () { // 會將本身賦值給 Dep.target pushTarget(this); var value; var vm = this.vm; try { // 調用 Watcher 構造函數中分裝的 getter() 方法 // 觸發數據的 getter,從而收集依賴(Watcher) value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } return value }; /** * Add a dependency to this directive. */ Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { dep.addSub(this); } } }; /** * Subscriber interface. * Will be called when a dependency changes. */ Watcher.prototype.update = function update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); } };
其餘章節請看:react
vue 快速入門 系列express