代理和反射是ES6新增的兩個特性,兩者之間是協調合作的關系,它們的具體功能將在接下來的章節中分別講解。
## 一、代理
  ES6引入代理(Proxy)地目的是攔截對象的內置操作,注入自定義的邏輯,改變對象的默認行為。也就是說,將某些JavaScript內部的操作暴露了出來,給予開發人員更多的權限。這其實是一種元編程(metaprogramming)的能力,即把代碼看成數據,對代碼進行編程,改變代碼的行為。
  在ES6中,代理是一種特殊的對象,如果要使用,需要像下面這樣先生成一個Proxy實例。
~~~
new Proxy(target, handler);
~~~
  構造函數Proxy()有兩個參數,其中target是要用代理封裝的目標對象,handler也是一個對象,它的方法被稱為陷阱(trap),用于指定攔截后的行為。下面是一個代理的簡單示例。
~~~
var obj = {},
handler = {
set(target, property, value, receiver) {
target[property] = "hello " + value;
}
},
p = new Proxy(obj, handler);
p.name = "strick";
console.log(p.name); //"hello strick"
~~~
  在上面的代碼中,p是一個Proxy實例,它的目標對象是obj,使用了屬性相關的陷阱:set()方法。當它寫入obj的name屬性時,會對其進行攔截,在屬性值之前加上“hello ”前綴。除了上例使用的set()方法,ES6還給出了另外12種可用的陷阱,在后面的章節中會對它們做簡單的介紹。
**1)陷阱**
  表12羅列了目前所有可用的陷阱,第二列表示當前陷阱可攔截的行為,注意,只挑選了其中的幾個用于展示。
:-: 
:-: 表12 十三種陷阱
  目前支持的攔截就上面幾種,像typeof運算符、全等比較等操作還不被ES6支持。接下來會挑選其中的兩次個陷阱,講解它們的簡單應用。
  在JavaScript中,當讀取對象上不存在的屬性時,不會報錯而是返回undefined,這其實在某些情況下會發生歧義,現在利用陷阱中的get()方法就能改變默認行為,如下所示。
~~~
var obj = {
name: "strick"
},
handler = {
get(target, property, receiver) {
if(property in target)
return target[property];
throw "未定義的錯誤";
}
},
p = new Proxy(obj, handler);
p.name; //"strick"
p.age; //未定義的錯誤
~~~
  在get()方法中有3個參數,target是目標對象(即obj),property是讀取的屬性的名稱(即“name”和“age”),receiver是當前的Proxy實例(即p)。在讀取屬性時,會用in運算符判斷當前屬性是否存在,如果存在就返回相應的屬性值,否則就會拋出錯誤,這樣就能避免歧義的出現。
  在眾多陷阱中,只有apply()和construct()的目標對象得是函數。以apply()方法為例,它有3個參數,target是目標函數,thisArg是this的指向,argumentsList是函數的參數序列,它的具體使用如下所示。
~~~
function getName(name) {
return name;
}
var obj = {
prefix: "hello "
},
handler = {
apply(target, thisArg, argumentsList) {
if(thisArg && thisArg.prefix)
return target(thisArg.prefix + argumentsList[0]);
return target(...argumentsList);
}
},
p = new Proxy(getName, handler);
p("strick"); //"strick"
p.call(obj, "strick"); //"hello strick"
~~~
  p是一個Proxy實例,p("strick")是一次普通的函數調用,此時雖然攔截了,但是仍然會把參數原樣傳過去;而p.call(obj, "strick")是間接的函數調用,此時會給第一個參數添加前綴,從而改變函數最終的返回值。
**2)撤銷代理**
  Proxy.revocable()方法能夠創建一個可撤銷的代理,它能接收兩個參數,其含義與構造函數Proxy()中的相同,但返回值是一個對象,包含兩個屬性,如下所列。
  (1)proxy:新生成的Proxy實例。
  (2)revoke:撤銷函數,它沒有參數,能把與它一起生成的Proxy實例撤銷掉。
  下面是一個簡單的示例,obj是目標對象,handler是陷阱對象,傳遞給Proxy.revocable()后,通過對象解構將返回值賦給了proxy和revoke兩個變量。
~~~
var obj = {},
handler = {};
let {proxy, revoke} = Proxy.revocable(obj, handler);
revoke();
delete proxy.name; //類型錯誤
typeof proxy; //"object"
~~~
  在調用revoke()函數后,就不能再對proxy進行攔截了。像上例使用delete運算符,就會拋出類型錯誤,但像typeof之類的不可攔截的運算符還是可以成功執行的。
