Proxy用法詳解

  • 概述

Proxy 用于修改某些操作的默認(rèn)行為钧排,可以理解成灯谣,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn)曲掰,都必須先通過(guò)這層攔截疾捍,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫蜈缤。
ES6 原生提供 Proxy 構(gòu)造函數(shù)拾氓,用來(lái)生成 Proxy 實(shí)例。

var proxy = new Proxy(target, handler);

Proxy 對(duì)象的所有用法底哥,都是上面這種形式咙鞍,不同的只是handler參數(shù)的寫法房官。其中,new Proxy()表示生成一個(gè)Proxy實(shí)例续滋,target參數(shù)表示所要攔截的目標(biāo)對(duì)象翰守,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為疲酌。

  • Proxy 實(shí)例的方法

get()

get方法用于攔截某個(gè)屬性的讀取操作蜡峰,可以接受三個(gè)參數(shù),依次為目標(biāo)對(duì)象朗恳、屬性名和 proxy 實(shí)例本身(嚴(yán)格地說(shuō)湿颅,是操作行為所針對(duì)的對(duì)象),其中最后一個(gè)參數(shù)可選粥诫。

var person={name:'小明'};
var proxyPerson=new Proxy(person,{
    get:function(target,property){
        if(property in target){
          return target[property]}
        else{
        throw new ReferenceError("Property \"" + property + "\" does not exist.")
        }
      }
    })
proxy.name // "小明"
proxy.age //Uncaught ReferenceError: Property "age" does not exist.

上面代碼表示油航,如果訪問(wèn)目標(biāo)對(duì)象不存在的屬性,會(huì)拋出一個(gè)錯(cuò)誤怀浆。如果沒(méi)有這個(gè)攔截函數(shù)谊囚,訪問(wèn)不存在的屬性,只會(huì)返回undefined执赡。

get方法可以繼承镰踏。

var child=Object.create(proxyPerson);
child.name //小明

上面代碼中,攔截操作定義在Prototype對(duì)象上面沙合,所以如果讀取obj對(duì)象繼承的屬性時(shí)奠伪,攔截會(huì)生效。

set()

set方法用來(lái)攔截某個(gè)屬性的賦值操作灌诅,可以接受四個(gè)參數(shù)芳来,依次為目標(biāo)對(duì)象、屬性名猜拾、屬性值和 Proxy 實(shí)例本身即舌,其中最后一個(gè)參數(shù)可選。

let personq=new Proxy({},{
    set:function(obj,prop,value){
        if(prop==='age'){if(!Number.isInteger(value)){
            throw new TypeError('The age is not an integer')}
        if(value>200){
            throw new RangeError('The age seems invalid')} 
        }
        obj[prop]=value
      }})

上面代碼中挎袜,由于設(shè)置了存值函數(shù)set顽聂,任何不符合要求的age屬性賦值,都會(huì)拋出一個(gè)錯(cuò)誤盯仪,這是數(shù)據(jù)驗(yàn)證的一種實(shí)現(xiàn)方法紊搪。利用set方法,還可以數(shù)據(jù)綁定全景,即每當(dāng)對(duì)象發(fā)生變化時(shí)耀石,會(huì)自動(dòng)更新 DOM。

apply()

apply方法攔截函數(shù)的調(diào)用爸黄、call和apply操作滞伟。
apply方法可以接受三個(gè)參數(shù)揭鳞,分別是目標(biāo)對(duì)象、目標(biāo)對(duì)象的上下文對(duì)象(this)和目標(biāo)對(duì)象的參數(shù)數(shù)組梆奈。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};
var p = new Proxy(target, handler);
p()
// "I am the proxy"

has()

has方法用來(lái)攔截HasProperty操作野崇,即判斷對(duì)象是否具有某個(gè)屬性時(shí),這個(gè)方法會(huì)生效亩钟。典型的操作就是in運(yùn)算符乓梨。
has方法可以接受兩個(gè)參數(shù),分別是目標(biāo)對(duì)象清酥、需查詢的屬性名扶镀。
下面的例子使用has方法隱藏某些屬性,不被in運(yùn)算符發(fā)現(xiàn)焰轻。

var animal=new Proxy(target,{
    has(target,key){
        if(key[0]==='_'){
            return false} 
        return target[key]
        }
    })

上面代碼中狈惫,如果原對(duì)象的屬性名的第一個(gè)字符是下劃線,proxy.has就會(huì)返回false鹦马,從而不會(huì)被in運(yùn)算符發(fā)現(xiàn)。
另外忆肾,雖然for...in循環(huán)也用到了in運(yùn)算符荸频,但是has攔截對(duì)for...in循環(huán)不生效。

