国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vue響應(yīng)式數(shù)據(jù): Observer模塊實(shí)現(xiàn)

shinezejian / 1894人閱讀

摘要:響應(yīng)式數(shù)據(jù)是在模塊中實(shí)現(xiàn)的我們可以看看是如何實(shí)現(xiàn)的。早期代碼使用是進(jìn)行單元測試,是事件模型的單元測試文件。模塊實(shí)際上采用采用組合繼承借用構(gòu)造函數(shù)原型繼承方式繼承了其目的就是繼承的,等方法。

前言

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

發(fā)布-訂閱模式  

  如果看過上兩篇文章的同學(xué)應(yīng)該會(huì)發(fā)現(xiàn)一個(gè)問題:數(shù)據(jù)響應(yīng)化的代碼與其他的代碼耦合太強(qiáng)了,比如說:
  

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

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

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

  在上面的代碼中我們監(jiān)聽了bodyclick事件,雖然我們不知道click事件什么時(shí)候會(huì)發(fā)生,但是我們一定能保證,如果發(fā)生了bodyclick事件,我們一定能得到通知,即回調(diào)函數(shù)被調(diào)用。在JavaScript中因?yàn)楹瘮?shù)是一等公民,我們很少使用傳統(tǒng)的發(fā)布-訂閱模式,多采用的是事件模型的方式實(shí)現(xiàn)。在Vue中也實(shí)現(xiàn)了一個(gè)事件模型,我們可以看一下。因?yàn)閂ue的模塊之間解耦的非常好,因此在看代碼之前,其實(shí)我們可以先來看看對(duì)應(yīng)的單元測試文件,你就知道這個(gè)模塊要實(shí)現(xiàn)什么功能,甚至如果你愿意的話,也可以自己實(shí)現(xiàn)一個(gè)類似的模塊放進(jìn)Vue的源碼中運(yùn)行。

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

describe是一個(gè)測試單元集合

it是一個(gè)測試用例

beforeEach會(huì)在每一個(gè)測試用例it執(zhí)行前執(zhí)行

expect期望函數(shù),用作對(duì)期望值和實(shí)際值之間執(zhí)行邏輯比較

createSpy用來創(chuàng)建spy,而spy的作用是監(jiān)測函數(shù)的調(diào)用相關(guān)信息和函數(shù)執(zhí)行參數(shù)

  

