日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

Vue响应式数据: Observer模块实现

發布時間:2024/1/17 vue 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue响应式数据: Observer模块实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

  首先歡迎大家關注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵。接下來的日子我應該會著力寫一系列關于Vue與React內部原理的文章,感興趣的同學點個關注或者Star。
  之前的兩篇文章響應式數據與數據依賴基本原理和從Vue數組響應化所引發的思考我們介紹了響應式數據相關的內容,沒有看的同學可以點擊上面的鏈接了解一下。如果大家都閱讀過上面兩篇文章的話,肯定對這方面內容有了足夠的知識儲備,想來是時候來看看Vue內部是如何實現數據響應化。目前Vue的代碼非常龐大,但其中包含了例如:服務器渲染等我們不關心的內容,為了能集中于我們想學習的部分,我們這次閱讀的是Vue的早期代碼,大家可以checkout到這里查看對應的代碼。
  之前零零碎碎的看過React的部分源碼,當我看到Vue的源碼,覺得真的是非常優秀,各個模塊之間解耦的非常好,可讀性也很高。Vue響應式數據是在Observer模塊中實現的,我們可以看看Observer是如何實現的。
  

發布-訂閱模式  

  如果看過上兩篇文章的同學應該會發現一個問題:數據響應化的代碼與其他的代碼耦合太強了,比如說:
  

//代碼來源于文章:響應式數據與數據依賴基本原理 //定義對象的單個響應式屬性 function defineReactive(obj, key, value){observify(value);Object.defineProperty(obj, key, {configurable: true,enumerable: true,set: function(newValue){var oldValue = value;value = newValue;//可以在修改數據時觸發其他的操作console.log("newValue: ", newValue, " oldValue: ", oldValue);},get: function(){return value;}}); }

  比如上面的代碼,set內部的處理的代碼就與整個數據響應化相耦合,如果下次我們想要在set中做其他的操作,就必須要修改set函數內部的內容,這是非常不友好的,不符合開閉原則(OCP: Open Close Principle)。當然Vue不會采用這種方式去設計,為了解決這個問題,Vue引入了發布-訂閱模式。其實發布-訂閱模式是前端工程師非常熟悉的一種模式,又叫做觀察者模式,它是一種定義對象間一種一對多的依賴關系,當一個對象的狀態發生改變的時候,其他觀察它的對象都會得到通知。我們最常見的DOM事件就是一種發布-訂閱模式。比如:
  

document.body.addEventListener("click", function(){console.log("click event"); });

  在上面的代碼中我們監聽了body的click事件,雖然我們不知道click事件什么時候會發生,但是我們一定能保證,如果發生了body的click事件,我們一定能得到通知,即回調函數被調用。在JavaScript中因為函數是一等公民,我們很少使用傳統的發布-訂閱模式,多采用的是事件模型的方式實現。在Vue中也實現了一個事件模型,我們可以看一下。因為Vue的模塊之間解耦的非常好,因此在看代碼之前,其實我們可以先來看看對應的單元測試文件,你就知道這個模塊要實現什么功能,甚至如果你愿意的話,也可以自己實現一個類似的模塊放進Vue的源碼中運行。

  Vue早期代碼使用是jasmine進行單元測試,emitter_spec.js是事件模型的單元測試文件。首先簡單介紹一下jasmine用到的函數,可以對照下面的代碼了解具體的功能:

  • describe是一個測試單元集合
  • it是一個測試用例
  • beforeEach會在每一個測試用例it執行前執行
  • expect期望函數,用作對期望值和實際值之間執行邏輯比較
  • createSpy用來創建spy,而spy的作用是監測函數的調用相關信息和函數執行參數

  

