91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

發布時間:2020-08-26 19:53:38 來源:腳本之家 閱讀:420 作者:于是乎_ 欄目:web開發

導 讀

vue3.0中,響應式數據部分棄用了 Object.defineProperty ,使用 Proxy 來代替它。本文將主要通過以下方面來分析為什么vue選擇棄用 Object.defineProperty

  • Object.defineProperty 真的無法監測數組下標的變化嗎?
  • 分析vue2.x中對數組 Observe 部分源碼
  • 對比 Object.definePropertyProxy

一、無法監控到數組下標的變化?

在一些技術博客上看到過這樣一種說法,認為 Object.defineProperty 有一個缺陷是無法監聽數組變化:

無法監控到數組下標的變化,導致直接通過數組的下標給數組設置值,不能實時響應。所以vue才設置了7個變異數組( pushpopshiftunshiftsplicesortreverse )的 hack 方法來解決問題。

Object.defineProperty 的第一個缺陷,無法監聽數組變化。 然而Vue的文檔提到了Vue是可以檢測到數組變化的,但是只有以下八種方法, vm.items[indexOfItem] = newValue 這種是無法檢測的。

這種說法是有問題的,事實上, Object.defineProperty 本身是可以監控到數組下標的變化的,只是在 Vue 的實現中,從性能/體驗的性價比考慮,放棄了這個特性。

下面我們通過一個例子來為 Object.defineProperty 正名:

function defineReactive(data, key, value) {
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: true,
 get: function defineGet() {
 console.log(`get key: ${key} value: ${value}`)
 return value
 },
 set: function defineSet(newVal) {
 console.log(`set key: ${key} value: ${newVal}`)
 value = newVal
 }
 })
}

function observe(data) {
 Object.keys(data).forEach(function(key) {
 defineReactive(data, key, data[key])
 })
}

let arr = [1, 2, 3]
observe(arr)

上面代碼對數組arr的每個屬性通過 Object.defineProperty 進行劫持,下面我們對數組arr進行操作,看看哪些行為會觸發數組的 gettersetter 方法。

1. 通過下標獲取某個元素和修改某個元素的值

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

可以看到,通過下標獲取某個元素會觸發 getter 方法, 設置某個值會觸發 setter

方法。

接下來,我們再試一下數組的一些操作方法,看看是否會觸發。

2. 數組的 push 方法

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

push 并未觸發 settergetter 方法,數組的下標可以看做是對象中的 key ,這里 push 之后相當于增加了下索引為3的元素,但是并未對新的下標進行 observe ,所以不會觸發。

3. 數組的 unshift 方法

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

我擦,發生了什么?

unshift 操作會導致原來索引為0,1,2,3的值發生變化,這就需要將原來索引為0,1,2,3的值取出來,然后重新賦值,所以取值的過程觸發了 getter ,賦值時觸發了 setter

下面我們嘗試通過索引獲取一下對應的元素:

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

只有索引為0,1,2的屬性才會觸發 getter

這里我們可以對比對象來看,arr數組初始值為[1, 2, 3],即只對索引為0,1,2執行了 observe 方法,所以無論后來數組的長度發生怎樣的變化,依然只有索引為0,1,2的元素發生變化才會觸發,其他的新增索引,就相當于對象中新增的屬性,需要再手動 observe 才可以。

4. 數組的 pop 方法

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

當移除的元素為引用為2的元素時,會觸發 getter

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

刪除了索引為2的元素后,再去修改或獲取它的值時,不會再觸發 settergetter

這和對象的處理是同樣的,數組的索引被刪除后,就相當于對象的屬性被刪除一樣,不會再去觸發 observe

到這里,我們可以簡單的總結一下結論。

Object.defineProperty 在數組中的表現和在對象中的表現是一致的,數組的索引就可以看做是對象中的 key

  • 通過索引訪問或設置對應元素的值時,可以觸發 gettersetter 方法
  • 通過 pushunshift 會增加索引,對于新增加的屬性,需要再手動初始化才能被 observe
  • 通過 popshift 刪除元素,會刪除并更新索引,也會觸發 settergetter 方法。