var Emitter = require("../../../src/emitter")
var u = undefined
// 代碼有刪減
describe("Emitter", function () {

  var e, spy
  beforeEach(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對(duì)象實(shí)例對(duì)外提供以下接口:

on: 注冊監(jiān)聽接口,參數(shù)分別是事件名監(jiān)聽函數(shù)

emit: 觸發(fā)事件函數(shù),參數(shù)是事件名

off: 取消對(duì)應(yīng)事件的注冊函數(shù),參數(shù)分別是事件名監(jiān)聽函數(shù)

once: 與on類似,僅會(huì)在第一次時(shí)通知監(jiān)聽函數(shù),隨后監(jiān)聽函數(shù)會(huì)被移除。

  看完了上面的單元測試代碼,我們現(xiàn)在已經(jīng)基本了解了這個(gè)模塊要干什么,現(xiàn)在讓我們看看對(duì)應(yīng)的代碼:

// 刪去了注釋并且對(duì)代碼順序有調(diào)整
// ctx是監(jiān)聽回調(diào)函數(shù)的執(zhí)行作用域(this)
function Emitter (ctx) {
  this._ctx = ctx || this
}

var p = Emitter.prototype

p.on = function (event, fn) {
  this._cbs = this._cbs || {}
  ;(this._cbs[event] || (this._cbs[event] = []))
    .push(fn)
  return this
}
// 三種模式 
// 不傳參情況清空所有監(jiān)聽函數(shù) 
// 僅傳事件名則清除該事件的所有監(jiān)聽函數(shù)
// 傳遞事件名和回調(diào)函數(shù),則對(duì)應(yīng)僅刪除對(duì)應(yīng)的監(jiān)聽事件
p.off = function (event, fn) {
  this._cbs = this._cbs || {}

  // all
  if (!arguments.length) {
    this._cbs = {}
    return this
  }

  // specific event
  var callbacks = this._cbs[event]
  if (!callbacks) return this

  // remove all handlers
  if (arguments.length === 1) {
    delete this._cbs[event]
    return this
  }

  // remove specific handler
  var cb
  for (var i = 0; i < callbacks.length; i++) {
    cb = callbacks[i]
    // 這邊的代碼之所以會(huì)有cb.fn === fn要結(jié)合once函數(shù)去看
    // 給once傳遞的監(jiān)聽函數(shù)其實(shí)已經(jīng)被wrapped過
    // 但是仍然可以通過原來的監(jiān)聽函數(shù)去off掉
    if (cb === fn || cb.fn === fn) {
      callbacks.splice(i, 1)
      break
    }
  }
  return this
}
// 觸發(fā)對(duì)應(yīng)事件的所有監(jiān)聽函數(shù),注意最多只能用給監(jiān)聽函數(shù)傳遞三個(gè)參數(shù)(采用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
}
// 觸發(fā)對(duì)應(yīng)事件的所有監(jiān)聽函數(shù),傳遞參數(shù)個(gè)數(shù)不受限制(采用apply)
p.applyEmit = function (event) {
  this._cbs = this._cbs || {}
  var callbacks = this._cbs[event], args

  if (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
}
// 通過調(diào)用on與off事件事件,在第一次觸發(fā)之后就`off`對(duì)應(yīng)的監(jiān)聽事件
p.once = function (event, fn) {
  var self = this
  this._cbs = this._cbs || {}

  function on () {
    self.off(event, on)
    fn.apply(this, arguments)
  }

  on.fn = fn
  this.on(event, on)
  return this
}

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

Observer 對(duì)外功能

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

//測試用例是精簡版,否則太冗長
var Observer = require("../../../src/observe/observer")
var _ = require("../../../src/util") //Vue內(nèi)部使用工具方法
var u = undefined
Observer.pathDelimiter = "." //配置Observer路徑分隔符

describe("Observer", function () {

  var spy
  beforeEach(function () {
    spy = jasmine.createSpy("observer")
  })
//我們可以看到我們通過Observer.create函數(shù)可以將數(shù)據(jù)變?yōu)榭身憫?yīng)化,
//然后我們監(jiān)聽get事件可以在屬性被讀取時(shí)觸發(fā)對(duì)應(yīng)事件,注意對(duì)象嵌套的情況(例如b.c)
  it("get", function () {
    Observer.emitGet = true
    var obj = {
      a: 1,
      b: {
        c: 2
      }
    }
    var ob = Observer.create(obj)
    ob.on("get", spy)

    var t = obj.b.c
    expect(spy).toHaveBeenCalledWith("b", u, u)
    expect(spy).toHaveBeenCalledWith("b.c", u, u)
    
    Observer.emitGet = false
  })
//我們可以監(jiān)聽響應(yīng)式數(shù)據(jù)的set事件,當(dāng)響應(yīng)式數(shù)據(jù)修改的時(shí)候,會(huì)觸發(fā)對(duì)應(yīng)的時(shí)間
  it("set", function () {
    var obj = {
      a: 1,
      b: {
        c: 2
      }
    }
    var ob = Observer.create(obj)
    ob.on("set", spy)

    obj.b.c = 4
    expect(spy).toHaveBeenCalledWith("b.c", 4, u)
  })
//帶有$與_開頭的屬性都不會(huì)被處理
  it("ignore prefix", function () {
    var obj = {
      _test: 123,
      $test: 234
    }
    var ob = Observer.create(obj)
    ob.on("set", spy)
    obj._test = 234
    obj.$test = 345
    expect(spy.calls.count()).toBe(0)
  })
//訪問器屬性也不會(huì)被處理
  it("ignore accessors", function () {
    var obj = {
      a: 123,
      get b () {
        return this.a
      }
    }
    var ob = Observer.create(obj)
    obj.a = 234
    expect(obj.b).toBe(234)
  })
// 對(duì)數(shù)屬性的get監(jiān)聽,注意嵌套的情況
  it("array get", function () {

    Observer.emitGet = true

    var obj = {
      arr: [{a:1}, {a:2}]
    }
    var ob = Observer.create(obj)
    ob.on("get", spy)

    var t = obj.arr[0].a
    expect(spy).toHaveBeenCalledWith("arr", u, u)
    expect(spy).toHaveBeenCalledWith("arr.0.a", u, u)
    expect(spy.calls.count()).toBe(2)

    Observer.emitGet = false
  })
// 對(duì)數(shù)屬性的get監(jiān)聽,注意嵌套的情況
  it("array set", function () {
    var obj = {
      arr: [{a:1}, {a:2}]
    }
    var ob = Observer.create(obj)
    ob.on("set", spy)

    obj.arr[0].a = 2
    expect(spy).toHaveBeenCalledWith("arr.0.a", 2, u)
  })
// 我們看到可以通過監(jiān)聽mutate事件,在push調(diào)用的時(shí)候?qū)?yīng)觸發(fā)事件
// 觸發(fā)事件第一個(gè)參數(shù)是"",代表的是路徑名,具體源碼可以看出,對(duì)于數(shù)組變異方法都是空字符串
// 觸發(fā)事件第二個(gè)參數(shù)是數(shù)組本身
// 觸發(fā)事件第三個(gè)參數(shù)比較復(fù)雜,其中:
// method屬性: 代表觸發(fā)的方法名稱
// args屬性: 代表觸發(fā)方法傳遞參數(shù)
// result屬性: 代表觸發(fā)變異方法之后數(shù)組的結(jié)果
// index屬性: 代表變異方法對(duì)數(shù)組發(fā)生變化的最開始元素
// inserted屬性: 代表數(shù)組新增的元素
// remove屬性: 代表數(shù)組刪除的元素
// 其他的變異方法: pop、shift、unshift、splice、sort、reverse內(nèi)容都是非常相似的
// 具體我們就不一一列舉的了,如果有疑問可以自己看到全部的單元測試代碼
  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])
  })
  
// 我們可以看到響應(yīng)式數(shù)據(jù)中存在$add方法,類似于Vue.set,可以監(jiān)聽add事件
// 可以向響應(yīng)式對(duì)象中添加新一個(gè)屬性,如果之前存在該屬性則操作會(huì)被忽略
// 并且新賦值的對(duì)象也必須被響應(yīng)化
// 我們省略了對(duì)象數(shù)據(jù)$delete方法的單元測試,功能類似于Vue.delete,與$add方法相反,可以用于刪除對(duì)象的屬性
// 我們省略了數(shù)組的$set方法的單元測試,功能也類似與Vue.set,可以用于設(shè)置數(shù)組對(duì)應(yīng)數(shù)字下標(biāo)的值
// 我們省略了數(shù)組的$remove方法的單元測試,功能用于移除數(shù)組給定下標(biāo)的值或者給定的值,例如:
// var arr = [{a:1}, {a:2}]
// var ob = Observer.create(arr)
// arr.$remove(0) => 移除對(duì)應(yīng)下標(biāo)的值 或者
// arr.$remove(arr[0]) => 移除給定的值

  it("object.$add", function () {
    var obj = {a:{b:1}}
    var ob = Observer.create(obj)
    ob.on("add", spy)

    // ignore existing keys
    obj.$add("a", 123)
    expect(spy.calls.count()).toBe(0)

    // add event
    var add = {d:2}
    obj.a.$add("c", add)
    expect(spy).toHaveBeenCalledWith("a.c", add, u)

    // check if add object is properly observed
    ob.on("set", spy)
    obj.a.c.d = 3
    expect(spy).toHaveBeenCalledWith("a.c.d", 3, u)
  })

// 下面的測試用例用來表示如果兩個(gè)不同對(duì)象parentA、parentB的屬性指向同一個(gè)對(duì)象obj,那么該對(duì)象obj改變時(shí)會(huì)分別parentA與parentB的監(jiān)聽事件

  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 = 2
    expect(spy.calls.count()).toBe(2)
    expect(spy).toHaveBeenCalledWith("child1.a", 2, u)
    expect(spy).toHaveBeenCalledWith("child2.a", 2, u)
    // test unobserve
    parentA.child1 = null
    obj.a = 3
    expect(spy.calls.count()).toBe(4)
    expect(spy).toHaveBeenCalledWith("child1", null, u)
    expect(spy).toHaveBeenCalledWith("child2.a", 3, u)
  })

})
源碼實(shí)現(xiàn) 數(shù)組

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

