JavaScript中的代理和反射

來源 | https://segmentfault.com/a/1190000040829478
什么是反射
public class User{private String name;private Date birthday;//....public int calculateAgeByBirthday(){// .....}}// 調(diào)用User u = new User("jack", new Date());u.calculateAgeByBirthday();
上面這種調(diào)用方式我們比較熟悉,不過當你想編寫一些抽象框架時(框架又需要與業(yè)務(wù)定義的類進行互操作),由于你不知道業(yè)務(wù)類的成員和方法,這時反射動態(tài)獲取成員變量或調(diào)用方法。
下面例子,我們利用反射將json轉(zhuǎn)換為Java對象。
public static class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}// 使用反射調(diào)用對象setter方法。public static <T> T fill(Class<T> userClass, Map<String, Object> json) throws Exception {Field[] fields = userClass.getDeclaredFields();T user = userClass.newInstance();for (Field field : fields) {// 首字母大寫String name = field.getName();char[] arr = name.toCharArray();arr[0] = Character.toUpperCase(arr[0]);System.out.println(new String(arr));Method method = userClass.getDeclaredMethod("set" + new String(arr), field.getType());Object returnValue = method.invoke(user, json.get(name));}return user;}
JavaScript中Reflect
JavaScript在ES6提供了反射內(nèi)置對象Reflect,但JavaScript里面的反射和Java反射有所不同。先看下Reflect提供的13個靜態(tài)方法。
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.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
Reflect.get(target, name, receiver)
Reflect.get方法查找并返回target對象的name屬性,如果沒有該屬性,則返回undefined。
const obj = {name: 'jack',age: 12,get userInfo() {return this.name + ' age is ' + this.age;}}Reflect.get(obj, 'name') // jackReflect.get(obj, 'age') // 12Reflect.get(obj, 'userInfo') // jack age is 12// 如果傳遞了receiver參數(shù),在調(diào)用userInfo()函數(shù)時,this是指向receiver對象。const receiverObj = {name: '小明',age: 22};Reflect.get(obj, 'userInfo', receiverObj) // 小明 age is 22
Reflect.set(target, name, value, receiver)
const obj = {name: 'jack',age: 12,set updateAge(value) {return this.age = value;},}Reflect.set(obj, 'age', 22);obj.age // 22// 如果傳遞了receiver參數(shù),在調(diào)用updateAge()函數(shù)時,this是指向receiver對象。const receiverObj = {age: 0};Reflect.set(obj, 'updateAge', 10, receiverObj) //obj.age // 22receiverObj.age // 10
Reflect.has(obj, name)
Reflect.has方法相當于name in obj里面的in運算符。
const obj = {name: 'jack',}obj in name // trueReflect.has(obj, 'name') // true
Reflect.deleteProperty(obj, name)
Reflect.deleteProperty方法相當于delete obj[name],用于刪除對象的屬性。如果刪除成功,或者被刪除的屬性不存在,返回true;刪除失敗,被刪除的屬性依然存在,返回false。
const obj = {name: 'jack',}delete obj.nameReflect.deleteProperty(obj, 'name')
Reflect.construct(target, args)
Reflect.construct方法等同于new target(...args)。
function User(name){this.name = name;}const user = new User('jack');Reflect.construct(User, ['jack']);
Reflect.getPrototypeOf(obj)
Reflect.getPrototypeOf方法用于讀取對象的__proto__屬性。
Reflect.setPrototypeOf(obj, newProto)
Reflect.setPrototypeOf方法用于設(shè)置目標對象的原型(prototype)。返回一個布爾值,表示是否設(shè)置成功。
const obj = {name: 'jack',}Reflect.setPrototypeOf(obj, Array.prototype);obj.length // 0
Reflect.apply(func, thisArg, args)
Reflect.apply方法相當于Function.prototype.apply.call(func, thisArg, args),用于綁定this對象后執(zhí)行給定函數(shù)。
const nums = [1,2,3,4,5];const min = Math.max.apply(Math, nums);// 通過 Reflect.apply 調(diào)用const min = Reflect.apply(Math.min, Math, nums);
Reflect.defineProperty(target, propertyKey, attributes)
Reflect.defineProperty方法相當于Object.defineProperty,用來為對象定義屬性。
const obj = {};Object.defineProperty(obj, 'property', {value: 0,writable: false});Reflect.defineProperty(obj, 'property', {value: 0,writable: false});
Reflect.getOwnPropertyDescriptor(target, propertyKey)
獲取指定屬性的描述對象。
Reflect.isExtensible (target)
返回一個布爾值,表示當前對象是否可擴展。
Reflect.preventExtensions(target)
用于讓一個對象變?yōu)椴豢蓴U展。它返回一個布爾值,表示是否操作成功。
Reflect.ownKeys (target)
Reflect.ownKeys方法用于返回對象的所有屬性。
const obj = {name: 'jack',age: 12,get userInfo() {return this.name + ' age is ' + this.age;}}Object.getOwnPropertyNames(obj)Reflect.ownKeys(obj) // ['name', 'age', 'userInfo']
JavaScript中Proxy
代理在編程中很有用,它可以在目標對象之前增加一層“攔截”實現(xiàn)一些通用邏輯。
Proxy 構(gòu)造函數(shù) Proxy(target, handler) 參數(shù):
target:代理的目標對象,它可以是任何類型的對象,包括內(nèi)置的數(shù)組,函數(shù),代理對象。
handler:它是一個對象,它的屬性提供了某些操作發(fā)生時的處理函數(shù)。
const user = {name: 'hello'}const proxy = new Proxy(user, {get: function(target, property) { // 讀取屬性時觸發(fā)return 'hi';}});proxy.name // 'hi'
Proxy中支持的攔截操作
handler.get(target, property, receiver)
handler.set(target, property, value, receiver)
handler.has(target, property)
handler.defineProperty(target, property, descriptor)
handler.deleteProperty(target, property)
handler.getOwnPropertyDescriptor(target, prop)
handler.getPrototypeOf(target)
handler.setPrototypeOf(target, prototype)
handler.isExtensible(target)
handler.ownKeys(target)
handler.preventExtensions(target)
handler.apply(target, thisArg, argumentsList)
handler.construct(target, argumentsList, newTarget)
get()
用于攔截某個屬性的讀取操作,可以接受三個參數(shù),依次為目標對象、屬性名和 proxy 實例本身,其中最后一個參數(shù)可選。
const user = {name: 'jack'}// 只有屬性存在才返回值,否則拋出異常。const proxy = new Proxy(user, {get: function(target, property) {if (!(property in target)) {throw new ReferenceError(`${property} does not exist.`);}return target[property];}});proxy.name // jackproxy.age // ReferenceError: age does not exist.
我們可以定義一些公共代理對象,然后讓子對象繼承。
// 只有屬性存在才返回值,否則拋出異常。const proxy = new Proxy({}, {get: function(target, property) {if (!(property in target)) {throw new ReferenceError(`${property} does not exist.`);}return target[property];}});let obj = Object.create(proxy);obj.name = 'hello'obj.name // helloobj.age // ReferenceError: age does not exist.
set()
用來攔截某個屬性的賦值操作,可以接受四個參數(shù),依次為目標對象、屬性名、屬性值和 Proxy 實例本身,其中最后一個參數(shù)可選。
// 字符類型的屬性長度校驗let sizeValidator = {set: function(target, property, value, receiver) {if (typeof value == 'string' && value.length > 5) {throw new RangeError('Cannot exceed 5 character.');}target[property] = value;return true;}};const validator = new Proxy({}, sizeValidator);let obj = Object.create(validator);obj.name = '123456' // RangeError: Cannot exceed 5 character.obj.age = 12 // 12
has()
用來攔截HasProperty操作,即判斷對象是否具有某個屬性時,這個方法會生效。如in運算符。
它接受兩個參數(shù),分別是目標對象、需查詢的屬性名。
const 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
defineProperty()
defineProperty()方法攔截了Object.defineProperty()操作。
deleteProperty()
用于攔截delete操作,如果這個方法拋出錯誤或者返回false,當前屬性就無法被delete命令刪除。
getOwnPropertyDescriptor()
getOwnPropertyDescriptor()方法攔截Object.getOwnPropertyDescriptor(),返回一個屬性描述對象或者undefined。
getPrototypeOf()
主要用來攔截獲取對象原型,攔截的操作如下:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
const obj = {};const proto = {};const handler = {getPrototypeOf(target) {console.log(target === obj); // trueconsole.log(this === handler); // truereturn proto;}};const p = new Proxy(obj, handler);console.log(Object.getPrototypeOf(p) === proto); // true
setPrototypeOf()
主要用來攔截Object.setPrototypeOf()方法。
const handlerReturnsFalse = {setPrototypeOf(target, newProto) {return false;}};const newProto = {}, target = {};const p1 = new Proxy(target, handlerReturnsFalse);Object.setPrototypeOf(p1, newProto); // throws a TypeErrorReflect.setPrototypeOf(p1, newProto); // returns false
isExtensible()
方法攔截Object.isExtensible()操作。
const p = new Proxy({}, {isExtensible: function(target) {console.log('called');return true;//也可以return 1;等表示為true的值}});console.log(Object.isExtensible(p)); // "called"// true
ownKeys()
用來攔截對象自身屬性的讀取操作。具體來說,攔截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in循環(huán)。
const p = new Proxy({}, {ownKeys: function(target) {console.log('called');return ['a', 'b', 'c'];}});console.log(Object.getOwnPropertyNames(p)); // "called"
preventExtensions()
用來攔截Object.preventExtensions()。該方法必須返回一個布爾值,否則會被自動轉(zhuǎn)為布爾值。
這個方法有一個限制,只有目標對象不可擴展時(即Object.isExtensible(proxy)為false),proxy.preventExtensions才能返回true,否則會報錯。
const p = new Proxy({}, {preventExtensions: function(target) {console.log('called');Object.preventExtensions(target);return true;}});console.log(Object.preventExtensions(p)); // "called"// false
apply()
apply方法攔截以下操作。
proxy(...args)
Function.prototype.apply() 和 Function.prototype.call()
Reflect.apply()
它接受三個參數(shù),分別是目標對象、目標對象的上下文對象(this)和目標對象的參數(shù)數(shù)組。
const handler = {apply (target, ctx, args) {return Reflect.apply(...arguments);}};
例子
const target = function () { };const handler = {apply: function (target, thisArg, argumentsList) {console.log('called: ' + argumentsList.join(', '));return argumentsList[0] + argumentsList[1] + argumentsList[2];}};const p = new Proxy(target, handler);p(1,2,3) // "called: 1, 2, 3" 6
construct()
用于攔截new命令,下面是攔截對象的寫法:
const handler = {construct (target, args, newTarget) {return new target(...args);}};
它方法接受三個參數(shù)。
target:目標對象。
args:構(gòu)造函數(shù)的參數(shù)數(shù)組。
newTarget:創(chuàng)造實例對象時,new命令作用的構(gòu)造函數(shù)。
注意:方法返回的必須是一個對象,目標對象必須是函數(shù),否則就會報錯。
const p = new Proxy(function() {}, {construct: function(target, argumentsList) {return 0;}});new p() // 返回值不是對象,報錯const p = new Proxy({}, {construct: function(target, argumentsList) {return {};}});new p() //目標對象不是函數(shù),報錯
觀察者模式
觀察者是一種很常用的模式,它的定義是當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
我們使用Proxy 來實現(xiàn)一個例子,當觀察對象狀態(tài)變化時,讓觀察函數(shù)自動執(zhí)行。
觀察者函數(shù),包裹觀察目標,添加觀察函數(shù)。
observable包裹觀察目標,返回一個Proxy對象。
observe 添加觀察函數(shù)到隊列。
const queuedObservers = new Set();const observe = fn => queuedObservers.add(fn);const observable = obj => new Proxy(obj, {set});// 屬性改變時,自動執(zhí)行觀察函數(shù)。function set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver);queuedObservers.forEach(observer => observer());return result;}
例子
const user = observable({name: 'jack',age: 20});function userInfo() {console.log(`${user.name}, ${user.age}`)}observe(userInfo);user.name = '小明'; // 小明, 20
小結(jié)
本文要點回顧,歡迎留言交流。
JavaScript中的內(nèi)置Reflect。
JavaScript中的內(nèi)置Proxy。
Proxy實現(xiàn)觀察者模式。
感謝你的時間,謝謝閱讀。
學習更多技能
請點擊下方公眾號
![]()