所以, Object.defineProperty 是有監控數組下標變化的能力的,只是vue2.x放棄了這個特性。

二、vue對數組的observe做了哪些處理?

vue的 Observer 類定義在 core/observer/index.js 中。

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

可以看到,vue的 Observer 對數組做了單獨的處理。

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

hasProto 是判斷數組的實例是否有 __proto__ 屬性,如果有 __proto__ 屬性就會執行 protoAugment 方法,將 arrayMethods 重寫到原型上。 hasProto 定義如下。

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

arrayMethods 是對數組的方法進行重寫,定義在 core/observer/array.js 中, 下面是這部分源碼的分析。

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

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

// 復制數組構造函數的原型,Array.prototype也是一個數組。
const arrayProto = Array.prototype
// 創建對象,對象的__proto__指向arrayProto,所以arrayMethods的__proto__包含數組的所有方法。
export const arrayMethods = Object.create(arrayProto)

// 下面的數組是要進行重寫的方法
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
// 遍歷methodsToPatch數組,對其中的方法進行重寫
methodsToPatch.forEach(function (method) {
 // cache original method
 const original = arrayProto[method]
 // def方法定義在lang.js文件中,是通過object.defineProperty對屬性進行重新定義。
 // 即在arrayMethods中找到我們要重寫的方法,對其進行重新定義
 def(arrayMethods, method, function mutator (...args) {
 const result = original.apply(this, args)
 const ob = this.__ob__
 let inserted
 switch (method) {
 // 上面已經分析過,對于push,unshift會新增索引,所以需要手動observe
 case 'push':
 case 'unshift':
 inserted = args
 break
 // splice方法,如果傳入了第三個參數,也會有新增索引,所以也需要手動observe
 case 'splice':
 inserted = args.slice(2)
 break
 }
 // push,unshift,splice三個方法觸發后,在這里手動observe,其他方法的變更會在當前的索引上進行更新,所以不需要再執行ob.observeArray
 if (inserted) ob.observeArray(inserted)
 // notify change
 ob.dep.notify()
 return result
 })
})

三 Object.defineProperty VS Proxy

上面已經知道 Object.defineProperty 對數組和對象的表現是一致的,那么它和 Proxy 對比存在哪些優缺點呢?

1. Object.defineProperty只能劫持對象的屬性,而Proxy是直接代理對象。

由于 Object.defineProperty 只能對屬性進行劫持,需要遍歷對象的每個屬性,如果屬性值也是對象,則需要深度遍歷。而 Proxy 直接代理對象,不需要遍歷操作。

2. Object.defineProperty對新增屬性需要手動進行Observe。

由于 Object.defineProperty 劫持的是對象的屬性,所以新增屬性時,需要重新遍歷對象,對其新增屬性再使用 Object.defineProperty 進行劫持。

也正是因為這個原因,使用vue給 data 中的數組或對象新增屬性時,需要使用 vm.$set 才能保證新增的屬性也是響應式的。

下面看一下vue的 set 方法是如何實現的, set 方法定義在 core/observer/index.js ,下面是核心代碼。

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
 // 如果target是數組,且key是有效的數組索引,會調用數組的splice方法,
 // 我們上面說過,數組的splice方法會被重寫,重寫的方法中會手動Observe
 // 所以vue的set方法,對于數組,就是直接調用重寫splice方法
 if (Array.isArray(target) && isValidArrayIndex(key)) {
 target.length = Math.max(target.length, key)
 target.splice(key, 1, val)
 return val
 }
 // 對于對象,如果key本來就是對象中的屬性,直接修改值就可以觸發更新
 if (key in target && !(key in Object.prototype)) {
 target[key] = val
 return val
 }
 // vue的響應式對象中都會添加了__ob__屬性,所以可以根據是否有__ob__屬性判斷是否為響應式對象
 const ob = (target: any).__ob__
 // 如果不是響應式對象,直接賦值
 if (!ob) {
 target[key] = val
 return val
 }
 // 調用defineReactive給數據添加了 getter 和 setter,
 // 所以vue的set方法,對于響應式的對象,就會調用defineReactive重新定義響應式對象,defineReactive 函數
 defineReactive(ob.value, key, val)
 ob.dep.notify()
 return val
}

