亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

Vue源碼解析之數組變異的實現

2024-05-06 16:47:15
字體:
來源:轉載
供稿:網友

力有不逮的對象

眾所周知,在 Vue 中,直接修改對象屬性的值無法觸發響應式。當你直接修改了對象屬性的值,你會發現,只有數據改了,但是頁面內容并沒有改變。

這是什么原因?

原因在于: Vue 的響應式系統是基于Object.defineProperty這個方法的,該方法可以監聽對象中某個元素的獲取或修改,經過了該方法處理的數據,我們稱其為響應式數據。但是,該方法有一個很大的缺點,新增屬性或者刪除屬性不會觸發監聽,舉個栗子:

var vm = new Vue({ data () {  return {   obj: {    a: 1   }  } }})// `vm.obj.a` 現在是響應式的vm.obj.b = 2// `vm.obj.b` 不是響應式的

原因在于,在 Vue 初始化的時候, Vue 內部會對 data 方法的返回值進行深度響應式處理,使其變為響應式數據,所以, vm.obj.a 是響應式的。但是,之后設置的 vm.obj.b 并沒有經過 Vue 初始化時響應式的洗禮,所以,理所應當的不是響應式。

那么,vm.obj.b可以變成響應式嗎?當然可以,通過 vm.$set 方法就可以完美地實現要求,在此不再贅述相關原理了,之后應該會寫一篇文章講述 vm.$set 背后的原理。

更凄慘的數組

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

比起對象,數組的境遇更加凄慘一些,看看官方文檔:

由于 JavaScript 的限制, Vue 不能檢測以下變動的數組:

  1. 當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
  2. 當你修改數組的長度時,例如:vm.items.length = newLength

有可能官方文檔不是很清晰,那我們繼續舉個栗子:

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

也就是說,數組連自身元素的修改也無法監聽,原因在于, Vue 對 data 方法返回的對象中的元素進行響應式處理時,如果元素是數組時,僅僅對數組本身進行響應式化,而不對數組內部元素進行響應式化。

這也就導致如官方文檔所寫的后果,無法直接修改數組內部元素來觸發響應式。

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

當然有,官方規定了 7 個數組方法,通過這 7 個數組方法,可以很開心地觸發數組的響應式,這 7 個數組方法分別是:

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

可以發現,這 7 個數組方法貌似就是原生的那些數組方法,為什么這 7 個數組方法可以觸發應式,觸發視圖更新呢?

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

騷瑞啊,這 7 個數組方法是真的可以為所欲為的。

因為,它們是變異后的數組方法。

數組變異思路

什么是變異數組方法?

變異數組方法即保持數組方法原有功能不變的前提下對其進行功能拓展,在 Vue 中這個所謂的功能拓展就是添加響應式功能。

將普通的數組變為變異數組的方法分為兩步:

  • 功能拓展
  • 數組劫持

功能拓展

先來個思考題:

有這樣一個需求,要求在不改變原有函數功能以及調用方式的情況下,使得每次調用該函數都能在控制臺中打印出'HelloWorld'

其實思路很簡單,分為三步:

  • 使用新的變量緩存原函數
  • 重新定義原函數
  • 在新定義的函數中調用原函數

看看具體的代碼實現:

function A () {  console.log('調用了函數A')}const nativeA = AA = function () {  console.log('HelloWorld')  nativeA()}

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

接下來,我們使用這種方法對數組原本方法進行功能拓展:

// 變異方法名稱const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']const arrayProto = Array.prototype// 繼承原有數組的方法const arrayMethods = Object.create(arrayProto)mutationMethods.forEach(method => {  // 緩存原生數組方法  const original = arrayProto[method]  arrayMethods[method] = function (...args) {    const result = original.apply(this, args)        console.log('執行響應式功能')        return result  }})

從代碼中可以看出來,我們調用 arrayMethods 這個對象中的方法有兩種情況:

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

通過上述方法,我們實現了對數組原生方法進行功能的拓展,但是,有一個巨大的問題擺在面前:我們該如何讓數組實例調用功能拓展后數組方法呢?

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

數組劫持

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

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

而想實現上述操作,就是通過原型鏈。

實現方法如下代碼所示:

let arr = []// 通過隱式原型繼承arrayMethodsarr.__proto__ = arrayMethods// 執行變異后方法arr.push(1)

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

源碼解析

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

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

Observer 這個類是 Vue 響應式系統的核心組成部分,在初始化階段最主要的功能是將目標對象進行響應式化。在這里,我們主要關注其對數組的處理。

其對數組的處理主要是以下代碼

// 能力檢測const augment = hasProto? protoAugment: copyAugment// 通過能力檢測的結果選擇不同方式進行數組劫持augment(value, arrayMethods, arrayKeys)// 對數組的響應式處理,很本文關系不大,略過this.observeArray(value)

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

我們來看看 hasProto

export const hasProto = '__proto__' in {}

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

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

不同的數組劫持方法

現在我們來看看 protoAugment 以及 copyAugment 。

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

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

接下來我們再看看 copyAugment 

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

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

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

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

回頭再看 copyAugment 函數就很清晰了,將所有變異數組中的方法,直接定義在數組實例本身,相當于變相的實現了數組的劫持。

實現了數組劫持后,我們再來看看 Vue 中是怎樣實現數組的功能拓展的。

功能拓展

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

import { def } from '../util/index'// 緩存數組原型const arrayProto = Array.prototype// 實現 arrayMethods.__proto__ === Array.prototypeexport 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 })})

可以發現,源碼在實現的方式上,和我在數組變異思路中采用的方法一致,只不過在其中添加了響應式的處理。

總結