var Emitter = require('../../../src/emitter') var u = undefined // 代碼有刪減 describe('Emitter', function () {var e, spybeforeEach(function () {e = new Emitter()spy = jasmine.createSpy('emitter')})it('on', function () {e.on('test', spy)e.emit('test', 1, 2 ,3)expect(spy.calls.count()).toBe(1)expect(spy).toHaveBeenCalledWith(1, 2, 3)})it('once', function () {e.once('test', spy)e.emit('test', 1, 2 ,3)e.emit('test', 2, 3, 4)expect(spy.calls.count()).toBe(1)expect(spy).toHaveBeenCalledWith(1, 2, 3)})it('off', function () {e.on('test1', spy)e.on('test2', spy)e.off()e.emit('test1')e.emit('test2')expect(spy.calls.count()).toBe(0)})it('apply emit', function () {e.on('test', spy)e.applyEmit('test', 1)e.applyEmit('test', 1, 2, 3, 4, 5)expect(spy).toHaveBeenCalledWith(1)expect(spy).toHaveBeenCalledWith(1, 2, 3, 4, 5)})})

  可以看出Emitter對象實例對外提供以下接口:

  • on: 注冊監聽接口,參數分別是事件名監聽函數
  • emit: 觸發事件函數,參數是事件名
  • off: 取消對應事件的注冊函數,參數分別是事件名監聽函數
  • once: 與on類似,僅會在第一次時通知監聽函數,隨后監聽函數會被移除。

  看完了上面的單元測試代碼,我們現在已經基本了解了這個模塊要干什么,現在讓我們看看對應的代碼:

// 刪去了注釋并且對代碼順序有調整 // ctx是監聽回調函數的執行作用域(this) function Emitter (ctx) {this._ctx = ctx || this }var p = Emitter.prototypep.on = function (event, fn) {this._cbs = this._cbs || {};(this._cbs[event] || (this._cbs[event] = [])).push(fn)return this } // 三種模式 // 不傳參情況清空所有監聽函數 // 僅傳事件名則清除該事件的所有監聽函數 // 傳遞事件名和回調函數,則對應僅刪除對應的監聽事件 p.off = function (event, fn) {this._cbs = this._cbs || {}// allif (!arguments.length) {this._cbs = {}return this}// specific eventvar callbacks = this._cbs[event]if (!callbacks) return this// remove all handlersif (arguments.length === 1) {delete this._cbs[event]return this}// remove specific handlervar cbfor (var i = 0; i < callbacks.length; i++) {cb = callbacks[i]// 這邊的代碼之所以會有cb.fn === fn要結合once函數去看// 給once傳遞的監聽函數其實已經被wrapped過// 但是仍然可以通過原來的監聽函數去off掉if (cb === fn || cb.fn === fn) {callbacks.splice(i, 1)break}}return this } // 觸發對應事件的所有監聽函數,注意最多只能用給監聽函數傳遞三個參數(采用call) p.emit = function (event, a, b, c) {this._cbs = this._cbs || {}var callbacks = this._cbs[event]if (callbacks) {callbacks = callbacks.slice(0)for (var i = 0, len = callbacks.length; i < len; i++) {callbacks[i].call(this._ctx, a, b, c)}}return this } // 觸發對應事件的所有監聽函數,傳遞參數個數不受限制(采用apply) p.applyEmit = function (event) {this._cbs = this._cbs || {}var callbacks = this._cbs[event], argsif (callbacks) {callbacks = callbacks.slice(0)args = callbacks.slice.call(arguments, 1)for (var i = 0, len = callbacks.length; i < len; i++) {callbacks[i].apply(this._ctx, args)}}return this } // 通過調用on與off事件事件,在第一次觸發之后就`off`對應的監聽事件 p.once = function (event, fn) {var self = thisthis._cbs = this._cbs || {}function on () {self.off(event, on)fn.apply(this, arguments)}on.fn = fnthis.on(event, on)return this }

  我們可以看到上面的代碼采用了原型模式創建了一個Emitter類。配合Karma跑一下這個模塊 ,測試用例全部通過,到現在我們已經閱讀完Emitter了,這算是一個小小的熱身吧,接下來讓我們正式看一下Observer模塊。
  

Observer

對外功能

  按照上面的思路我們先看看Observer對應的測試用例observer_spec.js,由于Observer的測試用例非常長,我會在代碼注釋中做解釋,并盡量精簡測試用例,能讓我們了解模塊對應功能即可,希望你能有耐心閱讀下來。
 