**3)原型**
  代理可以成為其它對象的原型,就像下面這樣。
~~~
var obj = {
name: "strick"
},
handler = {
get(target, property, receiver) {
if(property == "name")
return "hello " + target[property];
return true;
}
},
p = new Proxy({}, handler);
Object.setPrototypeOf(obj, p); //obj的原型指向Proxy實例
obj.name; //"strick"
obj.age; //true
~~~
  p是一個Proxy實例,它會攔截屬性的讀取操作,obj的原型指向了p,注意,p的目標對象不是obj。當obj讀取name屬性時,不會觸發攔截,因為name是自有屬性,所以不會去原型上查找,最終得到的結果是沒有前綴的“strick”。之前的代理都是直接作用于相關對象(例如上面的obj),因此只要執行可攔截的動作就會被處理,但現在中間隔了個原型,有了更多的限制。而在讀取age屬性時,由于自有屬性中沒有它,因此就會去原型上查找,從而觸發了攔截操作,返回了true。
## 二、反射
  反射(Reflect)向外界暴露了一些底層操作的默認行為,它是一個沒有構造函數的內置對象,類似于Math對象,其所有方法都是靜態的。代理中的每個陷阱都會對應一個同名的反射方法(例如Reflect.set()、Reflect.ownKeys()等),而每個反射方法又都會關聯到對應代理所攔截的行為(例如in運算符、Object.defineProperty()等),這樣就能保證某個操作的默認行為可隨時被訪問到。反射讓對象的內置行為變得更加嚴謹、合理與便捷,具體表現如下所列。
  (1)參數的檢驗更為嚴格,Object的getPrototypeOf()、isExtensible()等方法會將非對象的參數自動轉換成相應的對象(例如字符串轉換成String對象,如下代碼所示),而關聯的反射方法卻不會這么做,它會直接拋出類型錯誤。
~~~
Object.getPrototypeOf("strick") === String.prototype; //true
Reflect.getPrototypeOf("strick"); //類型錯誤
~~~
  (2)更合理的返回值,Object.setPrototypeOf()會返回它的第一個參數,而Reflect的同名方法會返回一個布爾值,后者能更直觀的反饋設置是否成功,兩個方法的對比如下所示。
~~~
var obj = {};
Object.setPrototypeOf(obj, String) === obj; //true
Reflect.setPrototypeOf(obj, String); //true
~~~
  (3)用方法替代運算符,反射能以調用方法的形式完成new、in、delete等運算符的功能,在下面的示例中,先使用運算符,再給出對應的反射方法。
~~~
function func() { }
new func();
Reflect.construct(func, []);
var people = {
name: "strick"
};
"name" in people;
Reflect.has(people, "name");
delete people["name"];
Reflect.deleteProperty(people, "name");
~~~
  (4)避免冗長的方法調用,以apply()方法為例,如下所示。
~~~
Function.prototype.apply.call(Math.ceil, null, [2.5]); //3
Reflect.apply(Math.ceil, null, [2.5]); //3
~~~
  上面代碼的第一條語句比較繞,需要將其分解成兩部分:Function.prototype.apply()和call()。ES5規定apply()和call()兩個方法在最后都要調用一個有特殊功能的內部函數,如下代碼所示,func參數表示調用這兩個方法的函數。
~~~
[[Call]](func, thisArg, argList)
~~~
  內部函數的功能就是在調用func()函數時,傳遞給它的參數序列是argList,其內部的this指向了thisArg。當執行第一條語句時,傳遞給\[\[Call\]\]函數的三個參數如下所示。
~~~
[[Call]](Function.prototype.apply, Math.ceil, [null, [2.5]])
~~~
  接下來會調用原型上的apply()方法,由于其this指向了Math.ceil(即當前調用apply()方法的是Math.ceil),因此\[\[Call\]\]函數的第一個參數就是Math.ceil,如下所示。
~~~
[[Call]](Math.ceil, null, [2.5])
//相當于
Math.ceil.apply(null, [2.5])
~~~
*****
> 原文出處:
[博客園-ES6躬行記](https://www.cnblogs.com/strick/category/1372951.html)
[知乎專欄-ES6躬行記](https://zhuanlan.zhihu.com/pwes6)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020