es6學習筆記11--Proxy和Reflect


Proxy概述

Proxy用於修改某些操作的默認行為,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。

Proxy可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。

ES6原生提供Proxy構造函數,用來生成Proxy實例。

var proxy = new Proxy(target, handler);

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

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

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

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

上面代碼中,作為構造函數,Proxy接受兩個參數第一個參數是所要代理的目標對象(上例是一個空對象),即如果沒有Proxy的介入,操作原來要訪問的就是這個對象;第二個參數是一個配置對象,對於每一個被代理的操作,需要提供一個對應的處理函數,該函數將攔截對應的操作。比如,上面代碼中,配置對象有一個get方法,用來攔截對目標對象屬性的訪問請求。get方法的兩個參數分別是目標對象和所要訪問的屬性。可以看到,由於攔截函數總是返回35,所以訪問任何屬性都得到35

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

如果handler沒有設置任何攔截,那就等同於直接通向原對象。

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

上面代碼中,handler是一個空對象,沒有任何攔截效果,訪問handeler就等同於訪問target

同一個攔截器函數,可以設置攔截多個操作。

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 args[1]; }
};

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

fproxy(1,2); // 1
new fproxy(1,2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'Hello, foo'

下面是Proxy支持的攔截操作一覽。

對於可以設置、但沒有設置攔截的操作,則直接落在目標對象上,按照原先的方式產生結果。

(1)get(target, propKey, receiver)

攔截對象屬性的讀取,比如proxy.fooproxy['foo'],返回類型不限。最后一個參數receiver可選,當target對象設置了propKey屬性的get函數時,receiver對象會綁定get函數的this對象。

(2)set(target, propKey, value, receiver)

攔截對象屬性的設置,比如proxy.foo = vproxy['foo'] = v,返回一個布爾值。

(3)has(target, propKey)

攔截propKey in proxy的操作,返回一個布爾值。

has方法可以隱藏某些屬性,不被in操作符發現。

(4)deleteProperty(target, propKey)

攔截delete proxy[propKey]的操作,返回一個布爾值。

(5)enumerate(target)

攔截for (var x in proxy),返回一個遍歷器。

注意與Proxy對象的has方法區分,后者用來攔截in操作符,對for...in循環無效。

(6)ownKeys(target)

攔截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy),返回一個數組。該方法返回對象所有自身的屬性,Object.keys()僅返回對象可遍歷的屬性。

(7)getOwnPropertyDescriptor(target, propKey)

攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

(8)defineProperty(target, propKey, propDesc)

攔截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一個布爾值。

(9)preventExtensions(target)

攔截Object.preventExtensions(proxy),返回一個布爾值。

(10)getPrototypeOf(target)

攔截Object.getPrototypeOf(proxy),返回一個對象。

(11)isExtensible(target)

攔截Object.isExtensible(proxy),返回一個布爾值。

(12)setPrototypeOf(target, proto)

攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。

如果目標對象是函數,那么還有兩種額外操作可以攔截。

(13)apply(target, object, args)

攔截Proxy實例作為函數調用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)

apply方法可以接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。

(14)construct(target, args, proxy)

攔截Proxy實例作為構造函數調用的操作,比如new proxy(...args)

construct方法用於攔截new命令。

 

Reflect概述

Reflect對象與Proxy對象一樣,也是ES6為了操作對象而提供的新API。Reflect對象的設計目的有這樣幾個。

(1) Object對象的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect對象上。現階段,某些方法同時在ObjectReflect對象上部署,未來的新方法將只部署在Reflect對象上。

(2) 修改某些Object方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false

// 老寫法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) 讓Object操作都變成函數行為。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)讓它們變成了函數行為。

// 老寫法
'assign' in Object // true

// 新寫法
Reflect.has(Object, 'assign') // true

(4)Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。這就讓Proxy對象可以方便地調用對應的Reflect方法,完成默認行為,作為修改行為的基礎。也就是說,不管Proxy怎么修改默認行為,你總可以在Reflect上獲取默認行為。

Reflect對象的方法

Reflect對象的方法清單如下,共14個。

  • Reflect.apply(target,thisArg,args)
  • Reflect.construct(target,args)
  • Reflect.get(target,name,receiver)
  • Reflect.set(target,name,value,receiver)
  • Reflect.defineProperty(target,name,desc)
  • Reflect.deleteProperty(target,name)
  • Reflect.has(target,name)
  • Reflect.ownKeys(target)
  • Reflect.enumerate(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

上面這些方法的作用,大部分與Object對象的同名方法的作用都是相同的,而且它與Proxy對象的方法是一一對應的。下面是對其中幾個方法的解釋。

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

查找並返回target對象的name屬性,如果沒有該屬性,則返回undefined

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

設置target對象的name屬性等於value。如果name屬性設置了賦值函數,則賦值函數的this綁定receiver

(3)Reflect.has(obj, name)

等同於name in obj

(4)Reflect.deleteProperty(obj, name)

等同於delete obj[name]

(5)Reflect.construct(target, args)

等同於new target(...args),這提供了一種不使用new,來調用構造函數的方法。

(6)Reflect.getPrototypeOf(obj)

讀取對象的__proto__屬性,對應Object.getPrototypeOf(obj)

(7)Reflect.setPrototypeOf(obj, newProto)

設置對象的__proto__屬性,對應Object.setPrototypeOf(obj, newProto)

(8)Reflect.apply(fun,thisArg,args)

等同於Function.prototype.apply.call(fun,thisArg,args)。一般來說,如果要綁定一個函數的this對象,可以這樣寫fn.apply(obj, args),但是如果函數定義了自己的apply方法,就只能寫成Function.prototype.apply.call(fn, obj, args),采用Reflect對象可以簡化這種操作。

另外,需要注意的是,Reflect.set()Reflect.defineProperty()Reflect.freeze()Reflect.seal()Reflect.preventExtensions()返回一個布爾值,表示操作是否成功。它們對應的Object方法,失敗時都會拋出錯誤。

// 失敗時拋出錯誤
Object.defineProperty(obj, name, desc);
// 失敗時返回false
Reflect.defineProperty(obj, name, desc);

上面代碼中,Reflect.defineProperty方法的作用與Object.defineProperty是一樣的,都是為對象定義一個屬性。但是,Reflect.defineProperty方法失敗時,不會拋出錯誤,只會返回false

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM