Proxy
攔截函數(shù)
get
- target 代理的對象
- propkeyName 讀取的屬性名稱
- receiver Proxy 對象本身
如果一個屬性不可配置(configurable)且不可寫(writable)构眯,則 Proxy 不能修改該屬性伍掀,否則通過 Proxy 對象訪問該屬性會報錯括饶。
var proxy = new Proxy({a:1},{
get:function(target,propkeyname,resiver){
return {target,propkeyname,resiver}
}
})
console.log(proxy.a)
/**
* {
* target:{a:1},
* propkeyName:"a",
* resiver: Proxy({a:1}) // 這指的就是 proxy 這個對象
* }
*/
dom 生成器
var dom = new Proxy(
{},
{
get: function (target, elementType,receiver) {
return function (attrs, ...children) {
const ele = document.createElement(elementType);
for (let attr of Object.keys(attrs)) {
ele.setAttribute(attr, attrs[attr]);
}
for (let child of children) {
if (typeof child === "string") {
child = document.createTextNode(child);
}
ele.append(child);
}
return ele;
};
},
}
);
var ele = dom.div(
{},
"Hello,world!",
dom.a({ href: "www.baidu.com" }, "百度")
);
console.log(ele);
// <div><a href="www.baidu.com">百度</a></div>
set
- target 代理的對象
- propkeyName 設(shè)置屬性的名稱
- propkeyValue 設(shè)置屬性的值
- receiver Proxy 對象本身
嚴(yán)格模式下:需要返回 true ,返回 false 或不返回會報錯
Object.defineProperty 不會觸發(fā)這個調(diào)用
如果一個屬性不可配置(configurable)且不可寫(writable)入录,則 Proxy 不能修改該屬性,否則通過 Proxy 對象不起作用晒夹。
var handler = {
set(target,propkeyName,propValue){
console.log("-------> set");
console.log("target",target);
console.log("propkeyName",propkeyName);
console.log("propValue",propValue);
target[propkeyName] = propValue;
console.log("<------- set");
},
};
var target = {a:1};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'
Object.defineProperty(proxy,'foo2',{ value: "bar2", writable: true, enumerable: true, configurable: true })
console.log(proxy);
apply
- target 代理的對象
- ctx 函數(shù)上下文中的 this 指針
- args 函數(shù)接收的參數(shù)
若代理對象是一個函數(shù)裆馒,那么可以攔截執(zhí)行調(diào)用
攔截調(diào)用
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
// 使用 Reflect 調(diào)用也會被攔截
Reflect.apply(proxy, null, [9, 10]) // 38
has
- target 代理的對象
- propkeyName 查詢屬性的名稱
如果原對象不可配置或者禁止擴展,這時 has()攔截會報錯丐怯。
不檢查屬性來自自身還是原型喷好,只對 in 關(guān)鍵字生效,且對 for_in 無效
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p // TypeError is thrown
constructor
- target 代理的函數(shù)
- args 構(gòu)造函數(shù)參數(shù)
- newTarget Proxy 對象
構(gòu)造函數(shù)攔截器必須返回一個對象
construct()方法中的 this 指向的是 handler读跷,而不是實例對象梗搅。
const handler = {
construct: function(target, args) {
console.log(this === handler);
return new target(...args);
}
}
let p = new Proxy(function () {}, handler);
new p() // true
deleteProperty
- target 代理對象
- propkeyName 屬性名稱
需要在該函數(shù)中將代理對象中對應(yīng)值刪除。需要返回 true 表示刪除成功,false/報錯表示刪除失敗
目標(biāo)對象自身的不可配置(configurable)的屬性无切,不能被 deleteProperty 方法刪除荡短,否則報錯。
const handle = {
deleteProperty:function(target,keyName){
console.log("target",target);
console.log("keyName",keyName);
delete target[keyName];
return true
}
}
const proxy = new Proxy({a:1},handle);
delete proxy.a;
console.log(proxy);
defineProperty
- target 代理對象
- propkeyName 屬性值名稱
- descriptor 屬性描述
當(dāng) set 與 defineProperty 同時存在的時候哆键,直接賦值只會調(diào)用 set掘托,但如果不存在 set 或者通過 Object.defineProperty 還是會被調(diào)用
返回值為 true,表示成功,如果無返回值會報錯
var handler = {
set(target,propkeyName,propValue){
console.log("-------> set");
console.log("target",target);
console.log("propkeyName",propkeyName);
console.log("propValue",propValue);
target[propkeyName] = propValue;
console.log("<------- set");
},
defineProperty (target, key, descriptor) {
console.log("-------> defineProperty");
console.log("target",target);
console.log("key",key);
console.log("descriptor",descriptor);
console.log("<------- defineProperty");
Object.defineProperty(target,key,descriptor);
return true;
}
};
var target = {a:1};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'
Object.defineProperty(proxy,'b',{ value: "bar", writable: true, enumerable: true, configurable: true })
console.log(proxy);
getOwnPropertyDescriptor
- target 代理對象
- propkeyName 屬性值名稱
必須返回一個屬性描述對象或者 undefinde籍嘹,否則會報錯
var handler = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }
getPrototypeOf()
- Object.prototype.__proto__
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
攔截以上方法,返回值必須是對象或是 null闪盔,否則會報錯。
另外辱士,如果目標(biāo)對象不可擴展(non-extensible)泪掀, getPrototypeOf()方法必須返回目標(biāo)對象的原型對象。
var proto = {};
var p = new Proxy({a:1}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(p) === proto // true
isExtensible
攔截 Object.isExtensible()操作识补。該函數(shù)必須返回布爾值族淮,其他值會被自動轉(zhuǎn)換辫红。
且返回值必須與目標(biāo)對象的 isExtensible 屬性保持一致凭涂,否則就會拋出錯誤
var target = {};
object.preventExtensions(target);
console.log(Object.isExtensions(target)); // false
var p = new Proxy(target, {
isExtensible: function(target) {
console.log("called");
return true;
}
});
Object.isExtensible(p) // 與 target.isExtensions 值不一致 會報錯
ownKeys
方法返回的數(shù)組成員,只能是字符串或 Symbol 值贴妻。如果有其他類型的值切油,或者返回的根本不是數(shù)組,就會報錯名惩。
如果目標(biāo)對象自身包含不可配置的屬性(configurable)澎胡,則該屬性必須被 ownKeys()方法返回,否則報錯娩鹉。
如果目標(biāo)對象是不可擴展的(non-extensible)攻谁,這時 ownKeys()方法返回的數(shù)組之中,必須包含原對象的所有屬性弯予,且不能包含多余的屬性戚宦,否則報錯。
攔截以下調(diào)用
-
Object.getOwnPropertyNames()
- 會自動過濾 屬性名為 Symbol 的屬性锈嫩,即使攔截函數(shù)中返回
-
Object.getOwnPropertySymbols()
- 會自動過濾 非屬性名為 Symbol 的屬性受楼,即使攔截函數(shù)中返回
-
Object.keys()
- 會自動過濾 不存在、不可遍歷呼寸、屬性名為 Symbol 的屬性艳汽,即使攔截函數(shù)中返回
-
for...in 循環(huán)
- 會自動過濾 不存在、不可遍歷对雪、屬性名為 Symbol 的屬性河狐,即使攔截函數(shù)中返回
let target = {
a: 1,
b: 2,
c: 3,
[Symbol.for('secret')]: '4',
};
Object.defineProperty(target, 'key', {
enumerable: false,
configurable: true,
writable: true,
value: 'static'
});
let handler = {
ownKeys(target) {
return ['a', 'd', Symbol.for('secret'), 'key'];
}
};
let proxy = new Proxy(target, handler);
console.log( Object.keys(proxy));
// Array ["a"]
console.log( Object.getOwnPropertyNames(proxy));
// Array ["a", "d", "key"]
console.log( Object.getOwnPropertySymbols(proxy));
// Array ["a", "d", "key"]
for(let key in proxy){
console.log(key);
}
// “a”
preventExtensions
preventExtensions()方法攔截 Object.preventExtensions()。該方法必須返回一個布爾值,否則會被自動轉(zhuǎn)為布爾值甚牲。
只有目標(biāo)對象不可擴展時(即 Object.isExtensible(proxy)為 false)义郑,proxy.preventExtensions 才能返回 true,否則會報錯丈钙。
為解決這個問題非驮,可以在攔截函數(shù)中,將目標(biāo)對象禁止掉擴展
var target = {};
var proxy = new Proxy(target, {
preventExtensions: function(target) {
return true;
}
});
Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
// proxy 的攔截 與 Object.isExtensions(proxy)[值為 false] 不一致 會報錯
setPrototypeOf
setPrototypeOf()方法主要用來攔截 Object.setPrototypeOf()方法雏赦。
該方法只能返回布爾值劫笙,否則會被自動轉(zhuǎn)為布爾值。另外星岗,如果目標(biāo)對象不可擴展(non-extensible)填大,setPrototypeOf()方法不得改變目標(biāo)對象的原型。
var handler = {
setPrototypeOf (target, proto) {
throw new Error('Changing the prototype is forbidden');
}
};
var proto = {};
var target = function () {}; // 函數(shù)或?qū)ο蠖际强梢缘? var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
Proxy.revocable
返回一個可取消的 Proxy 實例
Proxy.revocable()方法返回一個對象俏橘,該對象的 proxy 屬性是 Proxy 實例允华,revoke 屬性是一個函數(shù),可以取消 Proxy 實例寥掐。上面代碼中靴寂,當(dāng)執(zhí)行 revoke 函數(shù)之后,再訪問 Proxy 實例召耘,就會拋出一個錯誤百炬。
Proxy.revocable()的一個使用場景是,目標(biāo)對象不允許直接訪問污它,必須通過代理訪問剖踊,一旦訪問結(jié)束,就收回代理權(quán)衫贬,不允許再次訪問德澈。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
this 問題
在 Proxy 代理的情況下,目標(biāo)對象內(nèi)部的 this 關(guān)鍵字會指向 Proxy 代理固惯,也就是 handle梆造。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true