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

defineProperty和Proxy基礎(chǔ)功能及性能對(duì)比

 更新時(shí)間:2022年08月05日 10:46:56   作者:shenyWill  
這篇文章主要為大家介紹了defineProperty和Proxy基礎(chǔ)功能及性能對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

最近公司項(xiàng)目從vue2遷移到vue3,感覺(jué)自己對(duì)Object.defineProperty和Proxy的了解還是在淺嘗輒止的地步,所以今天抽空整體對(duì)二者進(jìn)行了深入(基礎(chǔ))的了解,主要是二者的基礎(chǔ)用法,性能對(duì)比,在vue中的應(yīng)用進(jìn)行了探索,希望能夠幫助到想了解的小伙伴。

Object.defineProperty簡(jiǎn)介

首先,來(lái)看看MDN上的定義:

Object.defineProperty() 方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象。

備注: 應(yīng)當(dāng)直接在 Object 構(gòu)造器對(duì)象上調(diào)用此方法,而不是在任意一個(gè) Object 類型的實(shí)例上調(diào)用。

語(yǔ)法

Object.defineProperty(obj, prop, descriptor)

參數(shù)說(shuō)明:

obj:要定義屬性的對(duì)象。

prop:要定義或修改的屬性的名稱或Symbol。

descriptor:要定義或修改的屬性描述符。

簡(jiǎn)單示例

let person = {};
let name = 'yuanwill';
Object.defineProperty(person, 'name', {
    get() {
        return name === 'yuanwill' ? 'zhangsan' : 'lisi'
    },
    set(newVal) {
        name = newVal
    }
});
console.log(person.name); // zhangsan
person.name = 'haha';
console.log(person.name); // lisi
  • 讀取personname屬性時(shí),訪問(wèn)了get方法,第一次nameyuanwill,所以返回了zhangsan
  • 修改personname屬性時(shí),訪問(wèn)了set方法,修改了name變量的值
  • 第二次讀取personname屬性,同理,返回了lisi

仿vue使用

在vue2中,使用了Object.defineProperty來(lái)實(shí)現(xiàn)數(shù)據(jù)雙向綁定的基礎(chǔ)(具體的observe,watcher,dep等等balabala就不細(xì)說(shuō)了),我們主要仿造vue來(lái)看看怎么通過(guò)Object.defineProperty來(lái)實(shí)現(xiàn)一個(gè)對(duì)象或數(shù)組(不扯對(duì)數(shù)組方法的攔截AOP)的屬性攔截和監(jiān)聽(tīng)。

對(duì)象的攔截

準(zhǔn)備一個(gè)對(duì)象如下:

let person = {
    name: 'yuanwill',
    age: 26,
    address: {
        home: 'guangzhou',
        now: 'shenzhen'
    }
};

很容易想到,我們需要遍歷person中的key,然后對(duì)每一個(gè)key進(jìn)行轉(zhuǎn)換即可,于是很自然的寫出了下面的錯(cuò)誤示例:

Object.keys(person).forEach(key => {
    Object.defineProperty(person, key, {
        get() {
            console.log('攔截到正在獲取屬性:' + key);
            return person[key]; // ①
        },
        set(val) {
            console.log('攔截到正在修改屬性:' + key);
            person[key] = val; // ②
        }
    })
})
console.log(person.name)

運(yùn)行代碼發(fā)現(xiàn)棧溢出了,錯(cuò)誤有兩處,代碼已經(jīng)標(biāo)明:

  • get中,直接使用person[key] 會(huì)繼續(xù)調(diào)用get,導(dǎo)致死循環(huán)
  • set中同理。

所以,需要使用一個(gè)方法,來(lái)傳遞person[key] 的值。

