Proxy 與 Reflect

Proxy 對(duì)象

Proxy 用來修改某些默認(rèn)操作赛蔫,等同于在語言層面做出修改。所以屬于一種元編程(meta programming), 即對(duì)編程語言進(jìn)行編程逛钻。字面理解為Proxy代理了某些默認(rèn)的操作。
其使用格式如下:

var proxy = new Proxy(target, handler);

target是被代理的目標(biāo)對(duì)象,handler也是個(gè)對(duì)象马绝,用來定制攔截行為,內(nèi)部定義每個(gè)被代理的行為挣菲。
注意:

  • 如果希望這個(gè)代理有效富稻,需要在 proxy 對(duì)象上調(diào)用屬性方法,而不是在 target 上調(diào)用
  • 如果指定 handler 為空對(duì)象白胀,那么得到對(duì)象和原對(duì)象一樣
  • 得到的 proxy 是 target 的引用椭赋,如果沒有代理,在 proxy 上的修改和在 target 上的修改等同

看一個(gè)簡單的實(shí)例

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
console.log(proxy.time);    //35
console.log(proxy.name);    //35
console.log(proxy.title);    //35
//被代理的對(duì)象無論輸入什么屬性都返回35

實(shí)際上或杠,proxy 對(duì)象也可以被繼承:

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
var obj = Object.create(proxy);
obj.time = 20;
console.log(obj.time);    //20
console.log(obj.name);    //35

感受一下它的威力:

var obj = new Proxy({}, {
  get: function(target, key, receiver){
    console.log(`getting ${key} ...`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver){
    console.log(`setting ${key} ...`);
    return Reflect.set(target, key, value, receiver);
  }
});

obj.count = 1;            //setting count ...
++obj.count;              //getting count ...
                          //setting count ...
console.log(obj.count);   //getting count ...
                          //2

可以看出來哪怔,handler對(duì)象中 get 方法表示屬性的訪問請(qǐng)求,set 方法表示屬性的寫入請(qǐng)求向抢。
當(dāng)然不僅僅 get 和 set蔓涧, 我們可以定義以下攔截函數(shù):

  • get(target, propKey, receiver = target)
    攔截對(duì)象的讀取屬性。當(dāng) target 對(duì)象設(shè)置了 propKey 屬性的 get 函數(shù)時(shí)笋额,receiver 綁定 get 函數(shù)的 this元暴。返回值任意
  • set(target, propKey, value, receiver = target)
    攔截對(duì)象的寫入屬性。返回一個(gè)布爾值
  • has(target, propKey)
    攔截 propKey in proxy 操作符兄猩,返回一個(gè)布爾值
  • deleteProperty(target, propKey)
    攔截 delete proxy[propKey] 操作符茉盏,返回一個(gè)布爾值
  • enumerate(target)
    攔截 for(let i in proxy) 遍歷器,返回一個(gè)遍歷器
  • hasOwn(target, propKey)
    攔截 proxy.hasOwnProperty('foo')枢冤,返回一個(gè)布爾值
  • ownKeys(target)
    攔截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy)鸠姨,返回一個(gè)數(shù)組。該方法返回對(duì)象所有自身屬性淹真,包括不可遍歷屬性讶迁,不包括 Symble屬性,但是Object.keys(proxy)不應(yīng)該包括不可遍歷屬性
  • getOwnPropertyDescriptor(target, propKey)
    攔截 Object.getOwnPropertyDescriptor(proxy, propKey)核蘸,返回其屬性描述符
  • defineProperty(target, propKey, propDesc)
    攔截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc)巍糯,返回一個(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è)布爾值
  • apply(target, object, args)
    攔截對(duì) proxy 實(shí)例的函數(shù)操作,包括 proxy(...args),proxy.call(object, ...args),proxy.apply(object, args)
  • construct(target, args, proxy)
    攔截用 new 調(diào)用 proxy 函數(shù)的操作宅楞,construct()返回的不是對(duì)象會(huì)報(bào)錯(cuò)

以下列舉一些 Proxy 的實(shí)例

訪問對(duì)象不存在的屬性報(bào)錯(cuò)

var obj = new Proxy({}, {
  get: function(target, key){
    if(key in target){
      return Reflect.get(target, key);
    } else {
      throw new ReferenceError(`"${key}" is not in object`);
    }
  }
});
obj.look = "picture";
console.log(obj.look);     //"picture"
console.log(obj.sleep);    //ReferenceError: "sleep" is not in object

數(shù)組索引為負(fù)時(shí)返回倒數(shù)位置的值