// 代碼出自于observe.js
// 為了方便講解我對(duì)代碼順序做了改變,要了解詳細(xì)的情況可以查看具體的源碼

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 = 1

function Observer (value, type, options) {
  Emitter.call(this, options && options.callbackContext)
  this.id = ++uid
  this.value = value
  this.type = type
  this.parents = null
  if (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 = ""

Observer.emitGet = false

Observer.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值沒有響應(yīng)化過(通過是否含有$observer屬性去判斷),則使用new操作符創(chuàng)建Obsever實(shí)例(區(qū)分對(duì)象OBJECT與數(shù)組ARRAY)。接下來我們看Observer的構(gòu)造函數(shù)是怎么定義的,首先借用Emitter構(gòu)造函數(shù):
  

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

配合原型繼承

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

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

id: 響應(yīng)式數(shù)據(jù)的唯一標(biāo)識(shí)

value: 原始數(shù)據(jù)

type: 標(biāo)識(shí)是數(shù)組還是對(duì)象

parents: 標(biāo)識(shí)響應(yīng)式數(shù)據(jù)的父級(jí),可能存在多個(gè),比如var obj = { a : { b: 1}},在處理{b: 1}的響應(yīng)化過程中parents中某個(gè)屬性指向的就是obj$observer

  我們接著看首先給該數(shù)據(jù)賦值$observer屬性,指向的是實(shí)例對(duì)象本身。_.define內(nèi)部是通過defineProperty實(shí)現(xiàn)的:

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

  下面我們首先看看是怎么處理數(shù)組類型的數(shù)據(jù)的

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

  如果看過我前兩篇文章的同學(xué),其實(shí)還記得我們對(duì)數(shù)組響應(yīng)化當(dāng)時(shí)還做了一個(gè)著重的原理講解,大概原理就是我們通過給數(shù)組對(duì)象設(shè)置新的原型對(duì)象,從而遮蔽掉原生數(shù)組的變異方法,大概的原理可以是:
  

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 () {
            // 調(diào)用對(duì)應(yīng)的原生方法并返回結(jié)果
            // do everything you what do !
            return original.apply(this, arguments);
        };
    });
    array.__proto__ = arrayAugmentations;
}

  回到Vue的源碼,雖然我們知道基本原理肯定是相同的,但是我們?nèi)匀恍枰纯?b>arrayAugmentations是什么?下面arrayAugmentations代碼比較長。我們會(huì)在注釋里面解釋基本原理:
  