//測試用例是精簡版,否則太冗長 var Observer = require('../../../src/observe/observer') var _ = require('../../../src/util') //Vue內部使用工具方法 var u = undefined Observer.pathDelimiter = '.' //配置Observer路徑分隔符describe('Observer', function () {var spybeforeEach(function () {spy = jasmine.createSpy('observer')}) //我們可以看到我們通過Observer.create函數可以將數據變為可響應化, //然后我們監聽get事件可以在屬性被讀取時觸發對應事件,注意對象嵌套的情況(例如b.c)it('get', function () {Observer.emitGet = truevar obj = {a: 1,b: {c: 2}}var ob = Observer.create(obj)ob.on('get', spy)var t = obj.b.cexpect(spy).toHaveBeenCalledWith('b', u, u)expect(spy).toHaveBeenCalledWith('b.c', u, u)Observer.emitGet = false}) //我們可以監聽響應式數據的set事件,當響應式數據修改的時候,會觸發對應的時間it('set', function () {var obj = {a: 1,b: {c: 2}}var ob = Observer.create(obj)ob.on('set', spy)obj.b.c = 4expect(spy).toHaveBeenCalledWith('b.c', 4, u)}) //帶有$與_開頭的屬性都不會被處理it('ignore prefix', function () {var obj = {_test: 123,$test: 234}var ob = Observer.create(obj)ob.on('set', spy)obj._test = 234obj.$test = 345expect(spy.calls.count()).toBe(0)}) //訪問器屬性也不會被處理it('ignore accessors', function () {var obj = {a: 123,get b () {return this.a}}var ob = Observer.create(obj)obj.a = 234expect(obj.b).toBe(234)}) // 對數屬性的get監聽,注意嵌套的情況it('array get', function () {Observer.emitGet = truevar obj = {arr: [{a:1}, {a:2}]}var ob = Observer.create(obj)ob.on('get', spy)var t = obj.arr[0].aexpect(spy).toHaveBeenCalledWith('arr', u, u)expect(spy).toHaveBeenCalledWith('arr.0.a', u, u)expect(spy.calls.count()).toBe(2)Observer.emitGet = false}) // 對數屬性的get監聽,注意嵌套的情況it('array set', function () {var obj = {arr: [{a:1}, {a:2}]}var ob = Observer.create(obj)ob.on('set', spy)obj.arr[0].a = 2expect(spy).toHaveBeenCalledWith('arr.0.a', 2, u)}) // 我們看到可以通過監聽mutate事件,在push調用的時候對應觸發事件 // 觸發事件第一個參數是"",代表的是路徑名,具體源碼可以看出,對于數組變異方法都是空字符串 // 觸發事件第二個參數是數組本身 // 觸發事件第三個參數比較復雜,其中: // method屬性: 代表觸發的方法名稱 // args屬性: 代表觸發方法傳遞參數 // result屬性: 代表觸發變異方法之后數組的結果 // index屬性: 代表變異方法對數組發生變化的最開始元素 // inserted屬性: 代表數組新增的元素 // remove屬性: 代表數組刪除的元素 // 其他的變異方法: pop、shift、unshift、splice、sort、reverse內容都是非常相似的 // 具體我們就不一一列舉的了,如果有疑問可以自己看到全部的單元測試代碼it('array push', function () {var arr = [{a:1}, {a:2}]var ob = Observer.create(arr)ob.on('mutate', spy)arr.push({a:3})expect(spy.calls.mostRecent().args[0]).toBe('')expect(spy.calls.mostRecent().args[1]).toBe(arr)var mutation = spy.calls.mostRecent().args[2]expect(mutation).toBeDefined()expect(mutation.method).toBe('push')expect(mutation.index).toBe(2)expect(mutation.removed.length).toBe(0)expect(mutation.inserted.length).toBe(1)expect(mutation.inserted[0]).toBe(arr[2])})// 我們可以看到響應式數據中存在$add方法,類似于Vue.set,可以監聽add事件 // 可以向響應式對象中添加新一個屬性,如果之前存在該屬性則操作會被忽略 // 并且新賦值的對象也必須被響應化 // 我們省略了對象數據$delete方法的單元測試,功能類似于Vue.delete,與$add方法相反,可以用于刪除對象的屬性 // 我們省略了數組的$set方法的單元測試,功能也類似與Vue.set,可以用于設置數組對應數字下標的值 // 我們省略了數組的$remove方法的單元測試,功能用于移除數組給定下標的值或者給定的值,例如: // var arr = [{a:1}, {a:2}] // var ob = Observer.create(arr) // arr.$remove(0) => 移除對應下標的值 或者 // arr.$remove(arr[0]) => 移除給定的值it('object.$add', function () {var obj = {a:{b:1}}var ob = Observer.create(obj)ob.on('add', spy)// ignore existing keysobj.$add('a', 123)expect(spy.calls.count()).toBe(0)// add eventvar add = {d:2}obj.a.$add('c', add)expect(spy).toHaveBeenCalledWith('a.c', add, u)// check if add object is properly observedob.on('set', spy)obj.a.c.d = 3expect(spy).toHaveBeenCalledWith('a.c.d', 3, u)})// 下面的測試用例用來表示如果兩個不同對象parentA、parentB的屬性指向同一個對象obj,那么該對象obj改變時會分別parentA與parentB的監聽事件it('shared observe', function () {var obj = { a: 1 }var parentA = { child1: obj }var parentB = { child2: obj }var obA = Observer.create(parentA)var obB = Observer.create(parentB)obA.on('set', spy)obB.on('set', spy)obj.a = 2expect(spy.calls.count()).toBe(2)expect(spy).toHaveBeenCalledWith('child1.a', 2, u)expect(spy).toHaveBeenCalledWith('child2.a', 2, u)// test unobserveparentA.child1 = nullobj.a = 3expect(spy.calls.count()).toBe(4)expect(spy).toHaveBeenCalledWith('child1', null, u)expect(spy).toHaveBeenCalledWith('child2.a', 3, u)})})

