真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Vue源碼解析之?dāng)?shù)組變異的實(shí)現(xiàn)

力有不逮的對(duì)象

我們提供的服務(wù)有:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、寶應(yīng)ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的寶應(yīng)網(wǎng)站制作公司

眾所周知,在 Vue 中,直接修改對(duì)象屬性的值無法觸發(fā)響應(yīng)式。當(dāng)你直接修改了對(duì)象屬性的值,你會(huì)發(fā)現(xiàn),只有數(shù)據(jù)改了,但是頁面內(nèi)容并沒有改變。

這是什么原因?

原因在于: Vue 的響應(yīng)式系統(tǒng)是基于Object.defineProperty這個(gè)方法的,該方法可以監(jiān)聽對(duì)象中某個(gè)元素的獲取或修改,經(jīng)過了該方法處理的數(shù)據(jù),我們稱其為響應(yīng)式數(shù)據(jù)。但是,該方法有一個(gè)很大的缺點(diǎn),新增屬性或者刪除屬性不會(huì)觸發(fā)監(jiān)聽,舉個(gè)栗子:

var vm = new Vue({
 data () {
  return {
   obj: {
    a: 1
   }
  }
 }
})
// `vm.obj.a` 現(xiàn)在是響應(yīng)式的

vm.obj.b = 2
// `vm.obj.b` 不是響應(yīng)式的

原因在于,在 Vue 初始化的時(shí)候, Vue 內(nèi)部會(huì)對(duì) data 方法的返回值進(jìn)行深度響應(yīng)式處理,使其變?yōu)轫憫?yīng)式數(shù)據(jù),所以, vm.obj.a 是響應(yīng)式的。但是,之后設(shè)置的 vm.obj.b 并沒有經(jīng)過 Vue 初始化時(shí)響應(yīng)式的洗禮,所以,理所應(yīng)當(dāng)?shù)牟皇琼憫?yīng)式。

那么,vm.obj.b可以變成響應(yīng)式嗎?當(dāng)然可以,通過 vm.$set 方法就可以完美地實(shí)現(xiàn)要求,在此不再贅述相關(guān)原理了,之后應(yīng)該會(huì)寫一篇文章講述 vm.$set 背后的原理。

更凄慘的數(shù)組

上面說了這么多,還沒有提到本篇文章的主角——數(shù)組,現(xiàn)在該主角出場了。

比起對(duì)象,數(shù)組的境遇更加凄慘一些,看看官方文檔:

由于 JavaScript 的限制, Vue 不能檢測以下變動(dòng)的數(shù)組:

  1. 當(dāng)你利用索引直接設(shè)置一個(gè)項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue
  2. 當(dāng)你修改數(shù)組的長度時(shí),例如:vm.items.length = newLength

有可能官方文檔不是很清晰,那我們繼續(xù)舉個(gè)栗子:

var vm = new Vue({
  data () {
    return {
      items: ['a', 'b', 'c']
    }
  }
})
vm.items[1] = 'x' // 不是響應(yīng)性的
vm.items.length = 2 // 不是響應(yīng)性的

也就是說,數(shù)組連自身元素的修改也無法監(jiān)聽,原因在于, Vue 對(duì) data 方法返回的對(duì)象中的元素進(jìn)行響應(yīng)式處理時(shí),如果元素是數(shù)組時(shí),僅僅對(duì)數(shù)組本身進(jìn)行響應(yīng)式化,而不對(duì)數(shù)組內(nèi)部元素進(jìn)行響應(yīng)式化。

這也就導(dǎo)致如官方文檔所寫的后果,無法直接修改數(shù)組內(nèi)部元素來觸發(fā)響應(yīng)式。

那么,有沒有破解方法呢?

當(dāng)然有,官方規(guī)定了 7 個(gè)數(shù)組方法,通過這 7 個(gè)數(shù)組方法,可以很開心地觸發(fā)數(shù)組的響應(yīng)式,這 7 個(gè)數(shù)組方法分別是:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

可以發(fā)現(xiàn),這 7 個(gè)數(shù)組方法貌似就是原生的那些數(shù)組方法,為什么這 7 個(gè)數(shù)組方法可以觸發(fā)應(yīng)式,觸發(fā)視圖更新呢?

你是不是心里想著:數(shù)組方法了不起呀,數(shù)組方法就可以為所欲為???

騷瑞啊,這 7 個(gè)數(shù)組方法是真的可以為所欲為的。