var origin = [10,20];
var arr = new Proxy(origin, {
  get(target, key){
    let index = parseInt(key);
    if(index < 0){
      index = target.length + index;
      if(index < 0) return undefined;
    }
    return Reflect.get(target, index);
  }
});
console.log(arr[0]);     //10
console.log(arr[1]);     //20
console.log(arr[2]);     //undefined
console.log(arr[-1]);    //20
console.log(arr[-4]);    //undefined

保護(hù)對(duì)象內(nèi)以 "_" 開頭的屬性為私有屬性:

var o = {
  "_name": "Bob",
  "age": 13,
  "_fun": function(){
    console.log("_fun is called");
  }
};
var obj = new Proxy(o, {
  get(target, key){
    if(key.charAt(0) === '_'){
      return undefined;
    }
    return Reflect.get(target, key);
  },
  set(target, key, value){
    if(key.charAt(0) === '_'){
      throw new Error('Cannot define a property begin with "_"');
    }
    return  Reflect.set(target, key, value);
  },
  has(target,key){
    if(key.charAt(0) === '_'){
      return false;
    }
    return Reflect.has(target, key);
  },
  deleteProperty(target,key){
    if(key.charAt(0) === '_'){
      return false;
    } else {
      Reflect.deleteProperty(..arguments);
    }
  },
  apply(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect apply(...arguments);
    }
  },
  defineProperty(target,key,desc){
    if(key.charAt(0) === '_'){
      return new Error(`cannot define property begin with "_"`);
    } else {
      Reflect.defineProperty(..arguments);
    }
  },
  setPrototypeOf(target,proto){
    throw new TypeError(`Cannot change the proto of ${target}`);
  },
  construct(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect construct(...arguments);
    }
  }
});

console.log(obj.age);    //13
obj.age = 20;
console.log(obj.age);    //20
console.log(obj._name);  //undefined
obj._hobby = "Coding";   //Error: Cannot define a property begin with "_"
_name in key             //false
delete obj._name;
Object.defineProperty(obj,"_hobby",{
  value: "Coding"
});
Object.defineProperties(obj,{
  '_hobby': {
    value: "Coding"
  }
});
obj._fun();
var a = new obj._fun();
obj.__proto__ = {};     //Cannot define a property begin with "_"
Object.setPrototypeOf(obj,{})    //Cannot change the proto of obj

當(dāng)然不是所有 proxy 代理都不可取消针姿,下面方法設(shè)置的代理是可以通過定義代理時(shí)返回的revoke函數(shù)取消:

var a = {
  name:"Bob"
};
var {proxy, revoke} = Proxy.revocable(a, {
  get(target,key){
    return undefined;
  }
});
proxy.name;   //undefined;
revoke();
proxy.name;   //TypeError: Cannot perform 'get' on a proxy that has been revoked

Reflect 對(duì)象

Reflect 對(duì)象有一下作用:

  1. 將 Object對(duì)象的一些明顯屬于語言層面的方法部署在 Reflect 上
  2. 修改某些 Object 對(duì)象的方法使其更合理。比如 Object.defineProperty 遇到無法定義屬性時(shí)會(huì)拋出錯(cuò)誤厌衙,而 Reflect.defineProperty 會(huì)返回 false
  3. 把所以 object 的操作都替換成函數(shù)行為距淫,比如用 Reflect.has(obj,name) 替換 name in obj
  4. 保證只要是 Proxy 有的方法就一定可以在 Reflect 上找到相同的方法,這樣可以在實(shí)現(xiàn) proxy 時(shí)方便的完成默認(rèn)行為婶希。換言之榕暇,無論 proxy 怎么修改默認(rèn)行為,你總可以在 Reflect 上找到真正默認(rèn)的行為

代理在添加額外的功能時(shí)饲趋,利用 Reflect 保證了原始功能的實(shí)現(xiàn)拐揭。舉個(gè)例子:

var loggedObj = new Proxy({}, {
  get(target,propKey){
    console.log(`getting ${target}.${propKey}`);  //當(dāng)然你最好把操作記錄到一個(gè) log 中
    return Reflect.get(target,propKey);
  }
});