set 方法中,對 target 是數組和對象做了分別的處理, target 是數組時,會調用重寫過的 splice 方法進行手動 Observe

對于對象,如果 key 本來就是對象的屬性,則直接修改值觸發更新,否則調用 defineReactive 方法重新定義響應式對象。

如果采用 proxy 實現, Proxy 通過 set(target, propKey, value, receiver) 攔截對象屬性的設置,是可以攔截到對象的新增屬性的。

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

不止如此, Proxy 對數組的方法也可以監測到,不需要像上面vue2.x源碼中那樣進行 hack

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

完美!!!

3. Proxy支持13種攔截操作,這是defineProperty所不具有的

get(target, propKey, receiver):攔截對象屬性的讀取,比如 proxy.fooproxy['foo']

set(target, propKey, value, receiver):攔截對象屬性的設置,比如 proxy.foo = vproxy['foo'] = v ,返回一個布爾值。

has(target, propKey):攔截 propKey in proxy 的操作,返回一個布爾值。

deleteProperty(target, propKey):攔截 delete proxy[propKey] 的操作,返回一個布爾值。

ownKeys(target):攔截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而 Object.keys() 的返回結果僅包括目標對象自身的可遍歷屬性。

getOwnPropertyDescriptor(target, propKey):攔截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回屬性的描述對象。

defineProperty(target, propKey, propDesc):攔截 Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs) ,返回一個布爾值。

preventExtensions(target):攔截 Object.preventExtensions(proxy) ,返回一個布爾值。

getPrototypeOf(target):攔截 Object.getPrototypeOf(proxy) ,返回一個對象。

isExtensible(target):攔截 Object.isExtensible(proxy) ,返回一個布爾值。

setPrototypeOf(target, proto):攔截 Object.setPrototypeOf(proxy, proto) ,返回一個布爾值。如果目標對象是函數,那么還有兩種額外操作可以攔截。

apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如 proxy(...args)proxy.call(object, ...args)proxy.apply(...)

construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如 new proxy(...args)

4. 新標準性能紅利

Proxy 作為新標準,長遠來看,JS引擎會繼續優化 Proxy ,但 gettersetter 基本不會再有針對性優化。

5. Proxy兼容性差

為什么Vue3.0使用Proxy實現數據監聽(defineProperty表示不背這個鍋)

可以看到, Proxy 對于IE瀏覽器來說簡直是災難。

并且目前并沒有一個完整支持 Proxy 所有攔截方法的Polyfill方案,有一個google編寫的proxy-polyfill 也只支持了 get,set,apply,construct 四種攔截,可以支持到IE9+和Safari 6+。

四 總結

  • Object.defineProperty 對數組和對象的表現一直,并非不能監控數組下標的變化,vue2.x中無法通過數組索引來實現響應式數據的自動更新是vue本身的設計導致的,不是 defineProperty 的鍋。
  • Object.defineProperty 和 Proxy 本質差別是,defineProperty 只能對屬性進行劫持,所以出現了需要遞歸遍歷,新增屬性需要手動 Observe 的問題。
  • Proxy 作為新標準,瀏覽器廠商勢必會對其進行持續優化,但它的兼容性也是塊硬傷,并且目前還沒有完整的polifill方案。

參考

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

https://www.jb51.net/article/171872.htm

https://zhuanlan.zhihu.com/p/35080324

http://es6.ruanyifeng.com/#docs/proxy

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宜良县| 南投市| 子洲县| 南昌县| 柯坪县| 永年县| 湾仔区| 喜德县| 大悟县| 长治县| 安西县| 英山县| 新疆| 莒南县| 上杭县| 长子县| 夏津县| 高要市| 葵青区| 河西区| 孝昌县| 宝兴县| 麻江县| 台东市| 高清| 牙克石市| 淮安市| 永安市| 抚顺市| 综艺| 襄汾县| 威海市| 泰宁县| 肥东县| 霍城县| 石门县| 张家口市| 巴塘县| 永嘉县| 贡山| 双城市|