您好,登錄后才能下訂單哦!
本篇內容介紹了“如何掌握前端JavaScript中的反射和代理”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
反射這個概念在很多編程語言中都存在,像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; }
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)
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
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
Reflect.has
方法相當于name in obj
里面的in
運算符。
const obj = { name: 'jack', } obj in name // true Reflect.has(obj, 'name') // true
Reflect.deleteProperty
方法相當于delete obj[name]
,用于刪除對象的屬性。如果刪除成功,或者被刪除的屬性不存在,返回true
;刪除失敗,被刪除的屬性依然存在,返回false。
const obj = { name: 'jack', } delete obj.name Reflect.deleteProperty(obj, 'name')
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
方法用于設置目標對象的原型(prototype
)。返回一個布爾值,表示是否設置成功。
const obj = { name: 'jack', } Reflect.setPrototypeOf(obj, Array.prototype); obj.length // 0
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);
Reflect.defineProperty
方法相當于Object.defineProperty
,用來為對象定義屬性。
const obj = {}; Object.defineProperty(obj, 'property', { value: 0, writable: false }); Reflect.defineProperty(obj, 'property', { value: 0, writable: false });
獲取指定屬性的描述對象。
返回一個布爾值,表示當前對象是否可擴展。
用于讓一個對象變為不可擴展。它返回一個布爾值,表示是否操作成功。
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']
代理在編程中很有用,它可以在目標對象之前增加一層“攔截”實現一些通用邏輯。
Proxy
構造函數 Proxy
(target, handler) 參數:
target
:代理的目標對象,它可以是任何類型的對象,包括內置的數組,函數,代理對象。
handler
:它是一個對象,它的屬性提供了某些操作發生時的處理函數。
const user = {name: 'hello'} const proxy = new Proxy(user, { get: function(target, property) { // 讀取屬性時觸發 return 'hi'; } }); proxy.name // 'hi'
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)
用于攔截某個屬性的讀取操作,可以接受三個參數,依次為目標對象、屬性名和 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.
用來攔截某個屬性的賦值操作,可以接受四個參數,依次為目標對象、屬性名、屬性值和 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
用來攔截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
defineProperty()
方法攔截了Object.defineProperty()
操作。
用于攔截
delete
操作,如果這個方法拋出錯誤或者返回false
,當前屬性就無法被delete
命令刪除。
getOwnPropertyDescriptor()
方法攔截Object.getOwnPropertyDescriptor()
,返回一個屬性描述對象或者undefined
。
主要用來攔截獲取對象原型,攔截的操作如下:
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
主要用來攔截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
方法攔截Object.isExtensible()操作。
const p = new Proxy({}, { isExtensible: function(target) { console.log('called'); return true;//也可以return 1;等表示為true的值 } }); console.log(Object.isExtensible(p)); // "called" // true
用來攔截對象自身屬性的讀取操作。具體來說,攔截以下操作。
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"
用來攔截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
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
用于攔截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() //目標對象不是函數,報錯
觀察者是一種很常用的模式,它的定義是當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
我們使用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中的反射和代理”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。