欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

為什么Vue3.0使用Proxy實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽(defineProperty表示不背這個(gè)鍋)

 更新時(shí)間:2019年10月14日 10:03:08   作者:于是乎_  
這篇文章主要介紹了為什么Vue3.0使用Proxy實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽?defineProperty表示不背這個(gè)鍋,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

導(dǎo) 讀

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

  • Object.defineProperty 真的無法監(jiān)測數(shù)組下標(biāo)的變化嗎?
  • 分析vue2.x中對(duì)數(shù)組 Observe 部分源碼
  • 對(duì)比 Object.definePropertyProxy

一、無法監(jiān)控到數(shù)組下標(biāo)的變化?

在一些技術(shù)博客上看到過這樣一種說法,認(rèn)為 Object.defineProperty 有一個(gè)缺陷是無法監(jiān)聽數(shù)組變化:

無法監(jiān)控到數(shù)組下標(biāo)的變化,導(dǎo)致直接通過數(shù)組的下標(biāo)給數(shù)組設(shè)置值,不能實(shí)時(shí)響應(yīng)。所以vue才設(shè)置了7個(gè)變異數(shù)組( push 、 pop 、 shift 、 unshift 、 splice 、 sort 、 reverse )的 hack 方法來解決問題。

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

這種說法是有問題的,事實(shí)上, Object.defineProperty 本身是可以監(jiān)控到數(shù)組下標(biāo)的變化的,只是在 Vue 的實(shí)現(xiàn)中,從性能/體驗(yàn)的性價(jià)比考慮,放棄了這個(gè)特性。

下面我們通過一個(gè)例子來為 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)

上面代碼對(duì)數(shù)組arr的每個(gè)屬性通過 Object.defineProperty 進(jìn)行劫持,下面我們對(duì)數(shù)組arr進(jìn)行操作,看看哪些行為會(huì)觸發(fā)數(shù)組的 gettersetter 方法。

1. 通過下標(biāo)獲取某個(gè)元素和修改某個(gè)元素的值

可以看到,通過下標(biāo)獲取某個(gè)元素會(huì)觸發(fā) getter 方法, 設(shè)置某個(gè)值會(huì)觸發(fā) setter

方法。

接下來,我們?cè)僭囈幌聰?shù)組的一些操作方法,看看是否會(huì)觸發(fā)。

2. 數(shù)組的 push 方法

push 并未觸發(fā) settergetter 方法,數(shù)組的下標(biāo)可以看做是對(duì)象中的 key ,這里 push 之后相當(dāng)于增加了下索引為3的元素,但是并未對(duì)新的下標(biāo)進(jìn)行 observe ,所以不會(huì)觸發(fā)。

3. 數(shù)組的 unshift 方法

我擦,發(fā)生了什么?

unshift 操作會(huì)導(dǎo)致原來索引為0,1,2,3的值發(fā)生變化,這就需要將原來索引為0,1,2,3的值取出來,然后重新賦值,所以取值的過程觸發(fā)了 getter ,賦值時(shí)觸發(fā)了 setter

下面我們嘗試通過索引獲取一下對(duì)應(yīng)的元素:

只有索引為0,1,2的屬性才會(huì)觸發(fā) getter 。

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

4. 數(shù)組的 pop 方法

當(dāng)移除的元素為引用為2的元素時(shí),會(huì)觸發(fā) getter 。

刪除了索引為2的元素后,再去修改或獲取它的值時(shí),不會(huì)再觸發(fā) settergetter 。

這和對(duì)象的處理是同樣的,數(shù)組的索引被刪除后,就相當(dāng)于對(duì)象的屬性被刪除一樣,不會(huì)再去觸發(fā) observe 。

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

Object.defineProperty 在數(shù)組中的表現(xiàn)和在對(duì)象中的表現(xiàn)是一致的,數(shù)組的索引就可以看做是對(duì)象中的 key 。

  • 通過索引訪問或設(shè)置對(duì)應(yīng)元素的值時(shí),可以觸發(fā) gettersetter 方法
  • 通過 pushunshift 會(huì)增加索引,對(duì)于新增加的屬性,需要再手動(dòng)初始化才能被 observe
  • 通過 popshift 刪除元素,會(huì)刪除并更新索引,也會(huì)觸發(fā) settergetter 方法。

所以, Object.defineProperty 是有監(jiān)控?cái)?shù)組下標(biāo)變化的能力的,只是vue2.x放棄了這個(gè)特性。

二、vue對(duì)數(shù)組的observe做了哪些處理?

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

可以看到,vue的 Observer 對(duì)數(shù)組做了單獨(dú)的處理。

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

arrayMethods 是對(duì)數(shù)組的方法進(jìn)行重寫,定義在 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'

// 復(fù)制數(shù)組構(gòu)造函數(shù)的原型,Array.prototype也是一個(gè)數(shù)組。
const arrayProto = Array.prototype
// 創(chuàng)建對(duì)象,對(duì)象的__proto__指向arrayProto,所以arrayMethods的__proto__包含數(shù)組的所有方法。
export const arrayMethods = Object.create(arrayProto)

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

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