deleteProperty()

deleteProperty方法用于攔截delete操作客冈,如果這個(gè)方法拋出錯(cuò)誤或者返回false旭从,當(dāng)前屬性就無(wú)法被delete命令刪除。

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

上面代碼中场仲,deleteProperty方法攔截了delete操作符和悦,刪除第一個(gè)字符為下劃線的屬性會(huì)報(bào)錯(cuò)。
注意渠缕,目標(biāo)對(duì)象自身的不可配置(configurable)的屬性鸽素,不能被deleteProperty方法刪除,否則報(bào)錯(cuò)亦鳞。
其他實(shí)例方法就不在一一贅述馍忽,可以參考https://es6.ruanyifeng.com/#docs/proxy

  • 使用場(chǎng)景

proxy 模式一般可以使用在當(dāng)你想要:
攔截或者控制對(duì)一個(gè)對(duì)象的訪問(wèn)時(shí)
通過(guò)掩蓋程序或隱藏邏輯來(lái)降低方法/類的復(fù)雜度
阻止沒(méi)有經(jīng)過(guò)驗(yàn)證/準(zhǔn)備的重資源操作

1. 抽離校驗(yàn)代碼
function createValidator(target, validator) { 
  return new Proxy(target, {
    _validator: validator,
    set(target, key, value, proxy) {
      if (target.hasOwnProperty(key)) {
        let validator = this._validator[key];
        if (!!validator(value)) {
          return Reflect.set(target, key, value, proxy);
        } else {
          throw Error(`Cannot set ${key} to ${value}. Invalid.`);
        }
      } else {
        // prevent setting a property that isn't explicitly defined in the validator
        throw Error(`${key} is not a valid property`)
      }
    }
  });
}
// Now, just define validators for each property
const personValidators = { 
  name(val) {
    return typeof val === 'string';
  },
  age(val) {
    return typeof age === 'number' && age > 18;
  }
}
class Person { 
  constructor(name, age) {
    this.name = name;
    this.age = age;
    return createValidator(this, personValidators);
  }
}
const bill = new Person('Bill', 25);

這樣,你就能在不改變你的類/方法的前提下無(wú)線擴(kuò)展你的校驗(yàn)代碼了燕差。

2. JavaScript 中真正的私有

通過(guò)get(),set()實(shí)例方法可以保證對(duì)象里面私有屬性真正實(shí)現(xiàn)私有遭笋。

var api = { 
  _apiKey: '123abc456def',
  /* mock methods that use this._apiKey */
  getUsers: function(){ },
  getUser: function(userId){ },
  setUser: function(userId, config){ }
};
// Add other restricted properties to this array
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, { 
    get(target, key, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, value, proxy);
    }
});
// throws an error
console.log(api._apiKey);
// throws an error
api._apiKey = '987654321';
3. 靜默對(duì)象訪問(wèn)日志

對(duì)于資源密集、運(yùn)行緩慢或重度使用的方法和接口徒探,你也許想要記錄他們的使用情況或性能瓦呼。Proxies 可以很容易的在后臺(tái)默默的做這件事。
每當(dāng)你調(diào)用一個(gè)方法测暗,你都會(huì)先 get 這個(gè)方法央串。所以如果你想攔截一個(gè)方法調(diào)用磨澡,你需要攔截它的 get 先,然后再攔截 apply蹋辅。

let api = { 
  _apiKey: '123abc456def',
  getUsers: function() { /* ... */ },
  getUser: function(userId) { /* ... */ },
  setUser: function(userId, config) { /* ... */ }
};
api = new Proxy(api, { 
  get: function(target, key, proxy) {
    var value = target[key];
    return function(...arguments) {
      logMethodAsync(new Date(), key);
      return Reflect.apply(value, target, arguments);
    };
  }
});
// executes apply trap in the background
api.getUsers();
function logMethodAsync(timestamp, method) { 
  setTimeout(function() {
    console.log(`${timestamp} - Logging ${method} request asynchronously.`);
  }, 0)
}
  1. 警告或阻止特定操作
    設(shè)你想要組織某人刪除 noDelete 屬性钱贯,想要告訴調(diào)用 oldMethod 的用戶那已經(jīng)被棄用了,或者想組織某人修改 doNotChange 屬性