const defineReactive = (obj, key, val) => {
    Object.defineProperty(obj, key, {
        get() {
            console.log('攔截到正在獲取屬性:' + key);
            return val;
        },
        set(newVal) {
            console.log('攔截到正在修改屬性:' + key);
            val = newVal;
        }
    })
}
const observer = obj => {
    // 如果obj不是一個(gè)對(duì)象,就沒(méi)必要包裝了
    if(typeof obj !== 'object' || !obj) {
        return;
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

實(shí)驗(yàn)一下:

observer(person);
person.name = 'haha'; // 攔截到正在修改屬性:name
console.log(person.name); // 攔截到正在獲取屬性:name, haha

可是,還有瑕疵,比如:

person.name = {
    firstName: 'yuan',
    lastName: 'will'
}; // 攔截到正在修改屬性:name
person.name.firstName = 'haha'; // 攔截到正在獲取屬性:name
console.log(person.name); // 攔截到正在獲取屬性:name

可以看到,person.name.firstName并沒(méi)有攔截到正在修改firstName屬性。原因是我們?cè)?code>set的時(shí)候,newVal可能也是一個(gè)object,所以也需要進(jìn)行observer。

修改set如下:

set(newVal) {
            if(typeof newVal === 'object') {
                observer(newVal);
            }
            console.log('攔截到正在修改屬性:' + key);
            val = newVal;
        }

當(dāng)然,還有瑕疵,比如訪問(wèn)深層對(duì)象:

console.log(person.address.home) // 攔截到正在獲取屬性:address

并沒(méi)有攔截到訪問(wèn)屬性home,所以我們還需要判斷val如果是對(duì)象也應(yīng)該再一次observer。優(yōu)化后的完整代碼如下:

const defineReactive = (obj, key, val) => {
    if(typeof val === 'object') {
        observer(val);
    }
    Object.defineProperty(obj, key, {
        get() {
            console.log('攔截到正在獲取屬性:' + key);
            return val;
        },
        set(newVal) {
            if(typeof newVal === 'object') {
                observer(newVal);
            }
            console.log('攔截到正在修改屬性:' + key);
            val = newVal;
        }
    })
}
const observer = obj => {
    // 如果obj不是一個(gè)對(duì)象,就沒(méi)必要包裝了
    if(typeof obj !== 'object' || !obj) {
        return;
    }
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
    })
}

數(shù)組的攔截

我們總說(shuō),Object.defineProperty不能攔截?cái)?shù)組,這種說(shuō)法不太準(zhǔn)確,看示例:

let list = [1,2,3,4];
observer(list);
console.log(list[0]) // 攔截到正在獲取屬性:0
list[0] = 2; // 攔截到正在修改屬性:0
list[6] = 6; // 無(wú)法攔截...
list.push(3); // 無(wú)法攔截...

可以看到,通過(guò)索引去訪問(wèn)或修改已經(jīng)存在的元素,是可以攔截到的。如果是不存在的元素,或者是通過(guò)push等方法去修改數(shù)組,則無(wú)法攔截。

正因?yàn)槿绱?,vue2在實(shí)現(xiàn)的時(shí)候,通過(guò)重寫了數(shù)組原型上的七個(gè)方法(push、pop、shift、unshift、splice、sort、reverse)來(lái)解決(具體可以看vue/src/core/observer/array.js),就不展開(kāi)了。

Proxy簡(jiǎn)介

同樣,來(lái)看看MDN上的定義:

Proxy 對(duì)象用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

語(yǔ)法

const p = new Proxy(target, handler)

參數(shù)說(shuō)明:

  • target:要使用 Proxy 包裝的目標(biāo)對(duì)象(可以是任何類型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)。
  • handler:一個(gè)通常以函數(shù)作為屬性的對(duì)象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為。

handler對(duì)象總共有13個(gè)屬性方法,具體的可以參考MDN,就不一一列舉了。

簡(jiǎn)單示例

let person = {
    name: 'yuanwill'
}
let personProxy = new Proxy(person, {
    get(target, key) {
        return target[key] === 'yuanwill' ? 'zhangsan': 'lisi'
    },
    set(target, key, val) {
        target[key] = val;
        return true;
    }
});
console.log(personProxy.name); // zhangsan
personProxy.name = 'haha';
console.log(personProxy.name); // lisi

案例及其簡(jiǎn)單,就不介紹了。

攔截的本質(zhì)

proxy的攔截,并不是“萬(wàn)事萬(wàn)物”都攔截,看MDN上面的定義,是對(duì)基本操作的攔截和自定義,那么何為基本操作呢,看下面的例子:

const person = {
    name: 'yuanwill',
    say() {
        console.log('你好呀')
    }
}
let personProxy = new Proxy(person, {
    get(target, key) {
        console.log('攔截到正在獲取屬性:' + key);
        return target[key]
    },
    set(target, key, val) {
        console.log('攔截到正在修改屬性:' + key);
        target[key] = val;
    },
    apply(target, thisArg, arguments) {
        console.log('攔截到了正在執(zhí)行的方法:' + target);
        return target.call(thisArg, ...arguments)
    }
})
console.log(personProxy.name); // 攔截到正在獲取屬性:name
personProxy.name = 'haha'; // 攔截到正在修改屬性:name
personProxy.say(); // 攔截到正在獲取屬性:say

重點(diǎn)在最后一句代碼,發(fā)現(xiàn)personProxy.say()并沒(méi)有走入apply方法中,原因就在于只攔截基本操作。

那么到底什么是基本操作呢?像上面的personProxy.name這種屬性的讀取,personProxy.name = 'haha'這種屬性的賦值就是基本操作,而personProxy.say()是由兩個(gè)基本操作(personProxy.say的讀取以及函數(shù)的調(diào)用)組成的復(fù)合操作,我們代理的對(duì)象是person,而不是person.say,所以,我們只攔截到了person.say的讀取操作。

