ES6 Reflect 與 Proxy

一彼棍、Reflect 反射

1. Reflect.get(target, name, receiver)

Reflect是一個(gè)全局對(duì)象

Reflect.get方法查找并返回target對(duì)象的name屬性,如果沒有該屬性,則返回undefined戴而。

注意:target只能為一個(gè)對(duì)象

如果name屬性部署了讀取函數(shù)(getter)苹熏,則讀取函數(shù)的this綁定receiver

注意:bas是一個(gè)屬性而不是函數(shù)移盆,因此不能用call來綁定receiver悼院,只能用Reflect來實(shí)現(xiàn)

普通函數(shù)的則可以直接用call調(diào)用

總結(jié)Reflect.get()的兩種用法

// 用法一
var obj = {
  name: '阿偉',
  age: '18'
}
Reflect.get(obj, 'name') // "阿偉"
// 用法二
var obj1 = {
  a: 1,
  b: 2,
  get c() {
    return this.a + this.b
  }
}
obj1.c // 3
var obj2 = {
  a: 3,
  b: 4
}
Reflect.get(obj1, 'c', obj2) // 7

3. Reflect.set(target, name, value, receiver)

Reflect.set方法設(shè)置target對(duì)象的name屬性等于value

如果name屬性設(shè)置了賦值函數(shù),則賦值函數(shù)的this綁定receiver

3. Reflect.apply(func, thisArg, args)

Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args)咒循,用于綁定this對(duì)象后執(zhí)行給定函數(shù)据途。

可將Reflect 的 set、get叙甸、apply 近似的理解為下面這樣

target.get('name') <===> Reflect.get(target, 'name', receiver) // target 為一個(gè)對(duì)象
target.set('name', 'xxx') <===> Reflect.set(target, 'name', 'xxx', receiver)
函數(shù).apply(this, args) <===> Reflect.apply(函數(shù), this, args)

4. Reflect.construct(target, args)

Reflect.construct方法等同于new target(...args)颖医,這提供了一種不使用new,來調(diào)用構(gòu)造函數(shù)的方法裆蒸。

function Greeting(name) {
  this.name = name;
}

// new 的寫法
const instance = new Greeting('張三');

// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);

注意:Reflect.construct()方法的第一個(gè)參數(shù)target必須是函數(shù)

二熔萧、Proxy 代理

Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”(中介),外界對(duì)該對(duì)象的訪問佛致,都必須先通過這層攔截贮缕,因此提供了一種機(jī)制,可以對(duì)外界的訪問進(jìn)行過濾和改寫俺榆。Proxy 這個(gè)詞的原意是代理感昼,用在這里表示由它來“代理”某些操作,可以譯為“代理器”罐脊。

ES6原生提供Proxy構(gòu)造函數(shù)定嗓,用來生成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ì)象反症,用來定制攔截行為辛块。

set
get

用圖來理解Proxy就是下面這樣

例子:使被代理對(duì)象中的lives始終大于0

但這樣存在一個(gè)問題,用戶還是可以直接訪問這個(gè)變量并對(duì)其進(jìn)行修改

因此我們需要讓用戶無法訪問這個(gè)變量铅碍,有兩種做法

  1. 可以用閉包來隱藏lives
  2. 不給這個(gè)被代理的對(duì)象名字润绵,即讓它為匿名對(duì)象(常見套路)

下面是另一個(gè)攔截讀取屬性行為的例子。

上面代碼中胞谈,作為構(gòu)造函數(shù)尘盼,Proxy接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是所要代理的目標(biāo)對(duì)象(上例是一個(gè)空對(duì)象)烦绳,即如果沒有Proxy的介入卿捎,操作原來要訪問的就是這個(gè)對(duì)象;第二個(gè)參數(shù)是一個(gè)配置對(duì)象径密,對(duì)于每一個(gè)被代理的操作午阵,需要提供一個(gè)對(duì)應(yīng)的處理函數(shù),該函數(shù)將攔截對(duì)應(yīng)的操作享扔。比如底桂,上面代碼中,配置對(duì)象有一個(gè)get方法惧眠,用來攔截對(duì)目標(biāo)對(duì)象屬性的訪問請(qǐng)求籽懦。get方法的兩個(gè)參數(shù)分別是目標(biāo)對(duì)象和所要訪問的屬性》湛可以看到暮顺,由于攔截函數(shù)總是返回35厅篓,所以訪問任何屬性都得到35

注意:要使得Proxy起作用拖云,必須針對(duì)Proxy實(shí)例(上例是proxy對(duì)象)進(jìn)行操作贷笛,而不是針對(duì)目標(biāo)對(duì)象(上例是空對(duì)象)進(jìn)行操作应又。

用代理實(shí)現(xiàn)“不可能實(shí)現(xiàn)的”自動(dòng)填充對(duì)象

