前端JavaScript中的反射和代理
1、什麼是反射
反射這個概念在很多編程語言中都存在,像Java
,C#
。
在面向對象編程中,一般會先將類和方法定義好,然後創建對象顯式調用方法,比如下面的例子:
public class User{ private String name; private Date birthday; //.... public int calculateAgeByBirthday(){ // ..... } } // 調用 User u = new User("jack", new Date()); u.calculateAgeByBirthday();
上面這種調用方式我們比較熟悉,不過當你想編寫一些抽象框架時(框架又需要與業務定義的類進行互操作),由於你不知道業務類的成員和方法,這時反射動態獲取成員變量或調用方法。
下面例子,我們利用反射將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個靜態方法。
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
對象後執行給定函數。
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)
用於讓一個對象變為不可擴展。它返回一個佈爾值,表示是否操作成功。
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
:它是一個對象,它的屬性提供瞭某些操作發生時的處理函數。
const user = {name: 'hello'} const proxy = new Proxy(user, { get: function(target, property) { // 讀取屬性時觸發 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
循環。
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
:創造實例對象時,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、觀察者模式
觀察者是一種很常用的模式,它的定義是當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
我們使用Proxy
來實現一個例子,當觀察對象狀態變化時,讓觀察函數自動執行。
觀察者函數,包裹觀察目標,添加觀察函數。
observable
包裹觀察目標,返回一個Proxy
對象。observe
添加觀察函數到隊列。
const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); // 屬性改變時,自動執行觀察函數。 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反射和代理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- JavaScript Object.defineProperty與proxy代理模式的使用詳細分析
- vue3響應式Proxy與Reflect的理解及基本使用實例詳解
- JavaScript的Proxy對象詳解
- proxy實現vue3數據雙向綁定原理
- vue3結構賦值失去響應式引發的問題思考