孿生兄弟Reflect

來(lái)看看MDN上的定義:

Reflect 是一個(gè)內(nèi)置的對(duì)象,它提供攔截 JavaScript 操作的方法。這些方法與proxy handlers (en-US)的方法相同。Reflect不是一個(gè)函數(shù)對(duì)象,因此它是不可構(gòu)造的。

換句話說(shuō),Reflet對(duì)象的方法和proxy的攔截器(第二個(gè)入?yún)andler)的方法完全一致,因此也有13個(gè)方法,就不一一列舉了。

Reflect的作用也及其簡(jiǎn)單,可以參考MDN上。

那么,為什么我們需要Reflect呢,來(lái)看下面的例子:

const person = {
    name: 'yuanwill',
    get firstName() {
        return this.name;
    }
};
const personProxy = new Proxy(person, {
    get(target, key) {
        console.log('攔截到正在獲取屬性:' + key);
        return target[key]
    },
    set(target, key, val) {
        console.log('攔截到正在修改屬性:' + key);
        target[key] = val;
    }
});
console.log(personProxy.firstName); // 攔截到正在獲取屬性:firstName

按照我們的理解,應(yīng)該還需要攔截到name屬性,因?yàn)槲覀冊(cè)?code>firstName中返回的是name屬性,那么為什么沒(méi)有攔截到呢?關(guān)鍵在于this指向問(wèn)題,personProxy.firstName會(huì)被get攔截,然后返回target[key],這里的target就是personkey就是firstName,所以這個(gè)時(shí)候的this.name就是person.name,而我們的代理對(duì)象是personProxy,所以訪問(wèn)name屬性就不會(huì)被攔截了。

那這個(gè)時(shí)候,Reflect就派上用場(chǎng)了:

const personProxy = new Proxy(person, {
    get(target, key, receiver) {
        console.log('攔截到正在獲取屬性:' + key);
        return Reflect.get(target, key, receiver);
    },
    set(target, key, val, receiver) {
        console.log('攔截到正在修改屬性:' + key);
        return Reflect.set(target, key, val, receiver);
    }
});

這個(gè)時(shí)候,就能攔截到了。原因在于,Reflect.get中的第三個(gè)參數(shù)receiver作用就是改變this的指向,MDN描述如下:

如果target對(duì)象中指定了getter,receiver則為getter調(diào)用時(shí)的this值。

仿vue使用

對(duì)象的攔截

還是使用上面的對(duì)象:

let person = {
    name: 'yuanwill',
    age: 26,
    address: {
        home: 'guangzhou',
        now: 'shenzhen'
    }
};

我們很自然就能寫出如下代碼:

const observer = obj => {
    // 如果obj不是一個(gè)對(duì)象,就沒(méi)必要包裝了
    if(typeof obj !== 'object' || !obj) {
        return obj;
    }
    const proxyConfig = {
        get(target, key, receiver) {
            console.log('攔截到正在獲取屬性:' + key);
            return Reflect.get(target, key, receiver)
        },
        set(target, key, val, receiver) {
            console.log('攔截到正在修改屬性:' + key);
            return Reflect.set(target, key, val, receiver);;
        }
    };
    const observed = new Proxy(obj, proxyConfig);
    return observed;
}

測(cè)試一下:

const personProxy = observer(person);
personProxy.name = 'haha'; // 攔截到正在修改屬性:name
console.log(personProxy.name); // 攔截到正在獲取屬性:name

當(dāng)然,也有瑕疵:

personProxy.name = {
    firstName: 'yuan',
    lastName: 'will'
}; // 攔截到正在修改屬性:name
personProxy.name.firstName = 'haha'; // 攔截到正在獲取屬性:name
console.log(personProxy.name); // 攔截到正在獲取屬性:name

可以看到,person.name.firstName依然沒(méi)有攔截到正在修改firstName屬性。原因在于,get返回的可能是個(gè)對(duì)象,我們需要對(duì)這個(gè)對(duì)象再次代理,所以修改如下:

const observer = obj => {
    // 如果obj不是一個(gè)對(duì)象,就沒(méi)必要包裝了
    if(typeof obj !== 'object' || !obj) {
        return obj;
    }
    const proxyConfig = {
        get(target, key, receiver) {
            console.log('攔截到正在獲取屬性:' + key);
            const result = Reflect.get(target, key, receiver);
            return observer(result);
        },
        set(target, key, val, receiver) {
            console.log('攔截到正在修改屬性:' + key);
            return Reflect.set(target, key, val, receiver);;
        }
    };
    const observed = new Proxy(obj, proxyConfig);
    return observed;
}

