js 中好用的代理 Proxy

const p = new Proxy(target, handler)

參數(shù)

target
要使用 Proxy 包裝的目標(biāo)對象(可以是任何類型的對象粥鞋,包括原生數(shù)組缘挽,函數(shù),甚至另一個代理)呻粹。
handler
一個通常以函數(shù)作為屬性的對象到踏,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時代理 p 的行為。
其中 handler的寫法要注意


handler 對象的方法

handler 對象是一個容納一批特定屬性的占位符對象尚猿。它包含有 Proxy 的各個捕獲器(trap)。

所有的捕捉器是可選的楣富。如果沒有定義某個捕捉器凿掂,那么就會保留源對象的默認(rèn)行為。


  • handler.getPrototypeOf()
    Object.getPrototypeOf方法的捕捉器。在讀取代理對象的原型時觸發(fā)該操作庄萎,比如在執(zhí)行 Object.getPrototypeOf(proxy)時踪少。

\color{rgb(106 90 205)}{demo}

const monster1 = {
  eyeCount: 4
};

const monsterPrototype = {
  eyeCount: 2
};

const handler = {
  getPrototypeOf(target) {
    return monsterPrototype;
  }
};

const proxy1 = new Proxy(monster1, handler);

console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
// 輸出: true

console.log(Object.getPrototypeOf(proxy1).eyeCount);
// 輸出: 2

  • handler.setPrototypeOf()
    Object.setPrototypeOf方法的捕捉器。在設(shè)置代理對象的原型時觸發(fā)該操作糠涛,比如在執(zhí)行 Object.setPrototypeOf(proxy, null)時援奢。

\color{rgb(106 90 205)}{demo}

/* 
target
被攔截目標(biāo)對象.
prototype
對象新原型或為null
如果成功修改了[[Prototype]], setPrototypeOf 方法返回 true,否則返回 false.
*/
// 1. ------------------------------------------------------------------------------
var handlerReturnsFalse = {
    setPrototypeOf(target, newProto) {
        return false;
    }
};
var newProto = {}, target = {};
var p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // return false

// 2. ------------------------------------------------------------------------------
var handlerThrows = {
    setPrototypeOf(target, newProto) {
        throw new Error('custom error');
    }
}; 
var newProto = {}, target = {};
var p2 = new Proxy(target, handlerThrows);
Object.setPrototypeOf(p2, newProto); // throws new Error("custom error")
Reflect.setPrototypeOf(p2, newProto); // throws new Error("custom error")

  • handler.isExtensible()
    Object.isExtensible方法的捕捉器。在判斷一個代理對象是否是可擴展時觸發(fā)該操作忍捡,比如在執(zhí)行 Object.isExtensible(proxy)時集漾。

\color{rgb(106 90 205)}{demo}

const monster1 = {
  canEvolve: true
};