因?yàn)椋鼈兪亲儺惡蟮臄?shù)組方法。

數(shù)組變異思路

什么是變異數(shù)組方法?

變異數(shù)組方法即保持?jǐn)?shù)組方法原有功能不變的前提下對(duì)其進(jìn)行功能拓展,在 Vue 中這個(gè)所謂的功能拓展就是添加響應(yīng)式功能。

將普通的數(shù)組變?yōu)樽儺悢?shù)組的方法分為兩步:

  • 功能拓展
  • 數(shù)組劫持

功能拓展

先來個(gè)思考題:

有這樣一個(gè)需求,要求在不改變原有函數(shù)功能以及調(diào)用方式的情況下,使得每次調(diào)用該函數(shù)都能在控制臺(tái)中打印出'HelloWorld'

其實(shí)思路很簡單,分為三步:

  • 使用新的變量緩存原函數(shù)
  • 重新定義原函數(shù)
  • 在新定義的函數(shù)中調(diào)用原函數(shù)

看看具體的代碼實(shí)現(xiàn):

function A () {
  console.log('調(diào)用了函數(shù)A')
}

const nativeA = A
A = function () {
  console.log('HelloWorld')
  nativeA()
}

可以看到,通過這種方式,我們就保證了在不改變 A 函數(shù)行為的前提下對(duì)其進(jìn)行了功能拓展。

接下來,我們使用這種方法對(duì)數(shù)組原本方法進(jìn)行功能拓展:

// 變異方法名稱
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
]

const arrayProto = Array.prototype
// 繼承原有數(shù)組的方法
const arrayMethods = Object.create(arrayProto)

mutationMethods.forEach(method => {
  // 緩存原生數(shù)組方法
  const original = arrayProto[method]
  arrayMethods[method] = function (...args) {
    const result = original.apply(this, args)
    
    console.log('執(zhí)行響應(yīng)式功能')
    
    return result
  }
})

從代碼中可以看出來,我們調(diào)用 arrayMethods 這個(gè)對(duì)象中的方法有兩種情況:

  1. 調(diào)用功能拓展方法:直接調(diào)用 arrayMethods 中的方法
  2. 調(diào)用原生方法:這種情況下,通過原型鏈查找定義在數(shù)組原型中的原生方法

通過上述方法,我們實(shí)現(xiàn)了對(duì)數(shù)組原生方法進(jìn)行功能的拓展,但是,有一個(gè)巨大的問題擺在面前:我們該如何讓數(shù)組實(shí)例調(diào)用功能拓展后數(shù)組方法呢?

解決這一問題的方法就是:數(shù)組劫持。

數(shù)組劫持

數(shù)組劫持,顧名思義就是將原本數(shù)組實(shí)例要繼承的方法替換成我們功能拓展后的方法。

想一想,我們在前面實(shí)現(xiàn)了一個(gè)功能拓展后的數(shù)組 arrayMethods ,這個(gè)自定義的數(shù)組繼承自數(shù)組對(duì)象,我們只需要將其和普通數(shù)組實(shí)例連接起來,讓普通數(shù)組繼承于它即可。

而想實(shí)現(xiàn)上述操作,就是通過原型鏈。

實(shí)現(xiàn)方法如下代碼所示:

let arr = []
// 通過隱式原型繼承arrayMethods
arr.__proto__ = arrayMethods

// 執(zhí)行變異后方法
arr.push(1)

通過功能拓展和數(shù)組劫持,我們終于實(shí)現(xiàn)了變異數(shù)組,接下來讓我們看看 Vue 源碼是如何實(shí)現(xiàn)變異數(shù)組的。

源碼解析

我們來到 src/core/observer/index.js 中在 Observer 類中的 constructor 函數(shù):

constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  // 檢測是否是數(shù)組
  if (Array.isArray(value)) {
    // 能力檢測
    const augment = hasProto
    ? protoAugment
    : copyAugment
    // 通過能力檢測的結(jié)果選擇不同方式進(jìn)行數(shù)組劫持
    augment(value, arrayMethods, arrayKeys)
    // 對(duì)數(shù)組的響應(yīng)式處理
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}

Observer 這個(gè)類是 Vue 響應(yīng)式系統(tǒng)的核心組成部分,在初始化階段最主要的功能是將目標(biāo)對(duì)象進(jìn)行響應(yīng)式化。在這里,我們主要關(guān)注其對(duì)數(shù)組的處理。

