• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

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

    來源:懂視網 責編:小采 時間:2020-11-27 22:02:58
    文檔

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

    Vue源碼解析之數組變異的實現:力有不逮的對象 眾所周知,在 Vue 中,直接修改對象屬性的值無法觸發響應式。當你直接修改了對象屬性的值,你會發現,只有數據改了,但是頁面內容并沒有改變。 這是什么原因? 原因在于: Vue 的響應式系統是基于Object.defineProperty這個方法的,該
    推薦度:
    導讀Vue源碼解析之數組變異的實現:力有不逮的對象 眾所周知,在 Vue 中,直接修改對象屬性的值無法觸發響應式。當你直接修改了對象屬性的值,你會發現,只有數據改了,但是頁面內容并沒有改變。 這是什么原因? 原因在于: Vue 的響應式系統是基于Object.defineProperty這個方法的,該

    力有不逮的對象

    眾所周知,在 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 = A
    A = 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 = []
    // 通過隱式原型繼承arrayMethods
    arr.__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.prototype
    export 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 的變異數組從本質上是來說是一種裝飾器模式,通過學習它的原理,我們在實際工作中可以輕松處理這類保持原有功能不變的前提下對其進行功能拓展的需求。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

    聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

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

    Vue源碼解析之數組變異的實現:力有不逮的對象 眾所周知,在 Vue 中,直接修改對象屬性的值無法觸發響應式。當你直接修改了對象屬性的值,你會發現,只有數據改了,但是頁面內容并沒有改變。 這是什么原因? 原因在于: Vue 的響應式系統是基于Object.defineProperty這個方法的,該
    推薦度:
    標簽: VUE 數組 vue的
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 精品亚洲A∨无码一区二区三区| 国产精品www| 国产在线精品一区二区不卡| 四虎影视永久在线精品免费| 成人国产精品一区二区视频 | 人妻少妇精品视中文字幕国语| 99亚洲精品视频| 国产精品1区2区| 国产亚洲欧美精品久久久| 亚洲精品国产成人片| 久久99精品久久久久久9蜜桃 | 91大神精品全国在线观看| 成人国产精品999视频| 国产精品无码av在线播放| 人人妻人人澡人人爽人人精品97| 亚洲精品国产高清嫩草影院| 久久精品国产清自在天天线| 国产成人毛片亚洲精品| 青青青国产精品国产精品久久久久 | 国产中文在线亚洲精品官网| 国产成人1024精品免费| 亚洲国产精品久久久久久| 久久国产精品国产自线拍免费| 国产成人精品综合网站| 国产精品成熟老女人视频| 国产三级久久久精品麻豆三级 | 一本久久a久久精品亚洲| 欧美在线精品一区二区三区| 麻豆精品国产自产在线观看一区| 久久亚洲精品无码播放| 欧美午夜精品一区二区三区91| 久久996热精品xxxx| 久热这里只有精品99国产6| 精品久久久久久久中文字幕| 久久精品国产一区二区| 老湿亚洲永久精品ww47香蕉图片| 久久99精品久久久久久9蜜桃 | 四虎精品亚洲一区二区三区| 亚洲国产精品自产在线播放| 亚洲国产高清精品线久久 | 成人国内精品久久久久影院|