Reflect有以下方法:

  • Reflect.getOwnPropertyDescriptor(target, propKey)
    等同于 ObjectgetOwnPropertyDescriptor(target, propKey)
  • Reflect.defineProperty(target,propKey,desc)
    等同于 Object.defineProperty(target,propKey,desc)
  • Reflect.getOwnPropertyNames(target)
    等同于 Object.getOwnPropertyNames(target)
  • Reflect.getPrototypeOf(target)
    等同于 Object.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, proto)
    等同于 Object.setPrototypeOf(target, proto)
  • Reflect.deleteProperty(target, propKey)
    等同于 delete target.propKey
  • Reflect.enumerate(target)
    等同于 for ... in target
  • Reflect.freeze(target)
    等同于 Object.freeze(target)
  • Reflect.seal(target)
    等同于 Object.seal(target)
  • Reflect.preventExtensions(target)
    等同于 Object.preventExtensions(target)
  • Reflect.isFrozen(target)
    等同于 Object.isFrozen(target)
  • Reflect.isSealed(target)
    等同于 Object.isSealed(target)
  • Reflect.isExtensible(target)
    等同于 Object.isExtensible(target)
  • Reflect.has(target, propKey)
    等同于 propkey in object
  • Reflect.hasOwn(target, propKey)
    等同于 target.hasOwnProperty(propKey)
  • Reflect.ownKeys(target)
    遍歷得到target自身所有屬性,包括不可枚舉屬性奕塑,不包括 Symbol 屬性
  • Reflect.get(target,propKey, receiver = target)
    如果 propKey 是個(gè)讀取器堂污,則讀取器中的 this 綁定到 receiver
var per = {
  bar: function(){console.log("per-bar")}
}
var obj = {
  get foo(){ this.bar(); },
  bar: function (){console.log("obj-bar")}
};
Reflect.get(obj, "foo", per);    //"per-bar"
  • Reflect.set(target,propKey, value, receiver = target)
    如果 propKey 是個(gè)讀取器,則讀取器中的 this 綁定到 receiver
  • Reflect.apply(target, thisArg, args)
    等同于 Function.prototype.apply.call(target, thisArg, args)thisArg.target(args)
  • Reflect.construct(target,args)
    等同于 new target(...args)

注意以上方法中龄砰,Reflect.set(), Reflect.defineProperty(), Reflect.freeze(), Reflect.seal(), Reflect.preventExtensions() 在成功時(shí)返回 true, 失敗時(shí)返回 false盟猖。對(duì)應(yīng)的 Object 方法失敗時(shí)會(huì)拋出錯(cuò)誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末换棚,一起剝皮案震驚了整個(gè)濱河市式镐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌固蚤,老刑警劉巖娘汞,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異夕玩,居然都是意外死亡你弦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門燎孟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禽作,“玉大人,你說我怎么就攤上這事揩页】醭ィ” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萍程。 經(jīng)常有香客問我幢妄,道長,這世上最難降的妖魔是什么尘喝? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任磁浇,我火速辦了婚禮斋陪,結(jié)果婚禮上朽褪,老公的妹妹穿的比我還像新娘。我一直安慰自己无虚,他們只是感情好缔赠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著友题,像睡著了一般嗤堰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上度宦,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天踢匣,我揣著相機(jī)與錄音,去河邊找鬼戈抄。 笑死离唬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的划鸽。 我是一名探鬼主播输莺,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼裸诽!你這毒婦竟也來了嫂用?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤丈冬,失蹤者是張志新(化名)和其女友劉穎嘱函,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埂蕊,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡往弓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粒梦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亮航。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匀们,靈堂內(nèi)的尸體忽然破棺而出缴淋,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布重抖,位于F島的核電站露氮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钟沛。R本人自食惡果不足惜畔规,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恨统。 院中可真熱鬧叁扫,春花似錦、人聲如沸畜埋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悠鞍。三九已至对室,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咖祭,已是汗流浹背掩宜。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留么翰,地道東北人牺汤。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像硬鞍,于是被迫代替她去往敵國和親慧瘤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • defineProperty() 學(xué)習(xí)書籍《ECMAScript 6 入門 》 Proxy Proxy 用于修改某...
    Bui_vlee閱讀 648評(píng)論 0 1
  • ECMAScript發(fā)展歷史 (1)ECMA-262 第1版:去除了對(duì)針對(duì)瀏覽器的特性,支持Unicode標(biāo)準(zhǔn)(多...
    congnie116閱讀 1,859評(píng)論 0 2
  • Proxy Proxy用于修改某些操作的默認(rèn)行為伐坏,等同于在語言層面作出修改怔匣,所以屬于一種“元編程”,即對(duì)編程語言進(jìn)...
    南藍(lán)NL閱讀 449評(píng)論 0 0
  • 很有意思的一章 Proxy get 利用Proxy桦沉,可以將讀取屬性的操作(get)每瞒,轉(zhuǎn)變?yōu)閳?zhí)行某個(gè)函數(shù),從而實(shí)現(xiàn)屬...
    KeithFu閱讀 636評(píng)論 0 0
  • 旅行,就像在人生軌道中的一個(gè)小叉口埠褪,悠悠蕩蕩一圈之后帶著更多的感悟再繼續(xù)前往人生軌道那個(gè)未知的終點(diǎn) 選擇去成都沒有...
    崔催脆閱讀 342評(píng)論 0 2