摘要:是中新增的特性。首先來說,的提出是為了整合之前中存在的一些不太合理的地方。表示當(dāng)前對象是否可擴(kuò)展,返回一個(gè)布爾值。更完美的枚舉很多代碼使用字符串普通或凍結(jié)的對象作為枚舉。通過記錄這些訪問和修改信息,能記錄下對這個(gè)對象的所有操作記錄。
Reflect
Reflect 是ES6中新增的特性。它是一個(gè)普通對象,下面有13個(gè)靜態(tài)方法(enumerate在最終的發(fā)布版中被移除),可以再全局下訪問。它不能當(dāng)做函數(shù)調(diào)用,也不可以用new操作符生成一個(gè)實(shí)例。
首先來說,Reflect的提出是為了整合之前JS中存在的一些不太合理的地方。
1)更加有用的返回值
Object.getOwnPropertyNames(Reflect) // ["defineProperty", "deleteProperty", "apply", "construct", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"]Reflect.apply
功能跟Function.prototype.apply類似。
var a = [1,2,3]; Math.max.apply(null, a);// 3 Reflect.apply(Math.max, null, a); // 3Reflect.isExtensible / Reflect.preventExtensions
Reflect.preventExtensions用于讓一個(gè)對象變?yōu)椴豢蓴U(kuò)展。它返回一個(gè)布爾值,表示是否操作成功。
Reflect.isExtensible表示當(dāng)前對象是否可擴(kuò)展, 返回一個(gè)布爾值。
var o = {}; Reflect.isExtensible(o);// true Object.isExtensible(o);// true Object.freeze(o); Reflect.isExtensible(o);// false Object.isExtensible(o);// false // 這兩方法的區(qū)別 Object.isExtensible(1) // false Reflect.isExtensible(1) // 報(bào)錯(cuò) var newO = {}; Reflect.isExtensible(newO);// true Reflect.preventExtensions(newO); // true 返回true,表示該操作成功 Reflect.isExtensible(newO);// falseReflect.set / Reflect.get
設(shè)置和獲取對象屬性, 這兩個(gè)方法還允許接受一個(gè)reciever,用于重定義setter和getter方法的上下文。
var o = {}; Reflect.set(o, "key", "value"); console.log(o); // {key: "value"} Reflect.get(o, "key"); // "value" Reflect.get(o, "nokey"); // undefined
下面演示一下receiver的使用方法
var o = { name: "O" } Object.defineProperty(o, "sayHi", { get(){ return "hi, I am " + this.name } }) o.sayHi; // "hi, I am O" var receiver = { name: "receiver" } // 下面是關(guān)鍵, 看好咯~ Reflect.get(o, "sayHi", receiver); // "hi, I am receiver" // 下面試驗(yàn)了下Proxy的用法,可以忽略 var p = new Proxy(o, { get(o, k, p){ return o[k] ? Reflect.get(o, k, receiver) : undefined; }, set(o, k, v, p){ v += +new Date(); Reflect.set(o, k, v, p) } }); p.sayHi; // "hi, I am receiver" p.t = "time:"; p.t; // "time:1530865528713"Reflect.ownKeys
Reflect.ownKeys方法用于返回對象的所有屬性數(shù)組。
這個(gè)數(shù)組的排序是根據(jù): 先顯示數(shù)字, 數(shù)字根據(jù)大小排序,然后是 字符串根據(jù)插入的順序排序, 最后是symbol類型的key也根據(jù)插入插入順序排序。
出現(xiàn)這種排序是因?yàn)椋憬o一個(gè)對象屬性賦值時(shí)候, 對象的key的排序規(guī)則就是先數(shù)字, 在字符串, 最后是symbol類型的數(shù)據(jù)。
Reflect.ownKeys(JSON); // ["parse", "stringify", Symbol(Symbol.toStringTag)] Object.getOwnPropertyNames(JSON); // ["parse", "stringify"] Object.getOwnPropertySymbols(JSON); // [Symbol(Symbol.toStringTag)]Reflect.has
用于判斷對象是否具有某個(gè)屬性
Reflect.has(JSON, "parse"); // true Reflect.has(JSON, "nokeyxx"); // false Reflect.has(1, "parse");// Uncaught TypeError: Reflect.has called on non-objectReflect.construct
功能類似于new操作符
var a = new Array(3); console.log(a); // [empty × 3] var b = Reflect.construct(Array, [3]) console.log(b); // [empty × 3]Reflect.defineProperty / Reflect.deleteProperty
Reflect.defineProperty 對應(yīng)于Object.DefineProperty;
Reflect.deleteProperfy 對應(yīng)于 delete 語句;
var o = {}; Object.defineProperty(o, "sayHi", { configurable: true, get(k){ return this[k] ? "Hi, I am " + this[k] : "I have no name"; } }); o.sayHi;// "I have no name" Reflect.defineProperty(o, "sayHello", { configurable: true, get(k){ return this[k] ? "Hi, I am " + this[k] : "I have no name"; } }) o.sayHello; // "I have no name" // 演示屬性刪除方法 delete o.sayHi; // true Reflect.deleteProperty(o, "sayHello"); // trueReflect.setPrototypeOf / Reflect.getPrototypeOf
這兩個(gè)方法用于設(shè)置和訪問對象的原型__proto__
function A(){}; A.prototype.name= "A"; Reflect.getPrototypeOf(A) // ? () { [native code] } var a = new A(); Reflect.getPrototypeOf(a) // {name: "A", constructor: ?} a.name; // "A" var c = {}; Reflect.setPrototypeOf(c, A.prototype); c.name; // "A" A.prototype.newName = "C"; c.newName; // "C"Reflect.getOwnPropertyDescriptor
基本等同于Object.getOwnPropertyDescriptor,用于得到指定屬性的描述對象。
Reflect.getOwnPropertyDescriptor(JSON, "parse"); // {value: ?, writable: true, enumerable: false, configurable: true} var c = {}; Reflect.defineProperty(c, "name", { configurable: true, enumerable: true, value: "CCC", writable: false }); Reflect.getOwnPropertyDescriptor(c, "name"); // {value: "CCC", writable: false, enumerable: true, configurable: true} c.name; // "CCC" c.name = 111; c.name; // "CCC", 因?yàn)閣ritable=== falseProxy
Proxy 對象用于定義JS對象的基本操作行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)。
下面看一個(gè)修改對象get行為的demo:
var obj = { n : 1}; var p = new Proxy(obj, { get(t, k){ if( Reflect.has(t, k) ){ return t[k] + 100; } else { throw Error("no the property") } } }); p.n; // 101 p.p; // Uncaught Error: no the property
關(guān)于Proxy具體可以重新定義哪些基本操作:
defineProperty
deleteProperty
apply
construct
get
getOwnPropertyDescriptor
getPrototypeOf
has
isExtensible
ownKeys
preventExtensions
set
setPrototypeOf
這個(gè)列表跟 Reflect 具有的靜態(tài)方法是一致的。更多信息可以參考Proxy可以處理的方法列表 的描述
不說上面這些值基本操作,我們列舉下一下Proxy的一些使用場景。
設(shè)置默認(rèn)值如果訪問一個(gè)對象中還沒有初始化的值,Proxy可以通過代理get方法,返回一個(gè)默認(rèn)值。
function setDefault(defaults) { const handler = { get(t, k) { return Reflect.get(t, k) || defaults[k]; } } return new Proxy({},handler); } const d = setDefault({ name: "name" }); const log = console.log; log(d.name); // "name" d.name = "new name"; log(d.name); // "new name" log(d);隱藏私有屬性
Proxy可以代理對象的基礎(chǔ)操作,我們通過代理 get 方法,控制外部對對象內(nèi)部屬性的方法。這樣便可以達(dá)到隱藏某些特性(私有屬性)的目的。
const log = console.log; function hideProp(obj,filter) { const handler = { get(t, k) { if(!filter(k)){ let v = Reflect.get(t, k); if(typeof v === "function"){ v = v.bind(t); } return v; } } } return new Proxy(obj,handler); } function filter(key){ return key[0] === "_"; } const o = { _p : "no access", p: "access", f: function(){ log(this._p); } }; const p = hideProp(o, filter); log(p.p); // access log(p._p); // undefined log(p.f()); // no access更完美的枚舉
很多JavaScript代碼使用字符串、普通或凍結(jié)的對象作為枚舉。這些解決方案有其類型的安全問題,而且通常容易出錯(cuò)。
Proxy可以提供一個(gè)可行的替代方案。我們采用一個(gè)簡單的鍵值對象,并通過保護(hù)它免受(甚至無意的)修改而使其更加健壯。甚至比Object.freeze更加完善。(Object.freeze不允許修改,但不會(huì)引發(fā)錯(cuò)誤,而是靜默報(bào)錯(cuò))
const log = console.log; function enumF(obj) { const handler = { get(t, k) { if(Reflect.has(t, k)){ let v = Reflect.get(t, k); if(typeof v === "function"){ v = v.bind(t); } return v; } }, set(t){ throw new TypeError("Enum is read only"); } } return new Proxy(obj,handler); } const o = { p: "access", f: function(){ log(this._p); } }; const p = enumF(o); log(p.p); // access p.p = "modified"; // TypeError // 對比Object.freeze const o1 = { p: "1" } Object.freeze(o1); o1.p = "modified"; log(o1.p);跟蹤對象變化
因?yàn)镻roxy可以截獲對象的大部分基礎(chǔ)操作,因此我們實(shí)際上是可以跟蹤的對 對象的訪問和修改。通過記錄這些訪問和修改信息,能記錄下對這個(gè)對象的所有操作記錄。
const log = console.log; function trackChange(obj, onchange) { const handler = { deleteProperty(t, k){ const oldVal = t[k]; Reflect.deleteProperty(t, k); onchange(t, k , undefined, oldVal) }, set(t,k, v){ const oldVal = t[k]; Reflect.set(t, k , v); onchange(t, k, v, oldVal); } } return new Proxy(obj,handler); } const o = { p: "access", f: function(){ log(this._p); } }; const p = trackChange(o, (t,k,newV, oldV)=>{ log(`the property: ${k} change from old value [${oldV}] to new value [${newV}]`) }); p.p = "modified"; delete p.f;Proxy實(shí)現(xiàn)單例模式
來看看通過Proxy如何實(shí)現(xiàn) 單例模式。
const log = console.log; function singleInstance(obj){ let instance, handler = { construct:function(t){ if(!instance){ instance = Reflect.construct(...arguments); } return instance; } } return new Proxy(obj, handler); } function A(){ this.v = 1; } const p = singleInstance(A); const p1 = new p(); const p2 = new p(); log(p1.v); log(p2.v); p1.v = "new value"; log(p1.v); log(p2.v);總結(jié)
總結(jié)一下,上面的幾個(gè)例子中,我們基本上代理的都是對象的get,set,deleteProperty方法。這些都是對象的常見操作。用好了Proxy可以解決很多問題,例如vue.js數(shù)據(jù)雙向綁定的特性實(shí)際上也可以使用Proxy實(shí)現(xiàn)。
說了這么多,但是并不極力推薦使用Proxy,并不是出于兼容性問題。主要是性能方面,可以肯定的是,使用了Proxy肯定不如原有對象訪問速度更快。Proxy相當(dāng)于在對象前添加了一道墻,任何操作都要先經(jīng)過Proxy處理。這肯定會(huì)增加成本。寫這篇文章的主要目的是 希望能夠了解更多的東西,為自己解決問題增加另外一種方案。
【參考資料】
ecma 262:Reflect
為什么要使用Reflect的原因
實(shí)例解析 ES6 Proxy 使用場景
ES6 Features - 10 Use Cases for Proxy
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/108182.html
摘要:即必須有返回值其中可接受三個(gè)參數(shù),為目標(biāo)對象,為屬性名,為實(shí)際接受的對象,默認(rèn)為本例中新建的,如果單獨(dú)指出一個(gè)對象,可使指出對象受到相同的方法作用。且中的必須有返回值,的不用,這也正是因?yàn)樵谒筮€會(huì)執(zhí)行所以不需要。 ES6 Proxy/Reflect Proxy 攔截器 proxy是es6的新特性,簡單來講,即是對目標(biāo)對象的屬性讀取、設(shè)置,亦或函數(shù)調(diào)用等操作進(jìn)行攔截(處理)。 let...
摘要:攔截實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如。方法等同于,這提供了一種不使用,來調(diào)用構(gòu)造函數(shù)的方法。方法對應(yīng),返回一個(gè)布爾值,表示當(dāng)前對象是否可擴(kuò)展。這是的一個(gè)提案,目前轉(zhuǎn)碼器已經(jīng)支持。別名或修飾器在控制臺(tái)顯示一條警告,表示該方法將廢除。 Proxy Proxy 這個(gè)詞的原意是代理,用在這里表示由它來代理某些操作,可以譯為代理器,即用自己的定義覆蓋了語言的原始定義。ES6 原生提供 Proxy...
摘要:下面是實(shí)現(xiàn)的函數(shù)為異步函數(shù)添加自動(dòng)超時(shí)功能超時(shí)時(shí)間異步函數(shù)包裝后的異步函數(shù)測試一下,是可以正常調(diào)用與訪問其上的屬性的好了,這便是吾輩最常用的一種方式了封裝高階函數(shù),為函數(shù)添加某些功能。 JavaScript 中的 ES6 Proxy 吾輩博客的原文地址: https://blog.rxliuli.com/p/c3... 場景 就算只是扮演,也會(huì)成為真實(shí)的自我的一部分。對人類的精神來說...
摘要:查找并返回對象的屬性例例屬性部署了讀取函數(shù)返回的是的參數(shù)對象注意如果的第一個(gè)參數(shù)不是對象,則會(huì)報(bào)錯(cuò)。它返回一個(gè)布爾值,表示是否操作成功用于返回對象的所有屬性使用和實(shí)現(xiàn)觀察者模式請參考觀察者模式 1、什么是Reflect?為操作對象而提供的新API 2、為什么要設(shè)計(jì)Reflect?(1)將Object對象的屬于語言內(nèi)部的方法放到Reflect對象上,即從Reflect對象上拿Object...
摘要:理解元編程和是屬于元編程范疇的,能介入的對象底層操作進(jìn)行的過程中,并加以影響。元編程中的元的概念可以理解為程序本身。中,便是兩個(gè)可以用來進(jìn)行元編程的特性。在之后,標(biāo)準(zhǔn)引入了,從而提供比較完善的元編程能力。 導(dǎo)讀 幾年前 ES6 剛出來的時(shí)候接觸過 元編程(Metaprogramming)的概念,不過當(dāng)時(shí)還沒有深究。今天在應(yīng)用和學(xué)習(xí)中不斷接觸到這概念,比如 mobx 5 中就用到了 Pr...
閱讀 2358·2021-09-28 09:36
閱讀 2309·2021-09-22 15:14
閱讀 3714·2019-08-30 12:47
閱讀 3084·2019-08-30 12:44
閱讀 1314·2019-08-29 17:06
閱讀 601·2019-08-29 14:12
閱讀 1053·2019-08-29 14:01
閱讀 2638·2019-08-29 12:17