源碼實現

數組

  能堅持看到這里,我們的長征路就走過了一半了,我們已經知道了Oberver對外提供的功能了,現在我們就來了解一下Oberver內部的實現原理。
  
  Oberver模塊實際上采用采用組合繼承(借用構造函數+原型繼承)方式繼承了Emitter,其目的就是繼承Emitter的on, off,emit等方法。我們在上面的測試用例發現,我們并沒有用new方法直接創建一個Oberver的對象實例,而是采用一個工廠方法Oberver.create方法來創建的,我們接下來看源碼,由于代碼比較多我會盡量去拆分成一個個小塊來講:
  

// 代碼出自于observe.js // 為了方便講解我對代碼順序做了改變,要了解詳細的情況可以查看具體的源碼var _ = require('../util') var Emitter = require('../emitter') var arrayAugmentations = require('./array-augmentations') var objectAugmentations = require('./object-augmentations')var uid = 0 /*** Type enums*/var ARRAY = 0 var OBJECT = 1function Observer (value, type, options) {Emitter.call(this, options && options.callbackContext)this.id = ++uidthis.value = valuethis.type = typethis.parents = nullif (value) {_.define(value, '$observer', this)if (type === ARRAY) {_.augment(value, arrayAugmentations)this.link(value)} else if (type === OBJECT) {if (options && options.doNotAlterProto) {_.deepMixin(value, objectAugmentations)} else {_.augment(value, objectAugmentations)}this.walk(value)}} }var p = Observer.prototype = Object.create(Emitter.prototype)Observer.pathDelimiter = '\b'Observer.emitGet = falseObserver.create = function (value, options) {if (value &&value.hasOwnProperty('$observer') &&value.$observer instanceof Observer) {return value.$observer} if (_.isArray(value)) {return new Observer(value, ARRAY, options)} else if (_.isObject(value) &&!value.$scope // avoid Vue instance) {return new Observer(value, OBJECT, options)} }

  我們首先從Observer.create看起,如果value值沒有響應化過(通過是否含有$observer屬性去判斷),則使用new操作符創建Obsever實例(區分對象OBJECT與數組ARRAY)。接下來我們看Observer的構造函數是怎么定義的,首先借用Emitter構造函數:
  

Emitter.call(this, options && options.callbackContext)

配合原型繼承

var p = Observer.prototype = Object.create(Emitter.prototype)

從而實現了組合繼承Emitter,因此Observer繼承了Emitter的屬性(ctx)和方法(on,emit等)。我們可以看到Observer有以下屬性:

  • id: 響應式數據的唯一標識
  • value: 原始數據
  • type: 標識是數組還是對象
  • parents: 標識響應式數據的父級,可能存在多個,比如var obj = { a : { b: 1}},在處理{b: 1}的響應化過程中parents中某個屬性指向的就是obj的$observer。

  我們接著看首先給該數據賦值$observer屬性,指向的是實例對象本身。_.define內部是通過defineProperty實現的:

define = function (obj, key, val, enumerable) {Object.defineProperty(obj, key, {value : val,enumerable : !!enumerable,writable : true,configurable : true}) }

  下面我們首先看看是怎么處理數組類型的數據的

if (type === ARRAY) {_.augment(value, arrayAugmentations)this.link(value) }

  如果看過我前兩篇文章的同學,其實還記得我們對數組響應化當時還做了一個著重的原理講解,大概原理就是我們通過給數組對象設置新的原型對象,從而遮蔽掉原生數組的變異方法,大概的原理可以是:
  

function observifyArray(array){var aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];var arrayAugmentations = Object.create(Array.prototype);aryMethods.forEach((method)=> {let original = Array.prototype[method];arrayAugmentations[method] = function () {// 調用對應的原生方法并返回結果// do everything you what do !return original.apply(this, arguments);};});array.__proto__ = arrayAugmentations; }

  回到Vue的源碼,雖然我們知道基本原理肯定是相同的,但是我們仍然需要看看arrayAugmentations是什么?下面arrayAugmentations代碼比較長。我們會在注釋里面解釋基本原理:
  

// 代碼來自于array-augmentations.js var _ = require('../util') var arrayAugmentations = Object.create(Array.prototype) // 這邊操作和我們之前的實現方式非常相似 // 創建arrayAugmentations原型繼承`Array.prototype`從而可以調用數組的原生方法 // 然后通過arrayAugmentations覆蓋數組的變異方法,基本邏輯大致相同 ['push','pop','shift','unshift','splice','sort','reverse'].forEach(function (method) {var original = Array.prototype[method]// 覆蓋arrayAugmentations中的變異方法_.define(arrayAugmentations, method, function () {var args = _.toArray(arguments)// 這里調用了原生的數組變異方法,并獲得結果var result = original.apply(this, args)var ob = this.$observervar inserted, removed, index// 下面switch這一部分代碼看起來很長,其實目的就是針對于不同的變異方法生成:// insert removed inserted 具體的含義對照之前的解釋,了解即可switch (method) {case 'push':inserted = argsindex = this.length - args.lengthbreakcase 'unshift':inserted = argsindex = 0breakcase 'pop':removed = [result]index = this.lengthbreakcase 'shift':removed = [result]index = 0breakcase 'splice':inserted = args.slice(2)removed = resultindex = args[0]break}// 如果給數組中插入新的數據,則需要調用ob.link// link函數其實在上面的_.augment(value, arrayAugmentations)之后也被調用了// 具體的實現我們可以先不管// 我們只要知道其目的就是分別對插入的數據執行響應化if (inserted) ob.link(inserted, index)// 其實從link我們就可以猜出unlink是干什么的// 主要就是對刪除的數據解除響應化,具體實現邏輯后面解釋if (removed) ob.unlink(removed)// updateIndices我們也先不講是怎么實現的,// 目的就是更新子元素在parents的key// 因為push和pop是不會改變現有元素的位置,因此不需要調用// 而諸如splce shift unshift等變異方法會改變對應下標值,因此需要調用if (method !== 'push' && method !== 'pop') {ob.updateIndices()}// 同樣我們先不考慮propagate內部實現,我們只要propagate函數的目的就是// 觸發自身及其遞歸觸發父級的事件// 如果數組中的數據有插入或者刪除,則需要對外觸發"length"被改變if (inserted || removed) {ob.propagate('set', 'length', this.length)}// 對外觸發mutate事件// 可以對照我們之前講的測試用例'array push',就是在這里觸發的,回頭看看吧ob.propagate('mutate', '', this, {method : method,args : args,result : result,index : index,inserted : inserted || [],removed : removed || []})return result}) })// 可以回看一下測試用例 array set,目的就是設置對應下標的值 // 其實就是調用了splice變異方法, 其實我們在Vue中國想要改變某個下標的值的時候 // 官網給出的建議無非是Vue.set或者就是splice,都是相同的原理 // 注意這里的代碼忽略了超出下標范圍的值 _.define(arrayAugmentations, '$set', function (index, val) {if (index >= this.length) {this.length = index + 1}return this.splice(index, 1, val)[0] }) // $remove與$add都是一個道理,都是調用的是`splice`函數 _.define(arrayAugmentations, '$remove', function (index) {if (typeof index !== 'number') {index = this.indexOf(index)}if (index > -1) {return this.splice(index, 1)[0]} })module.exports = arrayAugmentations

  上面的代碼相對比較長,具體的解釋我們在代碼中已經注釋。到這里我們已經了解完arrayAugmentations了,我們接著看看_.augment做了什么。我們在文章從Vue數組響應化所引發的思考中講過Vue是通過__proto__來實現數組響應化的,但是由于__proto__是個非標準屬性,雖然廣泛的瀏覽器廠商基本都實現了這個屬性,但是還是存在部分的安卓版本并不支持該屬性,Vue必須對此做相關的處理,_.augment就負責這個部分:
  

exports.augment = '__proto__' in {}? function (target, proto) {target.__proto__ = proto}: exports.deepMixinexports.deepMixin = function (to, from) {Object.getOwnPropertyNames(from).forEach(function (key) {var desc =Object.getOwnPropertyDescriptor(from, key)Object.defineProperty(to, key, desc)}) }

  我們看到如果瀏覽器不支持__proto__話調用deepMixin函數。而deepMixin的實現也是非常的簡單,就是使用Object.defineProperty將原對象的屬性描述符賦值給目標對象。接著調用了函數:
  

this.link(value)

  關于link函數在上面的備注中我們已經見過了:

if (inserted) ob.link(inserted, index)

  當時我們的解釋是將新插入的數據響應化,知道了功能我們看看代碼的實現:
  

// p === Observer.prototype p.link = function (items, index) {index = index || 0for (var i = 0, l = items.length; i < l; i++) {this.observe(i + index, items[i])} }p.observe = function (key, val) {var ob = Observer.create(val)if (ob) {// register self as a parent of the child observer.var parents = ob.parentsif (!parents) {ob.parents = parents = Object.create(null)}if (parents[this.id]) {_.warn('Observing duplicate key: ' + key)return}parents[this.id] = {ob: this,key: key}} }

  其實代碼邏輯非常簡單,link函數會對給定數組index(默認為0)之后的元素調用this.observe, 而observe其實也就是對給定的val值遞歸調用Observer.create,將數據響應化,并建立父級的Observer與當前實例的對應關系。前面其實我們發現Vue不僅僅會對插入的數據響應化,并且也會對刪除的元素調用unlink,具體的調用代碼是:

if (removed) ob.unlink(removed)

  之前我們大致講過其用作就是對刪除的數據解除響應化,我們來看看具體的實現:

p.unlink = function (items) {for (var i = 0, l = items.length; i < l; i++) {this.unobserve(items[i])} } p.unobserve = function (val) {if (val && val.$observer) {val.$observer.parents[this.id] = null} }

  代碼非常簡單,就是對數據調用unobserve,而unobserve函數的主要目的就是解除父級observer與當前數據的關系并且不再保留引用,讓瀏覽器內核必要的時候能夠回收內存空間。

  在arrayAugmentations中其實還調用過Observer的兩個原型方法,一個是:

ob.updateIndices()

  另一個是:

ob.propagate('set', 'length', this.length)

  首先看看updateIndices函數,當時的函數的作用是更新子元素在parents的key,來看看具體實現:
  

p.updateIndices = function () {var arr = this.valuevar i = arr.lengthvar obwhile (i--) {ob = arr[i] && arr[i].$observerif (ob) {ob.parents[this.id].key = i}} }

  接著看函數propagate:
  

p.propagate = function (event, path, val, mutation) {this.emit(event, path, val, mutation)if (!this.parents) returnfor (var id in this.parents) {var parent = this.parents[id]if (!parent) continuevar key = parent.keyvar parentPath = path? key + Observer.pathDelimiter + path: keyparent.ob.propagate(event, parentPath, val, mutation)} }

  我們之前說過propagate函數的作用的就是觸發自身及其遞歸觸發父級的事件,首先調用emit函數對外觸發時間,其參數分別是:事件名、路徑、值、mutatin對象。然后接著遞歸調用父級的事件,并且對應改變觸發的path參數。parentPath等于parents[id].key + Observer.pathDelimiter + path

  到此為止我們已經學習完了Vue是如何處理數組的響應化的,現在需要來看看是如何處理對象的響應化的。
  

對象  

  
  在Observer的構造函數中關于對象處理的代碼是:

if (type === OBJECT) {if (options && options.doNotAlterProto) {_.deepMixin(value, objectAugmentations)} else {_.augment(value, objectAugmentations)}this.walk(value) }

  和數組一樣,我們首先要了解一下objectAugmentations的內部實現:

var _ = require('../util') var objectAgumentations = Object.create(Object.prototype)_.define(objectAgumentations, '$add', function (key, val) {if (this.hasOwnProperty(key)) return_.define(this, key, val, true)var ob = this.$observerob.observe(key, val)ob.convert(key, val)ob.emit('add:self', key, val)ob.propagate('add', key, val) })_.define(objectAgumentations, '$delete', function (key) {if (!this.hasOwnProperty(key)) returndelete this[key]var ob = this.$observerob.emit('delete:self', key)ob.propagate('delete', key) })

  相比于arrayAugmentations,objectAgumentations內部實現則簡單的多,objectAgumentations添加了兩個方法: $add與$delete。

  $add用于給對象添加新的屬性,如果該對象之前就存在鍵值為key的屬性則不做任何操作,否則首先使用_.define賦值該屬性,然后調用ob.observe目的是遞歸調用使得val值響應化。而convert函數的作用是將該屬性轉換成訪問器屬性getter/setter使得屬性被訪問或者被改變的時候我們能夠監聽到,具體我可以看一下convert函數的內部實現:
  

p.convert = function (key, val) {var ob = thisObject.defineProperty(ob.value, key, {enumerable: true,configurable: true,get: function () {if (Observer.emitGet) {ob.propagate('get', key)}return val},set: function (newVal) {if (newVal === val) returnob.unobserve(val)val = newValob.observe(key, newVal)ob.emit('set:self', key, newVal)ob.propagate('set', key, newVal)}}) }

  convert函數的內部實現也不復雜,在get函數中,如果開啟了全局的Observer.emitGet開關,在該屬性被訪問的時候,會對調用propagate觸發本身以及父級的對應get事件。在set函數中,首先調用unobserve對之間的值接觸響應化,接著調用ob.observe使得新賦值的數據響應化。最后首先觸發本身的set:self事件,接著調用propagate觸發本身以及父級的對應set事件。

  $delete用于給刪除對象的屬性,如果不存在該屬性則直接退出,否則先用delete操作符刪除對象的屬性,然后對外觸發本身的delete:self事件,接著調用delete觸發本身以及父級對應的delete事件。

  看完了objectAgumentations之后,我們在Observer構造函數中知道,如果傳入的參數中存在op.doNotAlterProto意味著不要改變對象的原型,則采用deepMixin函數將$add和$delete函數添加到對象中,否則采用函數arguments函數將$add和$delete添加到對象的原型中。最后調用了walk函數,讓我們看看walk是內部是怎么實現的:
  

p.walk = function (obj) {var key, val, descriptor, prefixfor (key in obj) {prefix = key.charCodeAt(0)if (prefix === 0x24 || // $prefix === 0x5F // _) {continue}descriptor = Object.getOwnPropertyDescriptor(obj, key)// only process own non-accessor propertiesif (descriptor && !descriptor.get) {val = obj[key]this.observe(key, val)this.convert(key, val)}} }

  首先遍歷obj中的各個屬性,如果是以$或者_開頭的屬性名,則不做處理。接著獲取該屬性的描述符,如果不存在get函數,則對該屬性值調用observe函數,使得數據響應化,然后調用convert函數將該屬性轉換成訪問器屬性getter/setter使得屬性被訪問或者被改變的時候能被夠監聽。
  

總結

  到此為止,我們已經看完了整個Observer模塊的所有代碼,其實基本原理和我們之前設想都是差不多的,只不過Vue代碼中各個函數分解粒度非常小,使得代碼邏輯非常清晰??吹竭@里,我推薦你也clone一份Vue源碼,checkout到對應的版本號,自己閱讀一遍,跑跑測試用例,打個斷點試著調試一下,應該會對你理解這個模塊有所幫助。

  最后如果對這個系列的文章感興趣歡迎大家關注我的Github博客算是對我鼓勵,感謝大家的支持!
  
  

總結

以上是生活随笔為你收集整理的Vue响应式数据: Observer模块实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。