let dataStore = { 
  noDelete: 1235,
  oldMethod: function() {/*...*/ },
  doNotChange: "tried and true"
};
const NODELETE = ['noDelete']; 
const DEPRECATED = ['oldMethod']; 
const NOCHANGE = ['doNotChange'];
dataStore = new Proxy(dataStore, { 
  set(target, key, value, proxy) {
    if (NOCHANGE.includes(key)) {
      throw Error(`Error! ${key} is immutable.`);
    }
    return Reflect.set(target, key, value, proxy);
  },
  deleteProperty(target, key) {
    if (NODELETE.includes(key)) {
      throw Error(`Error! ${key} cannot be deleted.`);
    }
    return Reflect.deleteProperty(target, key);

  },
  get(target, key, proxy) {
    if (DEPRECATED.includes(key)) {
      console.warn(`Warning! ${key} is deprecated.`);
    }
    var val = target[key];

    return typeof val === 'function' ?
      function(...args) {
        Reflect.apply(target[key], target, args);
      } :
      val;
  }
});
// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo"; 
delete dataStore.noDelete; 
dataStore.oldMethod();
  1. 組織不必要的重資源操作
    假設(shè)你有個(gè)會(huì)返回一個(gè)很大的文件的服務(wù)端侦另,你不希望在之前的請(qǐng)求正在進(jìn)行時(shí)秩命,或者文件正在被下載時(shí),或者已經(jīng)下載完畢的情況下進(jìn)行請(qǐng)求褒傅。
  2. 即時(shí)取消對(duì)敏感數(shù)據(jù)的訪問(wèn)
    Proxy 提供任何時(shí)候取消對(duì)目標(biāo)對(duì)象訪問(wèn)功能弃锐。如果你想要完全封鎖(為了安全性、權(quán)限殿托、或性能原因)一些數(shù)據(jù)和 API 的訪問(wèn)這會(huì)很有用霹菊。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市支竹,隨后出現(xiàn)的幾起案子旋廷,更是在濱河造成了極大的恐慌,老刑警劉巖礼搁,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饶碘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡馒吴,警方通過(guò)查閱死者的電腦和手機(jī)扎运,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饮戳,“玉大人豪治,你說(shuō)我怎么就攤上這事〕豆蓿” “怎么了负拟?”我有些...
    開(kāi)封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)篮赢。 經(jīng)常有香客問(wèn)我齿椅,道長(zhǎng),這世上最難降的妖魔是什么启泣? 我笑而不...
    開(kāi)封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任涣脚,我火速辦了婚禮,結(jié)果婚禮上寥茫,老公的妹妹穿的比我還像新娘遣蚀。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布芭梯。 她就那樣靜靜地躺著险耀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玖喘。 梳的紋絲不亂的頭發(fā)上甩牺,一...
    開(kāi)封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音累奈,去河邊找鬼贬派。 笑死,一個(gè)胖子當(dāng)著我的面吹牛澎媒,可吹牛的內(nèi)容都是我干的搞乏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼戒努,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼请敦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起储玫,我...
    開(kāi)封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侍筛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后撒穷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體勾笆,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年桥滨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弛车。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡齐媒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纷跛,到底是詐尸還是另有隱情喻括,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布贫奠,位于F島的核電站唬血,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏唤崭。R本人自食惡果不足惜拷恨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谢肾。 院中可真熱鬧腕侄,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至分预,卻和暖如春兢交,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笼痹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工配喳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人与倡。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓界逛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纺座。 傳聞我的和親對(duì)象是個(gè)殘疾皇子息拜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • Proxy 概述 Proxy 用于修改某些操作的默認(rèn)行為,等同于在語(yǔ)言層面做出修改净响,所以屬于一種“元編程”(met...
    pauljun閱讀 3,251評(píng)論 0 1
  • 前不久換工作的時(shí)候面試某大廠被問(wèn)到關(guān)于Proxy的問(wèn)題少欺,腦子里有點(diǎn)印象但是又說(shuō)不出具體使用方法,主要還是自己平時(shí)積...
    HalShaw閱讀 7,032評(píng)論 2 2
  • defineProperty() 學(xué)習(xí)書籍《ECMAScript 6 入門 》 Proxy Proxy 用于修改某...
    Bui_vlee閱讀 654評(píng)論 0 1
  • 一馋贤、概述 Proxy 用于修改某些操作的默認(rèn)行為赞别,等同于在語(yǔ)言層面做出修改,所以屬于一種“元編程”(meta pr...
    了凡和纖風(fēng)閱讀 263評(píng)論 0 1
  • 概述 IDEA(https://www.jetbrains.com/idea/)配乓,全稱IntelliJ IDEAJ...
    lxtyp閱讀 2,942評(píng)論 0 3