// 代碼來自于array-augmentations.js
var _ = require("../util")
var arrayAugmentations = Object.create(Array.prototype)
// 這邊操作和我們之前的實(shí)現(xiàn)方式非常相似
// 創(chuàng)建arrayAugmentations原型繼承`Array.prototype`從而可以調(diào)用數(shù)組的原生方法
// 然后通過arrayAugmentations覆蓋數(shù)組的變異方法,基本邏輯大致相同
["push","pop","shift","unshift","splice","sort","reverse"].forEach(function (method) {
  var original = Array.prototype[method]
  // 覆蓋arrayAugmentations中的變異方法
  _.define(arrayAugmentations, method, function () {
    
    var args = _.toArray(arguments)
    // 這里調(diào)用了原生的數(shù)組變異方法,并獲得結(jié)果
    var result = original.apply(this, args)
    var ob = this.$observer
    var inserted, removed, index
    // 下面switch這一部分代碼看起來很長,其實(shí)目的就是針對(duì)于不同的變異方法生成:
    // insert removed inserted 具體的含義對(duì)照之前的解釋,了解即可
    switch (method) {
      case "push":
        inserted = args
        index = this.length - args.length
        break
      case "unshift":
        inserted = args
        index = 0
        break
      case "pop":
        removed = [result]
        index = this.length
        break
      case "shift":
        removed = [result]
        index = 0
        break
      case "splice":
        inserted = args.slice(2)
        removed = result
        index = args[0]
        break
    }

    // 如果給數(shù)組中插入新的數(shù)據(jù),則需要調(diào)用ob.link
    // link函數(shù)其實(shí)在上面的_.augment(value, arrayAugmentations)之后也被調(diào)用了
    // 具體的實(shí)現(xiàn)我們可以先不管
    // 我們只要知道其目的就是分別對(duì)插入的數(shù)據(jù)執(zhí)行響應(yīng)化
    if (inserted) ob.link(inserted, index)
    // 其實(shí)從link我們就可以猜出unlink是干什么的
    // 主要就是對(duì)刪除的數(shù)據(jù)解除響應(yīng)化,具體實(shí)現(xiàn)邏輯后面解釋
    if (removed) ob.unlink(removed)

    // updateIndices我們也先不講是怎么實(shí)現(xiàn)的,
    // 目的就是更新子元素在parents的key
    // 因?yàn)閜ush和pop是不會(huì)改變現(xiàn)有元素的位置,因此不需要調(diào)用
    // 而諸如splce shift unshift等變異方法會(huì)改變對(duì)應(yīng)下標(biāo)值,因此需要調(diào)用
    if (method !== "push" && method !== "pop") {
      ob.updateIndices()
    }

    // 同樣我們先不考慮propagate內(nèi)部實(shí)現(xiàn),我們只要propagate函數(shù)的目的就是
    // 觸發(fā)自身及其遞歸觸發(fā)父級(jí)的事件
    // 如果數(shù)組中的數(shù)據(jù)有插入或者刪除,則需要對(duì)外觸發(fā)"length"被改變
    if (inserted || removed) {
      ob.propagate("set", "length", this.length)
    }

    // 對(duì)外觸發(fā)mutate事件
    // 可以對(duì)照我們之前講的測試用例"array push",就是在這里觸發(fā)的,回頭看看吧
    ob.propagate("mutate", "", this, {
      method   : method,
      args     : args,
      result   : result,
      index    : index,
      inserted : inserted || [],
      removed  : removed || []
    })

    return result
  })
})

