前端JavaScript中的反射和代理
1、什么是反射
反射這個概念在很多編程語言中都存在,像Java
,C#
。
在面向對象編程中,一般會先將類和方法定義好,然后創(chuàng)建對象顯式調用方法,比如下面的例子:
public class User{ private String name; private Date birthday; //.... public int calculateAgeByBirthday(){ // ..... } } // 調用 User u = new User("jack", new Date()); u.calculateAgeByBirthday();
上面這種調用方式我們比較熟悉,不過當你想編寫一些抽象框架時(框架又需要與業(yè)務定義的類進行互操作),由于你不知道業(yè)務類的成員和方法,這時反射動態(tài)獲取成員變量或調用方法。
下面例子,我們利用反射將json轉換為Java對象。
public static class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } // 使用反射調用對象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; }
2、JavaScript中Reflect
JavaScript
在ES6
提供了反射內置對象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)
2.1 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') // jack Reflect.get(obj, 'age') // 12 Reflect.get(obj, 'userInfo') // jack age is 12 // 如果傳遞了receiver參數,在調用userInfo()函數時,this是指向receiver對象。 const receiverObj = { name: '小明', age: 22 }; Reflect.get(obj, 'userInfo', receiverObj) // 小明 age is 22
2.2 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參數,在調用updateAge()函數時,this是指向receiver對象。 const receiverObj = { age: 0 }; Reflect.set(obj, 'updateAge', 10, receiverObj) // obj.age // 22 receiverObj.age // 10
2.3 Reflect.has(obj, name)
Reflect.has
方法相當于name in obj
里面的in
運算符。
const obj = { name: 'jack', } obj in name // true Reflect.has(obj, 'name') // true
2.4 Reflect.deleteProperty(obj, name)
Reflect.deleteProperty
方法相當于delete obj[name]
,用于刪除對象的屬性。如果刪除成功,或者被刪除的屬性不存在,返回true
;刪除失敗,被刪除的屬性依然存在,返回false。
const obj = { name: 'jack', } delete obj.name Reflect.deleteProperty(obj, 'name')
2.5 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__屬性。
2.6 Reflect.setPrototypeOf(obj, newProto)
Reflect.setPrototypeOf
方法用于設置目標對象的原型(prototype
)。返回一個布爾值,表示是否設置成功。
const obj = { name: 'jack', } Reflect.setPrototypeOf(obj, Array.prototype); obj.length // 0
2.7 Reflect.apply(func, thisArg, args)
Reflect.apply
方法相當于Function.prototype.apply.call(func, thisArg, args)
,用于綁定this
對象后執(zhí)行給定函數。
const nums = [1,2,3,4,5]; const min = Math.max.apply(Math, nums); // 通過 Reflect.apply 調用 const min = Reflect.apply(Math.min, Math, nums);
2.8 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 });
2.9 Reflect.getOwnPropertyDescriptor(target, propertyKey)
獲取指定屬性的描述對象。
2.10 Reflect.isExtensible (target)
返回一個布爾值,表示當前對象是否可擴展。
2.11 Reflect.preventExtensions(target)
用于讓一個對象變?yōu)椴豢蓴U展。它返回一個布爾值,表示是否操作成功。
2.13 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']
3、JavaScript中Proxy
代理在編程中很有用,它可以在目標對象之前增加一層“攔截”實現一些通用邏輯。
Proxy
構造函數 Proxy
(target, handler) 參數:
target
:代理的目標對象,它可以是任何類型的對象,包括內置的數組,函數,代理對象。handler
:它是一個對象,它的屬性提供了某些操作發(fā)生時的處理函數。
const user = {name: 'hello'} const proxy = new Proxy(user, { get: function(target, property) { // 讀取屬性時觸發(fā) return 'hi'; } }); proxy.name // 'hi'
3.1 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)
3.2 get()
用于攔截某個屬性的讀取操作,可以接受三個參數,依次為目標對象、屬性名和 proxy
實例本身,其中最后一個參數可選。
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 // jack proxy.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 // hello obj.age // ReferenceError: age does not exist.
3.3 set()
用來攔截某個屬性的賦值操作,可以接受四個參數,依次為目標對象、屬性名、屬性值和 Proxy
實例本身,其中最后一個參數可選。
// 字符類型的屬性長度校驗 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
3.4 has()
用來攔截HasProperty
操作,即判斷對象是否具有某個屬性時,這個方法會生效。如in
運算符。
它接受兩個參數,分別是目標對象、需查詢的屬性名。
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
3.5 defineProperty()
defineProperty()
方法攔截了Object.defineProperty()
操作。
3.6 deleteProperty()
用于攔截
delete
操作,如果這個方法拋出錯誤或者返回false
,當前屬性就無法被delete
命令刪除。
3.7 getOwnPropertyDescriptor()
getOwnPropertyDescriptor()
方法攔截Object.getOwnPropertyDescriptor()
,返回一個屬性描述對象或者undefined
。
3.8 getPrototypeOf()
主要用來攔截獲取對象原型,攔截的操作如下:
Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
const obj = {}; const proto = {}; const handler = { getPrototypeOf(target) { console.log(target === obj); // true console.log(this === handler); // true return proto; } }; const p = new Proxy(obj, handler); console.log(Object.getPrototypeOf(p) === proto); // true
3.9 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 TypeError Reflect.setPrototypeOf(p1, newProto); // returns false
3.10 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
3.11 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"
3.12 preventExtensions()
用來攔截Object.preventExtensions()
。該方法必須返回一個布爾值,否則會被自動轉為布爾值。
這個方法有一個限制,只有目標對象不可擴展時(即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
3.13 apply()
apply方法攔截以下操作。
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
它接受三個參數,分別是目標對象、目標對象的上下文對象(this)和目標對象的參數數組。
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
3.14 construct()
用于攔截new
命令,下面是攔截對象的寫法:
const handler = { construct (target, args, newTarget) { return new target(...args); } };
它方法接受三個參數。
target
:目標對象。args
:構造函數的參數數組。newTarget
:創(chuàng)造實例對象時,new命令作用的構造函數。
注意:方法返回的必須是一個對象,目標對象必須是函數,否則就會報錯。
const p = new Proxy(function() {}, { construct: function(target, argumentsList) { return 0; } }); new p() // 返回值不是對象,報錯 const p = new Proxy({}, { construct: function(target, argumentsList) { return {}; } }); new p() //目標對象不是函數,報錯
4、觀察者模式
觀察者是一種很常用的模式,它的定義是當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
我們使用Proxy
來實現一個例子,當觀察對象狀態(tài)變化時,讓觀察函數自動執(zhí)行。
觀察者函數,包裹觀察目標,添加觀察函數。
observable
包裹觀察目標,返回一個Proxy
對象。observe
添加觀察函數到隊列。
const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); // 屬性改變時,自動執(zhí)行觀察函數。 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
到此這篇關于前端JavaScript
中的反射和代理的文章就介紹到這了,更多相關JavaScript反射和代理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解微信小程序開發(fā)之——wx.showToast(OBJECT)的使用
本篇文章主要介紹了微信小程序開發(fā)之——wx.showToast(OBJECT)的使用,具有一定的參考價值,有興趣的可以了解一下。2017-01-01用Move.js配合創(chuàng)建CSS3動畫的入門指引
這篇文章主要介紹了用Move.js配合創(chuàng)建CSS3動畫的入門指引,文中介紹了這個JavaScript庫中的一些基本方法的使用,需要的朋友可以參考下2015-07-07