Proxy

眾所周知军浆,vue3.0快要來(lái)了,用ts進(jìn)行了重構(gòu)椅挣,而且用Proxy替代了Object.defineProperty()头岔,所以在3.0真的到來(lái)之前,先了解一下Proxy鼠证。

Proxy

Proxy 用于修改某些操作的默認(rèn)行為峡竣,等同于在語(yǔ)言層面做出修改,所以屬于一種“元編程”量九,即對(duì)編程語(yǔ)言進(jìn)行編程适掰。

Proxy 可以理解成,在目標(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);

new Proxy()表示生成一個(gè)Proxy實(shí)例川队,target參數(shù)表示所要攔截的目標(biāo)對(duì)象力细,handler是一個(gè)配置的對(duì)象,對(duì)于每一個(gè)被代理的操作固额,需要提供一個(gè)對(duì)應(yīng)的處理函數(shù)艳汽,該函數(shù)將攔截對(duì)應(yīng)的操作。

要使得Proxy起作用对雪,必須針對(duì)Proxy實(shí)例(上例是proxy對(duì)象)進(jìn)行操作河狐,而不是針對(duì)目標(biāo)對(duì)象(上例是空對(duì)象)進(jìn)行操作。如果handler沒(méi)有設(shè)置任何攔截瑟捣,那就等同于直接通向原對(duì)象馋艺。

下面看下handler支持配置的鏈接操作

    let obj = new Proxy({}, {
        // 攔截對(duì)象屬性的讀取,比如proxy.foo和proxy['foo']
        get: function (target, propKey, receiver) {
            console.log('sonme code ....')
        },
        // 攔截對(duì)象屬性的設(shè)置,比如proxy.foo = v或proxy['foo'] = v迈套,返回一個(gè)布爾值
        set: function (target, propKey, value, receiver) {
            console.log('sonme code ....')
        },
        // 攔截propKey in proxy的操作捐祠,返回一個(gè)布爾值
        has: function (target, propKey, receiver) {
            console.log('sonme code ....')
        },
        // 攔截delete proxy[propKey]的操作,返回一個(gè)布爾值桑李。
        deleteProperty: function (target, propKey) {
            console.log('sonme code ....')
        },
        // 攔截Object.getOwnPropertyNames(proxy)踱蛀、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)贵白、for...in循環(huán)率拒,
        // 返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名禁荒,
        // 而Object.keys()的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性猬膨。
        ownKeys:function(target){
            console.log('sonme code ....')
        },
        // 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象
        getOwnPropertyDescriptor: function (target, propKey) {
            console.log('sonme code ....')
        },
        // 攔截Object.defineProperty(proxy, propKey, propDesc)呛伴、Object.defineProperties(proxy, propDescs)勃痴,返回一個(gè)布爾值谒所。
        defineProperty: function (target, propKey, propDesc) {
            console.log('sonme code ....')
        },
        // 攔截Object.preventExtensions(proxy),返回一個(gè)布爾值沛申。
        preventExtensions: function (target) {
            console.log('sonme code ....')
        },
        // 攔截Object.getPrototypeOf(proxy)劣领,返回一個(gè)對(duì)象
        getPrototypeOf: function (target) {
            console.log('sonme code ....')
        },
        // 攔截Object.isExtensible(proxy),返回一個(gè)布爾值铁材。
        isExtensible: function (target) {
            console.log('sonme code ....')
        },
        // 攔截Object.setPrototypeOf(proxy, proto)剖踊,返回一個(gè)布爾值。
        // 如果目標(biāo)對(duì)象是函數(shù)衫贬,那么還有兩種額外操作可以攔截德澈。
        setPrototypeOf: function (target, proto) {
            console.log('sonme code ....')
        },
        // 攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)固惯、
        // proxy.call(object, ...args)梆造、proxy.apply(...)。
        apply: function (target, object, args) {
            console.log('sonme code ....')
        },
        // 攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作葬毫,比如new proxy(...args)镇辉。
        construct:function (target, args) {

        }
    });

下面詳細(xì)介紹一下這些攔截方法

get()

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

下面的例子使用get攔截屹逛,實(shí)現(xiàn)數(shù)組讀取負(fù)數(shù)的索引。

function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