// 可以回看一下測試用例 array set,目的就是設(shè)置對(duì)應(yīng)下標(biāo)的值
// 其實(shí)就是調(diào)用了splice變異方法, 其實(shí)我們在Vue中國想要改變某個(gè)下標(biāo)的值的時(shí)候
// 官網(wǎng)給出的建議無非是Vue.set或者就是splice,都是相同的原理
// 注意這里的代碼忽略了超出下標(biāo)范圍的值
_.define(arrayAugmentations, "$set", function (index, val) {
  if (index >= this.length) {
    this.length = index + 1
  }
  return this.splice(index, 1, val)[0]
})
// $remove與$add都是一個(gè)道理,都是調(diào)用的是`splice`函數(shù)
_.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

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

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

  我們看到如果瀏覽器不支持__proto__話調(diào)用deepMixin函數(shù)。而deepMixin的實(shí)現(xiàn)也是非常的簡單,就是使用Object.defineProperty將原對(duì)象的屬性描述符賦值給目標(biāo)對(duì)象。接著調(diào)用了函數(shù):
  

this.link(value)

  關(guān)于link函數(shù)在上面的備注中我們已經(jīng)見過了:

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

  當(dāng)時(shí)我們的解釋是將新插入的數(shù)據(jù)響應(yīng)化,知道了功能我們看看代碼的實(shí)現(xiàn):
  

// p === Observer.prototype
p.link = function (items, index) {
  index = index || 0
  for (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.parents
    if (!parents) {
      ob.parents = parents = Object.create(null)
    }
    if (parents[this.id]) {
      _.warn("Observing duplicate key: " + key)
      return
    }
    parents[this.id] = {
      ob: this,
      key: key
    }
  }
}

  其實(shí)代碼邏輯非常簡單,link函數(shù)會(huì)對(duì)給定數(shù)組index(默認(rèn)為0)之后的元素調(diào)用this.observe, 而observe其實(shí)也就是對(duì)給定的val值遞歸調(diào)用Observer.create,將數(shù)據(jù)響應(yīng)化,并建立父級(jí)的Observer與當(dāng)前實(shí)例的對(duì)應(yīng)關(guān)系。前面其實(shí)我們發(fā)現(xiàn)Vue不僅僅會(huì)對(duì)插入的數(shù)據(jù)響應(yīng)化,并且也會(huì)對(duì)刪除的元素調(diào)用unlink,具體的調(diào)用代碼是:

if (removed) ob.unlink(removed)

  之前我們大致講過其用作就是對(duì)刪除的數(shù)據(jù)解除響應(yīng)化,我們來看看具體的實(shí)現(xiàn):

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
  }
}

  代碼非常簡單,就是對(duì)數(shù)據(jù)調(diào)用unobserve,而unobserve函數(shù)的主要目的就是解除父級(jí)observer與當(dāng)前數(shù)據(jù)的關(guān)系并且不再保留引用,讓瀏覽器內(nèi)核必要的時(shí)候能夠回收內(nèi)存空間。

  在arrayAugmentations中其實(shí)還調(diào)用過Observer的兩個(gè)原型方法,一個(gè)是:

ob.updateIndices()

  另一個(gè)是:

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

  首先看看updateIndices函數(shù),當(dāng)時(shí)的函數(shù)的作用是更新子元素在parents的key,來看看具體實(shí)現(xiàn):
  

p.updateIndices = function () {
  var arr = this.value
  var i = arr.length
  var ob
  while (i--) {
    ob = arr[i] && arr[i].$observer
    if (ob) {
      ob.parents[this.id].key = i
    }
  }
}

  接著看函數(shù)propagate:
  

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

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

  到此為止我們已經(jīng)學(xué)習(xí)完了Vue是如何處理數(shù)組的響應(yīng)化的,現(xiàn)在需要來看看是如何處理對(duì)象的響應(yīng)化的。
  

對(duì)象  

  
  在Observer的構(gòu)造函數(shù)中關(guān)于對(duì)象處理的代碼是:

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

  和數(shù)組一樣,我們首先要了解一下objectAugmentations的內(nèi)部實(shí)現(xiàn):

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.$observer
  ob.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)) return
  delete this[key]
  var ob = this.$observer
  ob.emit("delete:self", key)
  ob.propagate("delete", key)
})

  相比于arrayAugmentationsobjectAgumentations內(nèi)部實(shí)現(xiàn)則簡單的多,objectAgumentations添加了兩個(gè)方法: $add$delete

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

p.convert = function (key, val) {
  var ob = this
  Object.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) return
      ob.unobserve(val)
      val = newVal
      ob.observe(key, newVal)
      ob.emit("set:self", key, newVal)
      ob.propagate("set", key, newVal)
    }
  })
}

  convert函數(shù)的內(nèi)部實(shí)現(xiàn)也不復(fù)雜,在get函數(shù)中,如果開啟了全局的Observer.emitGet開關(guān),在該屬性被訪問的時(shí)候,會(huì)對(duì)調(diào)用propagate觸發(fā)本身以及父級(jí)的對(duì)應(yīng)get事件。在set函數(shù)中,首先調(diào)用unobserve對(duì)之間的值接觸響應(yīng)化,接著調(diào)用ob.observe使得新賦值的數(shù)據(jù)響應(yīng)化。最后首先觸發(fā)本身的set:self事件,接著調(diào)用propagate觸發(fā)本身以及父級(jí)的對(duì)應(yīng)set事件。

  $delete用于給刪除對(duì)象的屬性,如果不存在該屬性則直接退出,否則先用delete操作符刪除對(duì)象的屬性,然后對(duì)外觸發(fā)本身的delete:self事件,接著調(diào)用delete觸發(fā)本身以及父級(jí)對(duì)應(yīng)的delete事件。

  看完了objectAgumentations之后,我們在Observer構(gòu)造函數(shù)中知道,如果傳入的參數(shù)中存在op.doNotAlterProto意味著不要改變對(duì)象的原型,則采用deepMixin函數(shù)將$add$delete函數(shù)添加到對(duì)象中,否則采用函數(shù)arguments函數(shù)將$add$delete添加到對(duì)象的原型中。最后調(diào)用了walk函數(shù),讓我們看看walk是內(nèi)部是怎么實(shí)現(xiàn)的:
  

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

  首先遍歷obj中的各個(gè)屬性,如果是以$或者_開頭的屬性名,則不做處理。接著獲取該屬性的描述符,如果不存在get函數(shù),則對(duì)該屬性值調(diào)用observe函數(shù),使得數(shù)據(jù)響應(yīng)化,然后調(diào)用convert函數(shù)將該屬性轉(zhuǎn)換成訪問器屬性getter/setter使得屬性被訪問或者被改變的時(shí)候能被夠監(jiān)聽。
  

