Proxy 與Object.defineProperty介紹與對比

一混弥、Proxy概述

Proxy 用于修改某些操作的默認行為雨饺,等同于在語言層面做出修改,所以屬于一種“元編程”(meta programming)钠龙,即對編程語言進行編程炬藤。
Proxy 可以理解成,在目標對象之前架設(shè)一層“攔截”俊鱼,外界對該對象的訪問刻像,都必須先通過這層攔截畅买,因此提供了一種機制并闲,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理谷羞,用在這里表示由它來“代理”某些操作帝火,可以譯為“代理器”。

ES6 原生提供 Proxy 構(gòu)造函數(shù)湃缎,用來生成 Proxy 實例犀填。

var proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式嗓违,不同的只是handler參數(shù)的寫法九巡。其中,new Proxy()表示生成一個Proxy實例蹂季,target參數(shù)表示所要攔截的目標對象冕广,handler參數(shù)也是一個對象,用來定制攔截行為偿洁。

簡單demo(攔截讀取屬性行為):

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

上面代碼中撒汉,作為構(gòu)造函數(shù),Proxy接受兩個參數(shù)涕滋。第一個參數(shù)是所要代理的目標對象(上例是一個空對象)睬辐,即如果沒有Proxy的介入,操作原來要訪問的就是這個對象宾肺;第二個參數(shù)是一個配置對象溯饵,對于每一個被代理的操作,需要提供一個對應(yīng)的處理函數(shù)锨用,該函數(shù)將攔截對應(yīng)的操作瓣喊。比如,上面代碼中黔酥,配置對象有一個get方法藻三,用來攔截對目標對象屬性的訪問請求洪橘。get方法的兩個參數(shù)分別是目標對象和所要訪問的屬性】妹保可以看到熄求,由于攔截函數(shù)總是返回35,所以訪問任何屬性都得到35逗概。

注意弟晚,要使得Proxy起作用,必須針對Proxy實例(上例是proxy對象)進行操作逾苫,而不是針對目標對象(上例是空對象)進行操作卿城。

如果handler沒有設(shè)置任何攔截,那就等同于直接通向原對象铅搓。

demo2:

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

上面代碼中瑟押,handler是一個空對象,沒有任何攔截效果星掰,訪問proxy就等同于訪問target多望。

技巧:一個技巧是將 Proxy 對象,設(shè)置到object.proxy屬性氢烘,從而可以在object對象上調(diào)用怀偷。

如下所示:

var object = { proxy: new Proxy(target, handler) };

Proxy 實例也可以作為其他對象的原型對象。

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35

上面代碼中播玖,proxy對象是obj對象的原型椎工,obj對象本身并沒有time屬性,所以根據(jù)原型鏈蜀踏,會在proxy對象上讀取該屬性维蒙,導(dǎo)致被攔截。

同一個攔截器函數(shù)脓斩,可以設(shè)置攔截多個操作木西。

var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

對于可以設(shè)置、但沒有設(shè)置攔截的操作随静,則直接落在目標對象上八千,按照原先的方式產(chǎn)生結(jié)果。
下面是 Proxy 支持的攔截操作一覽燎猛,一共 13 種恋捆。

  • get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.foo和proxy['foo']重绷。
  • set(target, propKey, value, receiver):攔截對象屬性的設(shè)置沸停,比如proxy.foo = v或proxy['foo'] = v,返回一個布爾值昭卓。
  • has(target, propKey):攔截propKey in proxy的操作愤钾,返回一個布爾值瘟滨。
  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值能颁。
  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)杂瘸、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)伙菊、for...in循環(huán)败玉,返回一個數(shù)組。該方法返回目標對象所有自身的屬性的屬性名镜硕,而Object.keys()的返回結(jié)果僅包括目標對象自身的可遍歷屬性运翼。
  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象兴枯。
  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)血淌、Object.defineProperties(proxy, propDescs),返回一個布爾值念恍。
  • preventExtensions(target):攔截Object.preventExtensions(proxy)六剥,返回一個布爾值晚顷。
  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy)峰伙,返回一個對象。
  • isExtensible(target):攔截Object.isExtensible(proxy)该默,返回一個布爾值瞳氓。
  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值栓袖。如果目標對象是函數(shù)匣摘,那么還有兩種額外操作可以攔截。
  • apply(target, object, args):攔截 Proxy 實例作為函數(shù)調(diào)用的操作裹刮,比如proxy(...args)音榜、proxy.call(object, ...args)、proxy.apply(...)捧弃。
  • construct(target, args):攔截 Proxy 實例作為構(gòu)造函數(shù)調(diào)用的操作赠叼,比如new proxy(...args)。

Proxy this 問題

雖然 Proxy 可以代理針對目標對象的訪問违霞,但它不是目標對象的透明代理嘴办,即不做任何攔截的情況下,也無法保證與目標對象的行為一致买鸽。主要原因就是在 Proxy 代理的情況下涧郊,目標對象內(nèi)部的this關(guān)鍵字會指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

上面代碼中眼五,一旦proxy代理target.m妆艘,后者內(nèi)部的this就是指向proxy彤灶,而不是target。
下面是一個例子批旺,由于this指向的變化枢希,導(dǎo)致 Proxy 無法代理目標對象。

const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined

上面代碼中朱沃,目標對象jane的name屬性苞轿,實際保存在外部WeakMap對象_name上面,通過this鍵區(qū)分逗物。由于通過proxy.name訪問時搬卒,this指向proxy,導(dǎo)致無法取到值翎卓,所以返回undefined契邀。
此外,有些原生對象的內(nèi)部屬性失暴,只有通過正確的this才能拿到坯门,所以 Proxy 也無法代理這些原生對象的屬性。

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

上面代碼中逗扒,getDate方法只能在Date對象實例上面拿到古戴,如果this不是Date對象實例就會報錯。這時矩肩,this綁定原始對象现恼,就可以解決這個問題。

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 1

三黍檩、Proxy 與Object.defineProperty

使用Object.defineProperty

ES5 提供了 Object.defineProperty 方法叉袍,該方法可以在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性刽酱,并返回這個對象喳逛。

  • Object.defineProperty(obj, prop, descriptor)
    參數(shù):
    • obj: 要在其上定義屬性的對象。
    • prop: 要定義或修改的屬性的名稱棵里。
    • descriptor: 將被定義或修改的屬性的描述符润文。

下面一個簡單的輸入框變化,數(shù)據(jù)展示

var obj = {
    value:''
}
var value = '';
Object.defineProperty(obj,"value",{
    get:function(){
        return value
    },
    set:function(newVal){
        value = newVal
    }
})
document.querySelector('#input').oninput = function(){
    var value = this.value;
    obj.value = value;
    document.querySelector('#text').innerHTML = obj.value;
}

當然一般不會只是改變一個屬性,如下所示衍慎,遍歷劫持對象所有的屬性

//要劫持的對象
const data = {
    name:''
}
  //遍歷對象,對其屬性值進行劫持
  Object.keys(data).forEach(function(key){
    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get: function() {
            console.log('get');
          },
          set: function(newVal) {
            // 當屬性值發(fā)生變化時我們可以進行額外操作
            console.log(`我修改了成了${newVal}`);           
          },
    })
  })
  data.name = 'gg'

這個只是簡單的劫持

缺點:Object.defineProperty的第一個缺陷,無法監(jiān)聽數(shù)組變化

區(qū)別

  • Proxy可以直接監(jiān)聽對象而非屬性
  • Proxy直接可以劫持整個對象,并返回一個新對象,不管是操作便利程度還是底層功能上都遠強于Object.defineProperty转唉。
  • Proxy可以直接監(jiān)聽數(shù)組的變化
  • Proxy有多達13種攔截方法,不限于apply、ownKeys稳捆、deleteProperty赠法、has等等是Object.defineProperty不具備的。

參考文檔:http://es6.ruanyifeng.com/#docs/proxy

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砖织,隨后出現(xiàn)的幾起案子款侵,更是在濱河造成了極大的恐慌,老刑警劉巖侧纯,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件新锈,死亡現(xiàn)場離奇詭異,居然都是意外死亡眶熬,警方通過查閱死者的電腦和手機妹笆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娜氏,“玉大人拳缠,你說我怎么就攤上這事∶趁郑” “怎么了窟坐?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绵疲。 經(jīng)常有香客問我哲鸳,道長,這世上最難降的妖魔是什么盔憨? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任徙菠,我火速辦了婚禮,結(jié)果婚禮上般渡,老公的妹妹穿的比我還像新娘懒豹。我一直安慰自己芙盘,他們只是感情好驯用,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儒老,像睡著了一般蝴乔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驮樊,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天薇正,我揣著相機與錄音,去河邊找鬼囚衔。 笑死挖腰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的练湿。 我是一名探鬼主播猴仑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肥哎!你這毒婦竟也來了辽俗?” 一聲冷哼從身側(cè)響起疾渣,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崖飘,沒想到半個月后榴捡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡朱浴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年吊圾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翰蠢。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡街夭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躏筏,到底是詐尸還是另有隱情板丽,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布趁尼,位于F島的核電站埃碱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酥泞。R本人自食惡果不足惜砚殿,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芝囤。 院中可真熱鬧似炎,春花似錦、人聲如沸悯姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悯许。三九已至仆嗦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間先壕,已是汗流浹背瘩扼。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垃僚,地道東北人集绰。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像谆棺,于是被迫代替她去往敵國和親栽燕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 來自深入理解ES6第十二章,由于最近業(yè)務(wù)中經(jīng)常用到纫谅,記錄一下 這里內(nèi)容都太學(xué)術(shù)了炫贤,有一篇簡單介紹Proxy作用的文...
    NowhereToRun閱讀 1,011評論 0 1
  • Proxy 概述 Proxy 用于修改某些操作的默認行為,等同于在語言層面做出修改付秕,所以屬于一種“元編程”(met...
    pauljun閱讀 3,251評論 0 1
  • defineProperty() 學(xué)習(xí)書籍《ECMAScript 6 入門 》 Proxy Proxy 用于修改某...
    Bui_vlee閱讀 654評論 0 1
  • Proxy Proxy可以理解成兰珍,在目標對象之前架一層‘攔截’,外界對該對象的訪問询吴,都必須先通過這層攔截掠河,因此提供...
    nomooo閱讀 1,002評論 0 4
  • 驚醒是在剎那唠摹,似一只夜行的貓踩碎了寧靜。- 沒關(guān)掉的電腦里響著蔡淳佳溫暖的歌音奉瘤,她在說依戀勾拉,那似一種遙遠的情懷,且...
    渝青鸞閱讀 332評論 0 0