set()
set方法用來(lái)攔截某個(gè)屬性的賦值操作汛骂,可以接受四個(gè)參數(shù)罕模,依次為目標(biāo)對(duì)象、屬性名帘瞭、屬性值和 Proxy 實(shí)例本身淑掌,其中最后一個(gè)參數(shù)可選。

假定Person對(duì)象有一個(gè)age屬性蝶念,該屬性應(yīng)該是一個(gè)不大于 200 的整數(shù)抛腕,那么可以使用Proxy保證age的屬性值符合要求

let validator = {
  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');
      }
    }

    // 對(duì)于滿足條件的 age 屬性以及其他屬性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 報(bào)錯(cuò)
person.age = 300 // 報(bào)錯(cuò)

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"

上面代碼中舷夺,變量p是 Proxy 的實(shí)例苦酱,當(dāng)它作為函數(shù)調(diào)用時(shí)(p())售貌,就會(huì)被apply方法攔截,返回一個(gè)字符串疫萤。

has()
has方法用來(lái)攔截HasProperty操作颂跨,即判斷對(duì)象是否具有某個(gè)屬性時(shí),這個(gè)方法會(huì)生效扯饶。典型的操作就是in運(yùn)算符恒削。

has方法可以接受兩個(gè)參數(shù),分別是目標(biāo)對(duì)象尾序、需查詢的屬性名钓丰。

var handler = {
  has (target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false

如果原對(duì)象不可配置或者禁止擴(kuò)展,has攔截會(huì)報(bào)錯(cuò).

construct()
construct方法用于攔截new命令每币,construct方法可以接受三個(gè)參數(shù)携丁。
target:目標(biāo)對(duì)象
args:構(gòu)造函數(shù)的參數(shù)對(duì)象
newTarget:創(chuàng)造實(shí)例對(duì)象時(shí),new命令作用的構(gòu)造函數(shù)
construct方法返回的必須是一個(gè)對(duì)象兰怠,否則會(huì)報(bào)錯(cuò)梦鉴。

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

(new p(1)).value
// "called: 1"
// 10

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

目標(biāo)對(duì)象自身的不可配置(configurable)的屬性肥橙,不能被deleteProperty方法刪除,否則報(bào)錯(cuò)

defineProperty()
defineProperty方法攔截了Object.defineProperty操作

var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不會(huì)生效

defineProperty方法返回false秸侣,導(dǎo)致添加新屬性總是無(wú)效.
如果目標(biāo)對(duì)象不可擴(kuò)展(non-extensible)存筏,則defineProperty不能增加目標(biāo)對(duì)象上不存在的屬性,否則會(huì)報(bào)錯(cuò)味榛。另外方篮,如果目標(biāo)對(duì)象的某個(gè)屬性不可寫(writable)或不可配置(configurable),則defineProperty方法不得改變這兩個(gè)設(shè)置

getOwnPropertyDescriptor()
getOwnPropertyDescriptor方法攔截Object.getOwnPropertyDescriptor()励负,返回一個(gè)屬性描述對(duì)象或者undefined

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()
getPrototypeOf()方法主要用來(lái)攔截獲取對(duì)象原型藕溅。具體來(lái)說(shuō),攔截下面這些操作继榆。
Object.prototype.proto
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof

var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true

getPrototypeOf方法攔截Object.getPrototypeOf()巾表,返回proto對(duì)象
getPrototypeOf方法的返回值必須是對(duì)象或者null,否則報(bào)錯(cuò)略吨。另外集币,如果目標(biāo)對(duì)象不可擴(kuò)展(non-extensible), getPrototypeOf方法必須返回目標(biāo)對(duì)象的原型對(duì)象

isExtensible()

var p = new Proxy({}, {
  isExtensible: function(target) {
    console.log("called");
    return true;
  }
});

Object.isExtensible(p)
// "called"
// true

上面代碼設(shè)置了isExtensible方法翠忠,在調(diào)用Object.isExtensible時(shí)會(huì)輸出called鞠苟。

注意,該方法只能返回布爾值,否則返回值會(huì)被自動(dòng)轉(zhuǎn)為布爾值当娱。

這個(gè)方法有一個(gè)強(qiáng)限制吃既,它的返回值必須與目標(biāo)對(duì)象的isExtensible屬性保持一致,否則就會(huì)拋出錯(cuò)誤.

ownKeys()
ownKeys方法用來(lái)攔截對(duì)象自身屬性的讀取操作跨细。具體來(lái)說(shuō)鹦倚,攔截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in循環(huán)

let target = {
  a: 1,
  b: 2,
  c: 3
};

let handler = {
  ownKeys(target) {
    return ['a'];
  }
};

let proxy = new Proxy(target, handler);

Object.keys(proxy)
// [ 'a' ]

preventExtensions()
preventExtensions方法攔截Object.preventExtensions()冀惭。該方法必須返回一個(gè)布爾值震叙,否則會(huì)被自動(dòng)轉(zhuǎn)為布爾值。

這個(gè)方法有一個(gè)限制散休,只有目標(biāo)對(duì)象不可擴(kuò)展時(shí)(即Object.isExtensible(proxy)為false)媒楼,proxy.preventExtensions才能返回true,否則會(huì)報(bào)錯(cuò)

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    console.log('called');
// 為了防止出現(xiàn)這個(gè)問(wèn)題戚丸,通常要在proxy.preventExtensions方法里面匣砖,調(diào)用一次Object.preventExtensions
    Object.preventExtensions(target);
    return true;
  }
});