其對(duì)數(shù)組的處理主要是以下代碼

// 能力檢測
const augment = hasProto
? protoAugment
: copyAugment
// 通過能力檢測的結(jié)果選擇不同方式進(jìn)行數(shù)組劫持
augment(value, arrayMethods, arrayKeys)
// 對(duì)數(shù)組的響應(yīng)式處理,很本文關(guān)系不大,略過
this.observeArray(value)

首先定義了 augment 常量,這個(gè)常量的值由 hasProto 決定。

我們來看看 hasProto

export const hasProto = '__proto__' in {}

可以發(fā)現(xiàn), hasProto 其實(shí)就是一個(gè)布爾值常量,用來表示瀏覽器是否支持直接使用 __proto__ (隱式原型) 。

所以,第一段代碼很好理解:根據(jù)根據(jù)能力檢測結(jié)果選擇不同的數(shù)組劫持方法,如果瀏覽器支持隱式原型,則調(diào)用 protoAugment 函數(shù)作為數(shù)組劫持的方法,反之則使用 copyAugment 。

不同的數(shù)組劫持方法

現(xiàn)在我們來看看 protoAugment 以及 copyAugment 。

function protoAugment (target, src: Object, keys: any) {
 /* eslint-disable no-proto */
 target.__proto__ = src
 /* eslint-enable no-proto */
}

可以看到, protoAugment 函數(shù)極其簡潔,和在數(shù)組變異思路中所說的方法一致:將數(shù)組實(shí)例直接通過隱式原型與變異數(shù)組連接起來,通過這種方式繼承變異數(shù)組中的方法。

接下來我們再看看 copyAugment

function copyAugment (target: Object, src: Object, keys: Array) {
 for (let i = 0, l = keys.length; i < l; i++) {
  const key = keys[i]
  // Object.defineProperty的封裝
  def(target, key, src[key])
 }
}

由于在這種情況下,瀏覽器不支持直接使用隱式原型,所以數(shù)組劫持方法要麻煩很多。我們知道該函數(shù)接收的第一個(gè)參數(shù)是數(shù)組實(shí)例,第二個(gè)參數(shù)是變異數(shù)組,那么第三個(gè)參數(shù)是什么?

// 獲取變異數(shù)組中所有自身屬性的屬性名
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

arrayKeys 在該文件的開頭就定義了,即變異數(shù)組中的所有自身屬性的屬性名,是一個(gè)數(shù)組。

回頭再看 copyAugment 函數(shù)就很清晰了,將所有變異數(shù)組中的方法,直接定義在數(shù)組實(shí)例本身,相當(dāng)于變相的實(shí)現(xiàn)了數(shù)組的劫持。

實(shí)現(xiàn)了數(shù)組劫持后,我們再來看看 Vue 中是怎樣實(shí)現(xiàn)數(shù)組的功能拓展的。

功能拓展

數(shù)組功能拓展的代碼位于 src/core/observer/array.js ,代碼如下:

import { def } from '../util/index'

// 緩存數(shù)組原型
const arrayProto = Array.prototype
// 實(shí)現(xiàn) arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto)

// 需要進(jìn)行功能拓展的方法
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
 // cache original method
 // 緩存原生數(shù)組方法
 const original = arrayProto[method]
 // 在變異數(shù)組中定義功能拓展方法
 def(arrayMethods, method, function mutator (...args) {
  // 執(zhí)行并緩存原生數(shù)組方法的執(zhí)行結(jié)果
  const result = original.apply(this, args)
  // 響應(yīng)式處理
  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()
  // 返回原生數(shù)組方法的執(zhí)行結(jié)果
  return result
 })
})

可以發(fā)現(xiàn),源碼在實(shí)現(xiàn)的方式上,和我在數(shù)組變異思路中采用的方法一致,只不過在其中添加了響應(yīng)式的處理。

總結(jié)

Vue 的變異數(shù)組從本質(zhì)上是來說是一種裝飾器模式,通過學(xué)習(xí)它的原理,我們在實(shí)際工作中可以輕松處理這類保持原有功能不變的前提下對(duì)其進(jìn)行功能拓展的需求。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。


文章標(biāo)題:Vue源碼解析之?dāng)?shù)組變異的實(shí)現(xiàn)
網(wǎng)頁URL:http://weahome.cn/article/pjddoo.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部