創(chuàng)建一個(gè)Tree()函數(shù)來實(shí)現(xiàn)以下特性:

> var tree = Tree();
> tree
    { }
> tree.branch1.branch2.twig = "green";
> tree
    { branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
    { branch1: { branch2: { twig: "green" },
                 branch3: { twig: "yellow" }}}

普通對(duì)象是無法做到的

但是通過代理宙项,我們只用幾行代碼就可以輕松實(shí)現(xiàn),然后只需要接入tree.[[Get]]()就可以了

function Tree() {
  return new Proxy({}, handler);
}
var handler = {
  get: function(target, key, receiver) {
    if (!(key in target)) {
      target[key] = Tree(); // 自動(dòng)創(chuàng)建一個(gè)子樹 
    }
    return Reflect.get(target, key, receiver);
  }
};

總結(jié)Proxy的這種做法就是“如果不存在株扛,則先創(chuàng)建尤筐,再讀取”。

實(shí)例:Web 服務(wù)的客戶端

Proxy對(duì)象可以攔截目標(biāo)對(duì)象的任意屬性洞就,這使得它很合適用來寫Web服務(wù)的客戶端盆繁。

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});

上面代碼新建了一個(gè)Web服務(wù)的接口,這個(gè)接口返回各種數(shù)據(jù)旬蟋。Proxy可以攔截這個(gè)對(duì)象的任意屬性油昂,所以不用為每一種數(shù)據(jù)寫一個(gè)適配方法,只要寫一個(gè)Proxy攔截就可以了倾贰。

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

service.employees()實(shí)際上是不存在的冕碟,當(dāng)讀取service.employees()時(shí),就會(huì)執(zhí)行httpGet(baseUrl + '/' + propKey)匆浙,因此service.employees()就等價(jià)于httpGet('http://example.com/data/employees')

三安寺、Vue 3 將用 Proxy 改寫

<div id="app">
   姓名:{{obj.name}}<br> 
   年齡:{{obj.age}}<br>
   <button @click=createAge>創(chuàng)建年齡</button>
</div>
var app = new Vue({
  el: '#app',
  data: {
    obj:{
      name: '阿偉'
    }
  },
  methods:{
    createAge(){
      this.obj.age = 18
      console.log(this.obj)
    }
  }
})

我們會(huì)發(fā)現(xiàn)點(diǎn)擊按鈕后,頁面上并沒有顯示'18'首尼,但控制臺(tái)中卻打印出了age

為什么age創(chuàng)建之后卻無法更新到頁面的DOM上呢挑庶?

這是因?yàn)閂ue是通過object.defineProperty來實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式的

受現(xiàn)代JavaScript的限制 (而且Object.observe也已經(jīng)被廢棄),Vue 無法檢測(cè)到對(duì)象屬性的添加或刪除软能。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì)屬性執(zhí)行 getter/setter 轉(zhuǎn)化迎捺,所以屬性必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的

上面代碼中name已經(jīng)存在,因此app.name是響應(yīng)式的查排,而age不存在凳枝,因此app.age是非響應(yīng)式的。

解決:

  1. data加入age即可
  1. 使用 Proxy 代理

然后刷新DOM

var proxy = new Proxy(data.obj, {// 注意Proxy只會(huì)代理當(dāng)前對(duì)象雹嗦,并不會(huì)代理子對(duì)象范舀,因此這里是data.obj
  set(target, name, value){
    // refreshDom()
    return Reflect.set(target, name, value)
  }
})

參考:

  1. Reflect ES6 阮一峰
  2. Proxy ES6 阮一峰
  3. 深入淺出 ES6(十二):代理 Proxies
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市了罪,隨后出現(xiàn)的幾起案子锭环,更是在濱河造成了極大的恐慌,老刑警劉巖泊藕,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辅辩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玫锋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蛾茉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撩鹿,你說我怎么就攤上這事谦炬。” “怎么了节沦?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵键思,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我甫贯,道長(zhǎng)吼鳞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任叫搁,我火速辦了婚禮赔桌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渴逻。我一直安慰自己疾党,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布裸卫。 她就那樣靜靜地躺著仿贬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪墓贿。 梳的紋絲不亂的頭發(fā)上茧泪,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音聋袋,去河邊找鬼队伟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛幽勒,可吹牛的內(nèi)容都是我干的嗜侮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼啥容,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锈颗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咪惠,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤击吱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遥昧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體覆醇,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朵纷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了永脓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袍辞。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖常摧,靈堂內(nèi)的尸體忽然破棺而出搅吁,到底是詐尸還是另有隱情,我是刑警寧澤排宰,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布似芝,位于F島的核電站那婉,受9級(jí)特大地震影響板甘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜详炬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一盐类、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呛谜,春花似錦在跳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聚凹,卻和暖如春割坠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妒牙。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工彼哼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人湘今。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓敢朱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親摩瞎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拴签,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353