仔細(xì)分析上面的代碼,我們?cè)?code>get的時(shí)候,才去判斷了獲取的值是不是一個(gè)對(duì)象,而Object.defineProperty是最開(kāi)始就循環(huán)遍歷,對(duì)每個(gè)屬性進(jìn)行代理,所以,這樣性能就提升了。同時(shí),我們獲取personProxy.address.home也能攔截到home屬性了(想想就知道為啥了)。

數(shù)組的攔截

let list = [1,2,3,4];
let listProxy = observer(list);
console.log(listProxy[0]) // 攔截到正在獲取屬性:0
listProxy[0] = 2; // 攔截到正在修改屬性:0
listProxy[6] = 6; // 攔截到正在修改屬性:6
/**
 * 攔截到正在獲取屬性:push
 * 攔截到正在獲取屬性:length
 * 攔截到正在修改屬性:7
 * 攔截到正在修改屬性:length
 */
listProxy.push(3);

可以看到,proxy天然的解決了數(shù)組的相關(guān)問(wèn)題。

最后

Object.definePropertyProxy的相關(guān)基礎(chǔ)就介紹完了,文章只是講解了比較基礎(chǔ)的功能,學(xué)無(wú)止境,沒(méi)辦法了,慢慢來(lái)把~~~

更多關(guān)于defineProperty Proxy基礎(chǔ)功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue打包使用Nginx代理解決跨域問(wèn)題

    vue打包使用Nginx代理解決跨域問(wèn)題

    這篇文章主要介紹了vue打包使用Nginx代理解決跨域問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • Vue自定義組件的四種方式示例詳解

    Vue自定義組件的四種方式示例詳解

    本文給大家分享vue自定義組件的四種方式,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2020-02-02
  • Vue中v-for的數(shù)據(jù)分組實(shí)例

    Vue中v-for的數(shù)據(jù)分組實(shí)例

    下面小編就為大家分享一篇Vue中v-for的數(shù)據(jù)分組實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • vue ajax 攔截原理與實(shí)現(xiàn)方法示例

    vue ajax 攔截原理與實(shí)現(xiàn)方法示例

    這篇文章主要介紹了vue ajax 攔截原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了vue.js基于ajax攔截實(shí)現(xiàn)無(wú)刷新登錄的相關(guān)原理與操作技巧,需要的朋友可以參考下
    2019-11-11
  • vue3-vite安裝后main.ts文件和tsconfig.app.json文件報(bào)錯(cuò)解決辦法

    vue3-vite安裝后main.ts文件和tsconfig.app.json文件報(bào)錯(cuò)解決辦法

    Vue.js是一個(gè)流行的JavaScript框架,它可以幫助開(kāi)發(fā)者構(gòu)建交互式Web應(yīng)用程序,這篇文章主要給大家介紹了關(guān)于vue3-vite安裝后main.ts文件和tsconfig.app.json文件報(bào)錯(cuò)解決辦法,需要的朋友可以參考下
    2023-12-12
  • VUE3使用JSON編輯器方式

    VUE3使用JSON編輯器方式

    這篇文章主要介紹了VUE3使用JSON編輯器方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • Vue中watch使用方法詳解

    Vue中watch使用方法詳解

    watch就是當(dāng)值第一次綁定的時(shí)候,是不會(huì)執(zhí)行監(jiān)聽(tīng)函數(shù)的,只有值誕生改變才會(huì)執(zhí)行。如果需要在第一次綁定的時(shí)候也執(zhí)行函數(shù),則需要用到immediate屬性,比如當(dāng)父組件向子組件動(dòng)態(tài)傳值時(shí),子組件props首次獲取到父組件傳來(lái)的No認(rèn)知時(shí),也需要執(zhí)行函數(shù)
    2023-01-01
  • vue項(xiàng)目中仿element-ui彈框效果的實(shí)例代碼

    vue項(xiàng)目中仿element-ui彈框效果的實(shí)例代碼

    這篇文章主要介紹了vue項(xiàng)目中仿element-ui彈框效果的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • vue中axios的使用詳解

    vue中axios的使用詳解

    這篇文章主要為大家詳細(xì)介紹了vue中axios的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • rollup3.x+vue2打包組件的實(shí)現(xiàn)

    rollup3.x+vue2打包組件的實(shí)現(xiàn)

    本文主要介紹了rollup3.x+vue2打包組件的實(shí)現(xiàn),詳細(xì)的介紹了打包會(huì)存在的問(wèn)題,包版本的問(wèn)題,babel 轉(zhuǎn)換jsx等問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-03-03

最新評(píng)論