總結(jié)

  到此為止,我們已經(jīng)看完了整個(gè)Observer模塊的所有代碼,其實(shí)基本原理和我們之前設(shè)想都是差不多的,只不過Vue代碼中各個(gè)函數(shù)分解粒度非常小,使得代碼邏輯非常清晰。看到這里,我推薦你也clone一份Vue源碼,checkout到對(duì)應(yīng)的版本號(hào),自己閱讀一遍,跑跑測試用例,打個(gè)斷點(diǎn)試著調(diào)試一下,應(yīng)該會(huì)對(duì)你理解這個(gè)模塊有所幫助。

  最后如果對(duì)這個(gè)系列的文章感興趣歡迎大家關(guān)注我的Github博客算是對(duì)我鼓勵(lì),感謝大家的支持!
  
  

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/95842.html

相關(guān)文章

  • Vue: Binding與Watcher

    摘要:當(dāng)數(shù)據(jù)改變時(shí),我們不需要直接觸發(fā)所有的回調(diào)函數(shù),而是去通知對(duì)應(yīng)的數(shù)據(jù)的,然后由去執(zhí)行相應(yīng)的邏輯。對(duì)于其邏輯可能是一個(gè)指令用于連接與響應(yīng)式數(shù)據(jù)或者是一個(gè)偵聽器的回調(diào)函數(shù),這樣就能符合單一職責(zé)原則,解除模塊之間的耦合度,讓程序更易維護(hù)。 前言   首先歡迎大家關(guān)注我的Github博客,也算是對(duì)我的一點(diǎn)鼓勵(lì),畢竟寫東西沒法獲得變現(xiàn),能堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。接下來的日子我應(yīng)...

    tyheist 評(píng)論0 收藏0
  • 淺析Vue響應(yīng)原理(二)

    摘要:響應(yīng)式原理之之前簡單介紹了和類的代碼和作用,現(xiàn)在來介紹一下類和。對(duì)于數(shù)組,響應(yīng)式的實(shí)現(xiàn)稍有不同。不存在時(shí),說明不是響應(yīng)式數(shù)據(jù),直接更新。如果對(duì)象是響應(yīng)式的,確保刪除能觸發(fā)更新視圖。 Vue響應(yīng)式原理之Observer 之前簡單介紹了Dep和Watcher類的代碼和作用,現(xiàn)在來介紹一下Observer類和set/get。在Vue實(shí)例后再添加響應(yīng)式數(shù)據(jù)時(shí)需要借助Vue.set/vm.$se...

    rockswang 評(píng)論0 收藏0
  • 預(yù)計(jì)今年發(fā)布的Vue3.0到底有什么不一樣的地方?

    摘要:模板語法的將保持不變。基于的觀察者機(jī)制目前,的反應(yīng)系統(tǒng)是使用的和。為了繼續(xù)支持,將發(fā)布一個(gè)支持舊觀察者機(jī)制和新版本的構(gòu)建。 showImg(https://segmentfault.com/img/remote/1460000017862774?w=1898&h=796); 還有幾個(gè)月距離vue2的首次發(fā)布就滿3年了,而vue的作者尤雨溪也在去年年末發(fā)布了關(guān)于vue3.0的計(jì)劃,如果不...

    fnngj 評(píng)論0 收藏0
  • Vue 數(shù)據(jù)響應(yīng)原理

    摘要:接下來,我們就一起深入了解的數(shù)據(jù)響應(yīng)式原理,搞清楚響應(yīng)式的實(shí)現(xiàn)機(jī)制。回調(diào)函數(shù)只是打印出新的得到的新的值,由執(zhí)行后生成。及異步更新相信讀過前文,你應(yīng)該對(duì)響應(yīng)式原理有基本的認(rèn)識(shí)。 前言 Vue.js 的核心包括一套響應(yīng)式系統(tǒng)。 響應(yīng)式,是指當(dāng)數(shù)據(jù)改變后,Vue 會(huì)通知到使用該數(shù)據(jù)的代碼。例如,視圖渲染中使用了數(shù)據(jù),數(shù)據(jù)改變后,視圖也會(huì)自動(dòng)更新。 舉個(gè)簡單的例子,對(duì)于模板: {{ name ...

    Mike617 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<