Object.preventExtensions(proxy)
// "called"
// Proxy {}

setPrototypeOf()
setPrototypeOf方法主要用來(lái)攔截Object.setPrototypeOf方法。

var handler = {
  setPrototypeOf (target, proto) {
    throw new Error('Changing the prototype is forbidden');
  }
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

Proxy.revocable()
Proxy.revocable方法返回一個(gè)可取消的 Proxy 實(shí)例

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable方法返回一個(gè)對(duì)象昏滴,該對(duì)象的proxy屬性是Proxy實(shí)例猴鲫,revoke屬性是一個(gè)函數(shù),可以取消Proxy實(shí)例谣殊。上面代碼中拂共,當(dāng)執(zhí)行revoke函數(shù)之后,再訪問(wèn)Proxy實(shí)例姻几,就會(huì)拋出一個(gè)錯(cuò)誤宜狐。

Proxy.revocable的一個(gè)使用場(chǎng)景是,目標(biāo)對(duì)象不允許直接訪問(wèn)蛇捌,必須通過(guò)代理訪問(wèn)抚恒,一旦訪問(wèn)結(jié)束,就收回代理權(quán)络拌,不允許再次訪問(wèn)

this問(wèn)題
Proxy 代理的情況下俭驮,目標(biāo)對(duì)象內(nèi)部的this關(guān)鍵字會(huì)指向 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

兼容性

image.png

本文參考:
ECMAScript6 入門

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末混萝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萍恕,更是在濱河造成了極大的恐慌逸嘀,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允粤,死亡現(xiàn)場(chǎng)離奇詭異崭倘,居然都是意外死亡翼岁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門司光,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)琅坡,“玉大人,你說(shuō)我怎么就攤上這事飘庄∧匀洌” “怎么了购撼?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵跪削,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我迂求,道長(zhǎng)碾盐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任揩局,我火速辦了婚禮毫玖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凌盯。我一直安慰自己付枫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布驰怎。 她就那樣靜靜地躺著阐滩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪县忌。 梳的紋絲不亂的頭發(fā)上掂榔,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音症杏,去河邊找鬼装获。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厉颤,可吹牛的內(nèi)容都是我干的穴豫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逼友,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绩郎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起翁逞,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肋杖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挖函,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體状植,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浊竟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了津畸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片振定。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肉拓,靈堂內(nèi)的尸體忽然破棺而出后频,到底是詐尸還是另有隱情,我是刑警寧澤暖途,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布卑惜,位于F島的核電站,受9級(jí)特大地震影響驻售,放射性物質(zhì)發(fā)生泄漏露久。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一欺栗、第九天 我趴在偏房一處隱蔽的房頂上張望毫痕。 院中可真熱鬧,春花似錦迟几、人聲如沸消请。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)臊泰。三九已至,卻和暖如春存哲,著一層夾襖步出監(jiān)牢的瞬間因宇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工祟偷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留察滑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓修肠,卻偏偏與公主長(zhǎng)得像贺辰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嵌施,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345