Vue 的變異數組從本質上是來說是一種裝飾器模式,通過學習它的原理,我們在實際工作中可以輕松處理這類保持原有功能不變的前提下對其進行功能拓展的需求。希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲国产高潮在线观看| 久久理论片午夜琪琪电影网| 91精品啪在线观看麻豆免费| 日韩欧美黄色动漫| 国产精品综合久久久| 国产在线观看91精品一区| 欧美性资源免费| www日韩中文字幕在线看| 亚洲精品一区在线观看香蕉| 亚洲春色另类小说| 日韩av免费一区| 欧美电影《睫毛膏》| 国产精品成人一区| 欧美日韩国产在线看| 中文字幕av一区二区三区谷原希美| 欧美性一区二区三区| 精品亚洲一区二区三区四区五区| 精品久久香蕉国产线看观看gif| 中文字幕日韩欧美| 亚洲国产精品中文| 久久99久久99精品免观看粉嫩| 国产在线精品自拍| 国产精品欧美日韩一区二区| 欧美性色19p| 亚洲区一区二区| 美女视频久久黄| 亚洲国产女人aaa毛片在线| 日韩精品视频免费| 亚洲精品国产精品国自产观看浪潮| 国产ts一区二区| 91av在线不卡| 亚洲网站在线看| 成人免费高清完整版在线观看| 成人日韩av在线| 丝袜亚洲欧美日韩综合| 国产亚洲精品91在线| 国产精品中文字幕在线观看| 狠狠做深爱婷婷久久综合一区| 亚洲激情久久久| 日韩精品极品在线观看| 欧美另类69精品久久久久9999| 亚洲精品乱码久久久久久金桔影视| 久久视频这里只有精品| 在线精品国产欧美| 成人淫片在线看| 日韩乱码在线视频| 久久久久成人网| 亚洲欧美精品伊人久久| 色一情一乱一区二区| 亚洲女人天堂视频| 欲色天天网综合久久| 久久精品一区中文字幕| 中文字幕亚洲欧美日韩2019| 亚洲社区在线观看| 亚洲美女免费精品视频在线观看| 国产精品第2页| 日韩中文字幕欧美| 亚洲电影免费观看高清完整版在线观看| 日韩有码在线视频| 日韩中文字幕视频| 精品亚洲一区二区| 日韩成人中文字幕| 色多多国产成人永久免费网站| 欧美激情一区二区三区在线视频观看| 国产精品久久精品| 亚洲国产婷婷香蕉久久久久久| xxx一区二区| 国产一区二区三区18| 国产91在线高潮白浆在线观看| 一区二区三区四区视频| 亚洲日韩中文字幕| 欧美高清电影在线看| 国产丝袜一区二区三区| yellow中文字幕久久| 91高潮在线观看| 国产伦精品一区二区三区精品视频| 欧美一区二区大胆人体摄影专业网站| 中文在线不卡视频| 性色av一区二区三区| 欧美激情第99页| 亚洲第一区在线观看| 成人国产精品免费视频| 久久成人亚洲精品| 国产成人a亚洲精品| 国产91网红主播在线观看| yw.139尤物在线精品视频| 永久免费毛片在线播放不卡| 国产精品精品视频一区二区三区| 久久久噜噜噜久久| 精品小视频在线| 色哟哟入口国产精品| 日韩麻豆第一页| 欧美激情在线播放| 久久亚洲精品中文字幕冲田杏梨| 久久亚洲精品国产亚洲老地址| 日韩在线免费视频观看| 亚洲视频在线观看网站| 日韩高清不卡av| 亚洲国产福利在线| 91九色单男在线观看| 91亚洲精品久久久久久久久久久久| 成人网在线免费观看| 亚洲精品www久久久久久广东| 国产专区精品视频| 久久免费少妇高潮久久精品99| 欧美日韩国产麻豆| 日韩av中文在线| 亚洲男人第一网站| 欧美成人亚洲成人| 久久国产精品久久久久| 国产精品久久久久久久app| 午夜精品www| 日本一区二区三区在线播放| 欧美三级欧美成人高清www| 国产精品观看在线亚洲人成网| 97在线观看视频国产| 欧美激情精品久久久久| 国产精品a久久久久久| 欧美亚洲另类视频| 欧美—级a级欧美特级ar全黄| 91系列在线观看| 日韩成人中文电影| 欧美另类99xxxxx| 日韩av电影免费观看高清| 日韩中文字幕久久| 国产伦精品一区二区三区精品视频| 国内精品视频一区| 欧美激情2020午夜免费观看| 日韩av在线电影网| 久久免费视频在线| 国产精品久久久久久亚洲调教| 久久久亚洲影院| 91精品久久久久久久久久另类| 欧美成人网在线| 国产精品高清在线| 国产成人精彩在线视频九色| 亚洲奶大毛多的老太婆| 欧美日韩亚洲激情| 日韩精品高清在线观看| 欧美日韩综合视频| 91在线观看免费| 黄色精品一区二区| 欧美大全免费观看电视剧大泉洋| 欧美国产日韩精品| 91在线|亚洲| 2021久久精品国产99国产精品| 亚洲欧洲国产一区| 伊人伊人伊人久久| 久久久国产一区| 中文字幕日韩免费视频| 91精品国产高清久久久久久91| 色噜噜狠狠狠综合曰曰曰88av| 2019中文字幕免费视频| zzijzzij亚洲日本成熟少妇| 欧美性xxxxxx| 国产精品一区二区电影| 日韩欧美成人精品| 亚洲a成v人在线观看| 久久久久九九九九| 久久激情视频久久| 国产精品69精品一区二区三区| 欧美床上激情在线观看| 青青精品视频播放| 97精品视频在线播放|