三 Object.defineProperty VS Proxy

上面已經(jīng)知道 Object.defineProperty 對(duì)數(shù)組和對(duì)象的表現(xiàn)是一致的,那么它和 Proxy 對(duì)比存在哪些優(yōu)缺點(diǎn)呢?

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

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

2. Object.defineProperty對(duì)新增屬性需要手動(dòng)進(jìn)行Observe。

由于 Object.defineProperty 劫持的是對(duì)象的屬性,所以新增屬性時(shí),需要重新遍歷對(duì)象,對(duì)其新增屬性再使用 Object.defineProperty 進(jìn)行劫持。

也正是因?yàn)檫@個(gè)原因,使用vue給 data 中的數(shù)組或?qū)ο笮略鰧傩詴r(shí),需要使用 vm.$set 才能保證新增的屬性也是響應(yīng)式的。

下面看一下vue的 set 方法是如何實(shí)現(xiàn)的, 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是數(shù)組,且key是有效的數(shù)組索引,會(huì)調(diào)用數(shù)組的splice方法,
 // 我們上面說過,數(shù)組的splice方法會(huì)被重寫,重寫的方法中會(huì)手動(dòng)Observe
 // 所以vue的set方法,對(duì)于數(shù)組,就是直接調(diào)用重寫splice方法
 if (Array.isArray(target) && isValidArrayIndex(key)) {
 target.length = Math.max(target.length, key)
 target.splice(key, 1, val)
 return val
 }
 // 對(duì)于對(duì)象,如果key本來就是對(duì)象中的屬性,直接修改值就可以觸發(fā)更新
 if (key in target && !(key in Object.prototype)) {
 target[key] = val
 return val
 }
 // vue的響應(yīng)式對(duì)象中都會(huì)添加了__ob__屬性,所以可以根據(jù)是否有__ob__屬性判斷是否為響應(yīng)式對(duì)象
 const ob = (target: any).__ob__
 // 如果不是響應(yīng)式對(duì)象,直接賦值
 if (!ob) {
 target[key] = val
 return val
 }
 // 調(diào)用defineReactive給數(shù)據(jù)添加了 getter 和 setter,
 // 所以vue的set方法,對(duì)于響應(yīng)式的對(duì)象,就會(huì)調(diào)用defineReactive重新定義響應(yīng)式對(duì)象,defineReactive 函數(shù)
 defineReactive(ob.value, key, val)
 ob.dep.notify()
 return val
}

set 方法中,對(duì) target 是數(shù)組和對(duì)象做了分別的處理, target 是數(shù)組時(shí),會(huì)調(diào)用重寫過的 splice 方法進(jìn)行手動(dòng) Observe

對(duì)于對(duì)象,如果 key 本來就是對(duì)象的屬性,則直接修改值觸發(fā)更新,否則調(diào)用 defineReactive 方法重新定義響應(yīng)式對(duì)象。

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

不止如此, Proxy 對(duì)數(shù)組的方法也可以監(jiān)測到,不需要像上面vue2.x源碼中那樣進(jìn)行 hack 。

完美!??!

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

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

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

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

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

ownKeys(target):攔截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、 Object.keys(proxy) 、 for...in 循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名,而 Object.keys() 的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性。

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

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

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

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

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

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

apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如 proxy(...args)proxy.call(object, ...args) 、 proxy.apply(...)

construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如 new proxy(...args)

4. 新標(biāo)準(zhǔn)性能紅利

Proxy 作為新標(biāo)準(zhǔn),長遠(yuǎn)來看,JS引擎會(huì)繼續(xù)優(yōu)化 Proxy ,但 gettersetter 基本不會(huì)再有針對(duì)性優(yōu)化。

5. Proxy兼容性差

可以看到, Proxy 對(duì)于IE瀏覽器來說簡直是災(zāi)難。

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

四 總結(jié)

  • Object.defineProperty 對(duì)數(shù)組和對(duì)象的表現(xiàn)一直,并非不能監(jiān)控?cái)?shù)組下標(biāo)的變化,vue2.x中無法通過數(shù)組索引來實(shí)現(xiàn)響應(yīng)式數(shù)據(jù)的自動(dòng)更新是vue本身的設(shè)計(jì)導(dǎo)致的,不是 defineProperty 的鍋。
  • Object.defineProperty 和 Proxy 本質(zhì)差別是,defineProperty 只能對(duì)屬性進(jìn)行劫持,所以出現(xiàn)了需要遞歸遍歷,新增屬性需要手動(dòng) Observe 的問題。
  • Proxy 作為新標(biāo)準(zhǔn),瀏覽器廠商勢(shì)必會(huì)對(duì)其進(jìn)行持續(xù)優(yōu)化,但它的兼容性也是塊硬傷,并且目前還沒有完整的polifill方案。

參考

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

http://www.dbjr.com.cn/article/171872.htm

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

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

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論