const handler1 = {
  isExtensible(target) {
    return Reflect.isExtensible(target);
  },
  preventExtensions(target) {
    target.canEvolve = false;
    return Reflect.preventExtensions(target);
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(Object.isExtensible(proxy1));
// 輸出: true

console.log(monster1.canEvolve);
// 輸出: true

Object.preventExtensions(proxy1);

console.log(Object.isExtensible(proxy1));
// 輸出: false

console.log(monster1.canEvolve);
// 輸出: false

  • handler.preventExtensions()
    Object.preventExtensions 方法的捕捉器。在讓一個代理對象不可擴展時觸發(fā)該操作砸脊,比如在執(zhí)行 Object.preventExtensions(proxy)時具篇。

\color{rgb(106 90 205)}{demo}

const monster1 = {
  canEvolve: true
};

const handler1 = {
  preventExtensions(target) {
    target.canEvolve = false;
    Object.preventExtensions(target);
    return true;
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(monster1.canEvolve);
// 輸出: true

Object.preventExtensions(proxy1);

console.log(monster1.canEvolve);
// 輸出: false


  • handler.getOwnPropertyDescriptor()
    Object.getOwnPropertyDescriptor方法的捕捉器。在獲取代理對象某個屬性的屬性描述時觸發(fā)該操作凌埂,比如在執(zhí)行 Object.getOwnPropertyDescriptor(proxy, "foo")時驱显。

\color{rgb(106 90 205)}{demo}

/*
target
目標(biāo)對象。
prop
返回屬性名稱的描述瞳抓。
這個攔截器可以攔截這些操作:

*   Object.getOwnPropertyDescriptor()
*   Reflect.getOwnPropertyDescriptor()
*/
getOwnPropertyDescriptor 方法必須返回一個 object 或 undefined埃疫。

var p = new Proxy({ a: 20}, {
  getOwnPropertyDescriptor: function(target, prop) {
    console.log('called: ' + prop);
    return { configurable: true, enumerable: true, value: 10 };
  }
});

console.log(Object.getOwnPropertyDescriptor(p, 'a').value); 
// "called: a"
// 10
/*
如果下列不變量被違反,代理將拋出一個 TypeError:
*   getOwnPropertyDescriptor 必須返回一個 object 或 undefined孩哑。
*   如果屬性作為目標(biāo)對象的不可配置的屬性存在栓霜,則該屬性無法報告為不存在。
*   如果屬性作為目標(biāo)對象的屬性存在臭笆,并且目標(biāo)對象不可擴展叙淌,則該屬性無法報告為不存在。
*   如果屬性不存在作為目標(biāo)對象的屬性愁铺,并且目標(biāo)對象不可擴展鹰霍,則不能將其報告為存在。
*   屬性不能被報告為不可配置茵乱,如果它不作為目標(biāo)對象的自身屬性存在茂洒,或者作為目標(biāo)對象的可配置的屬性存在。
*   Object.getOwnPropertyDescriptor(target)的結(jié)果可以使用 Object.defineProperty 應(yīng)用于目標(biāo)對象瓶竭,也不會拋出異常督勺。

*/

  • handler.defineProperty()
    Object.defineProperty方法的捕捉器。在定義代理對象某個屬性時的屬性描述時觸發(fā)該操作斤贰,比如在執(zhí)行 Object.defineProperty(proxy, "foo", {})時智哀。

\color{rgb(106 90 205)}{demo}

/*
target
目標(biāo)對象。
property
待檢索其描述的屬性名荧恍。
descriptor
待定義或修改的屬性的描述符瓷叫。
defineProperty 方法必須以一個 Boolean返回屯吊,表示定義該屬性的操作成功與否。
*/
var p = new Proxy({}, {
  defineProperty: function(target, prop, descriptor) {
    console.log('called: ' + prop);
    return true;
  }
});

var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, 'a', desc); 
// "called: a"

/*
當(dāng)調(diào)用 Object.defineProperty()` 或者 Reflect.defineProperty()摹菠,傳遞給 `defineProperty` 的 `descriptor`   有一個限制 - 只有以下屬性才有用盒卸,非標(biāo)準(zhǔn)的屬性將會被無視 :

*   enumerable
*   configurable
*   writable
*   value
*   get
*   set
*/
var p = new Proxy({}, {
  defineProperty(target, prop, descriptor) {
    console.log(descriptor);
    return Reflect.defineProperty(target, prop, descriptor);
  }
});

Object.defineProperty(p, 'name', {
  value: 'proxy',
  type: 'custom'
});  
// { value: 'proxy' }

/*
如果違背了以下的不變量,proxy會拋出 TypeError

*   如果目標(biāo)對象不可擴展次氨, 將不能添加屬性蔽介。
*   不能添加或者修改一個屬性為不可配置的,如果它不作為一個目標(biāo)對象的不可配置的屬性存在的話煮寡。
*   如果目標(biāo)對象存在一個對應(yīng)的可配置屬性虹蓄,這個屬性可能不會是不可配置的。
*   如果一個屬性在目標(biāo)對象中存在對應(yīng)的屬性洲押,那么 `Object.defineProperty(target, prop, descriptor)` 將不會拋出異常武花。
*   在嚴(yán)格模式下, `false` 作為` handler.defineProperty` 方法的返回值的話將會拋出 [`TypeError`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError "TypeError(類型錯誤) 對象用來表示值的類型非預(yù)期類型時發(fā)生的錯誤杈帐。") 異常.

*/

  • handler.has()
    in 操作符的捕捉器体箕。在判斷代理對象是否擁有某個屬性時觸發(fā)該操作,比如在執(zhí)行 "foo" in proxy 時挑童。

\color{rgb(106 90 205)}{demo}

const handler1 = {
  has(target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};

const monster1 = {
  _secret: 'easily scared',
  eyeCount: 4
};

const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// 輸出: true

console.log('_secret' in proxy1);
// 輸出: false

console.log('_secret' in monster1);
//輸出: true

/*
target
目標(biāo)對象.
prop
需要檢查是否存在的屬性.
has 方法返回一個 boolean 屬性的值.
*/

  • handler.get()
    屬性讀取操作的捕捉器累铅。在讀取代理對象的某個屬性時觸發(fā)該操作,比如在執(zhí)行 proxy.foo 時站叼。

\color{rgb(106 90 205)}{demo}

/*
以下是傳遞給get方法的參數(shù)娃兽,this上下文綁定在handler對象上.

target
目標(biāo)對象。
property
被獲取的屬性名尽楔。
receiver
Proxy或者繼承Proxy的對象
返回值
get方法可以返回任何值投储。
*/
var p = new Proxy({}, {
  get: function(target, prop, receiver) {
    console.log("called: " + prop);
    return 10;
  }
});

console.log(p.a); 
// "called: a"
// 10

/*
該方法會攔截目標(biāo)對象的以下操作:

*   訪問屬性: proxy[foo]和proxy.bar
*   訪問原型鏈上的屬性: Object.create(proxy)[foo]
*   `Reflect.get()
*/

  • handler.set()
    屬性設(shè)置操作的捕捉器。在給代理對象的某個屬性賦值時觸發(fā)該操作阔馋,比如在執(zhí)行 proxy.foo = 1 時玛荞。

\color{rgb(106 90 205)}{demo}

function Monster() {
  this.eyeCount = 4;
}

const handler1 = {
  set(obj, prop, value) {
    if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
      console.log('Monsters must have an even number of eyes');
    } else {
      return Reflect.set(...arguments);
    }
  }
};

const monster1 = new Monster();
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
// 輸出: "Monsters must have an even number of eyes"

console.log(proxy1.eyeCount);
// 輸出: 4

/*
target
*   目標(biāo)對象。
property
*   將被設(shè)置的屬性名或 Symbol.
value
*   新屬性值.
receiver
*   最初被調(diào)用的對象呕寝。通常是 proxy 本身勋眯,但 handler 的 set 方法也有可能在原型鏈上,或以其他方式被間接地調(diào)用(因此不一定是 proxy 本身)
set() 方法應(yīng)當(dāng)返回一個布爾值
*   返回 `true` 代表屬性設(shè)置成功.
*   在嚴(yán)格模式下下梢,如果 `set()` 方法返回 `false`客蹋,那么會拋出一個 TypeError異常.
*/
var p = new Proxy({}, {
  set: function(target, prop, value, receiver) {
    target[prop] = value;
    console.log('property set: ' + prop + ' = ' + value);
    return true;
  }
})

console.log('a' in p);  // false

p.a = 10;               // "property set: a = 10"
console.log('a' in p);  // true
console.log(p.a);       // 10

  • handler.deleteProperty()
    delete操作符的捕捉器。在刪除代理對象的某個屬性時觸發(fā)該操作孽江,即使用 delete運算符讶坯,比如在執(zhí)行 delete proxy.foo 時。

\color{rgb(106 90 205)}{demo}

var p = new Proxy(target, {
  deleteProperty: function(target, property) {
  }
});

/*
target
*  目標(biāo)對象
property
*  待刪除的屬性名
deleteProperty 必須返回一個 Boolean 類型的值岗屏,表示了該屬性是否被成功刪除闽巩。
*/

var p = new Proxy({}, {
  deleteProperty: function(target, prop) {
    console.log('called: ' + prop);
    return true;
  }
});

delete p.a; // "called: a"

  • handler.ownKeys()
    Object.getOwnPropertyNames方法和 Object.getOwnPropertySymbols方法的捕捉器钧舌。

\color{rgb(106 90 205)}{demo}

const monster1 = {
  _age: 111,
  [Symbol('secret')]: 'I am scared!',
  eyeCount: 4
};

const handler1 = {
  ownKeys(target) {
    return Reflect.ownKeys(target);
  }
};

const proxy1 = new Proxy(monster1, handler1);

for (const key of Object.keys(proxy1)) {
  console.log(key);
  // 輸出: "_age"
  // 輸出: "eyeCount"
}
/*
target
*  目標(biāo)對象.
*  返回值
ownKeys 方法必須返回一個可枚舉對象.
*/

var p = new Proxy({}, {
  ownKeys: function(target) {
    console.log('called');
    return ['a', 'b', 'c'];
  }
});

console.log(Object.getOwnPropertyNames(p)); 
// "called"
 // [ 'a', 'b', 'c' ]

  • handler.apply()
    函數(shù)調(diào)用操作的捕捉器。

\color{rgb(106 90 205)}{demo}

function sum(a, b) {
  return a + b;
}

const handler = {
  apply: function(target, thisArg, argumentsList) {
    console.log(`Calculate sum: ${argumentsList}`);
    // expected output: "Calculate sum: 1,2"

    return target(argumentsList[0], argumentsList[1]) * 10;
  }
};

const proxy1 = new Proxy(sum, handler);

console.log(sum(1, 2));
// 輸出: 3
console.log(proxy1(1, 2));
// 輸出: 30

/*
target
*  目標(biāo)對象(函數(shù))涎跨。
thisArg
*  被調(diào)用時的上下文對象。
argumentsList
*  被調(diào)用時的參數(shù)數(shù)組崭歧。
apply方法可以返回任何值隅很。
*/

var p = new Proxy(function() {}, {
  apply: function(target, thisArg, argumentsList) {
    console.log('called: ' + argumentsList.join(', '));
    return argumentsList[0] + argumentsList[1] + argumentsList[2];
  }
});

console.log(p(1, 2, 3)); 
// "called: 1, 2, 3"
// 6

  • handler.construct()
    new 操作符的捕捉器。

\color{rgb(106 90 205)}{demo}

/*
handler.construct() 方法用于攔截new操作符. 為了使new操作符在生成的Proxy對象上生效率碾,用于初始化代理的目標(biāo)對象自身必須具有[[Construct]]內(nèi)部方法(即 new target 必須是有效的)叔营。
*/
function monster1(disposition) {
  this.disposition = disposition;
}

const handler1 = {
  construct(target, args) {
    console.log('monster1 constructor called');
    // expected output: "monster1 constructor called"

    return new target(...args);
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(new proxy1('fierce').disposition);
// 輸出: "fierce"
/*
target
*  目標(biāo)對象。
argumentsList
*  constructor的參數(shù)列表所宰。
newTarget
*  最初被調(diào)用的構(gòu)造函數(shù)绒尊,就上面的例子而言是p。
construct 方法必須返回一個對象仔粥。
*/

var p = new Proxy(function() {}, {
  construct: function(target, argumentsList, newTarget) {
    console.log('called: ' + argumentsList.join(', '));
    return { value: argumentsList[0] * 10 };
  }
});

console.log(new p(1).value); 
// "called: 1"
// 10
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婴谱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子躯泰,更是在濱河造成了極大的恐慌谭羔,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麦向,死亡現(xiàn)場離奇詭異瘟裸,居然都是意外死亡,警方通過查閱死者的電腦和手機诵竭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門话告,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卵慰,你說我怎么就攤上這事沙郭。” “怎么了呵燕?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵棠绘,是天一觀的道長。 經(jīng)常有香客問我再扭,道長氧苍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任泛范,我火速辦了婚禮让虐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罢荡。我一直安慰自己赡突,他們只是感情好对扶,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惭缰,像睡著了一般浪南。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漱受,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天络凿,我揣著相機與錄音,去河邊找鬼昂羡。 笑死絮记,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虐先。 我是一名探鬼主播怨愤,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蛹批!你這毒婦竟也來了撰洗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤般眉,失蹤者是張志新(化名)和其女友劉穎了赵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甸赃,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡柿汛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了埠对。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片络断。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖项玛,靈堂內(nèi)的尸體忽然破棺而出貌笨,到底是詐尸還是另有隱情,我是刑警寧澤襟沮,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布锥惋,位于F島的核電站,受9級特大地震影響开伏,放射性物質(zhì)發(fā)生泄漏膀跌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一固灵、第九天 我趴在偏房一處隱蔽的房頂上張望捅伤。 院中可真熱鬧,春花似錦巫玻、人聲如沸丛忆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熄诡。三九已至可很,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粮彤,已是汗流浹背根穷。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留导坟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓圈澈,卻偏偏與公主長得像惫周,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子康栈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359