前端JavaScript中的反射和代理
1、什么是反射
反射這個(gè)概念在很多編程語(yǔ)言中都存在,像Java
,C#
。
在面向?qū)ο缶幊讨?,一般?huì)先將類和方法定義好,然后創(chuàng)建對(duì)象顯式調(diào)用方法,比如下面的例子:
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)用方式我們比較熟悉,不過(guò)當(dāng)你想編寫(xiě)一些抽象框架時(shí)(框架又需要與業(yè)務(wù)定義的類進(jìn)行互操作),由于你不知道業(yè)務(wù)類的成員和方法,這時(shí)反射動(dòng)態(tài)獲取成員變量或調(diào)用方法。
下面例子,我們利用反射將json轉(zhuǎn)換為Java對(duì)象。
public static class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } // 使用反射調(diào)用對(duì)象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) { // 首字母大寫(xiě) 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
提供了反射內(nèi)置對(duì)象Reflect
,但JavaScript
里面的反射和Java反射有所不同。先看下Reflect
提供的13個(gè)靜態(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對(duì)象的name屬性,如果沒(méi)有該屬性,則返回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參數(shù),在調(diào)用userInfo()函數(shù)時(shí),this是指向receiver對(duì)象。 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參數(shù),在調(diào)用updateAge()函數(shù)時(shí),this是指向receiver對(duì)象。 const receiverObj = { age: 0 }; Reflect.set(obj, 'updateAge', 10, receiverObj) // obj.age // 22 receiverObj.age // 10
2.3 Reflect.has(obj, name)
Reflect.has
方法相當(dāng)于name in obj
里面的in
運(yùn)算符。
const obj = { name: 'jack', } obj in name // true Reflect.has(obj, 'name') // true
2.4 Reflect.deleteProperty(obj, name)
Reflect.deleteProperty
方法相當(dāng)于delete obj[name]
,用于刪除對(duì)象的屬性。如果刪除成功,或者被刪除的屬性不存在,返回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方法用于讀取對(duì)象的__proto__屬性。
2.6 Reflect.setPrototypeOf(obj, newProto)
Reflect.setPrototypeOf
方法用于設(shè)置目標(biāo)對(duì)象的原型(prototype
)。返回一個(gè)布爾值,表示是否設(shè)置成功。
const obj = { name: 'jack', } Reflect.setPrototypeOf(obj, Array.prototype); obj.length // 0
2.7 Reflect.apply(func, thisArg, args)
Reflect.apply
方法相當(dāng)于Function.prototype.apply.call(func, thisArg, args)
,用于綁定this
對(duì)象后執(zhí)行給定函數(shù)。
const nums = [1,2,3,4,5]; const min = Math.max.apply(Math, nums); // 通過(guò) Reflect.apply 調(diào)用 const min = Reflect.apply(Math.min, Math, nums);
2.8 Reflect.defineProperty(target, propertyKey, attributes)
Reflect.defineProperty
方法相當(dāng)于Object.defineProperty
,用來(lái)為對(duì)象定義屬性。
const obj = {}; Object.defineProperty(obj, 'property', { value: 0, writable: false }); Reflect.defineProperty(obj, 'property', { value: 0, writable: false });
2.9 Reflect.getOwnPropertyDescriptor(target, propertyKey)
獲取指定屬性的描述對(duì)象。
2.10 Reflect.isExtensible (target)
返回一個(gè)布爾值,表示當(dāng)前對(duì)象是否可擴(kuò)展。
2.11 Reflect.preventExtensions(target)
用于讓一個(gè)對(duì)象變?yōu)椴豢蓴U(kuò)展。它返回一個(gè)布爾值,表示是否操作成功。
2.13 Reflect.ownKeys (target)
Reflect.ownKeys
方法用于返回對(duì)象的所有屬性。
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
代理在編程中很有用,它可以在目標(biāo)對(duì)象之前增加一層“攔截”實(shí)現(xiàn)一些通用邏輯。
Proxy
構(gòu)造函數(shù) Proxy
(target, handler) 參數(shù):
target
:代理的目標(biāo)對(duì)象,它可以是任何類型的對(duì)象,包括內(nèi)置的數(shù)組,函數(shù),代理對(duì)象。handler
:它是一個(gè)對(duì)象,它的屬性提供了某些操作發(fā)生時(shí)的處理函數(shù)。
const user = {name: 'hello'} const proxy = new Proxy(user, { get: function(target, property) { // 讀取屬性時(shí)觸發(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()
用于攔截某個(gè)屬性的讀取操作,可以接受三個(gè)參數(shù),依次為目標(biāo)對(duì)象、屬性名和 proxy
實(shí)例本身,其中最后一個(gè)參數(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 // jack proxy.age // ReferenceError: age does not exist.
我們可以定義一些公共代理對(duì)象,然后讓子對(duì)象繼承。
// 只有屬性存在才返回值,否則拋出異常。 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()
用來(lái)攔截某個(gè)屬性的賦值操作,可以接受四個(gè)參數(shù),依次為目標(biāo)對(duì)象、屬性名、屬性值和 Proxy
實(shí)例本身,其中最后一個(gè)參數(shù)可選。
// 字符類型的屬性長(zhǎng)度校驗(yàn) 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()
用來(lái)攔截HasProperty
操作,即判斷對(duì)象是否具有某個(gè)屬性時(shí),這個(gè)方法會(huì)生效。如in
運(yùn)算符。
它接受兩個(gè)參數(shù),分別是目標(biāo)對(duì)象、需查詢的屬性名。
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
操作,如果這個(gè)方法拋出錯(cuò)誤或者返回false
,當(dāng)前屬性就無(wú)法被delete
命令刪除。
3.7 getOwnPropertyDescriptor()
getOwnPropertyDescriptor()
方法攔截Object.getOwnPropertyDescriptor()
,返回一個(gè)屬性描述對(duì)象或者undefined
。
3.8 getPrototypeOf()
主要用來(lái)攔截獲取對(duì)象原型,攔截的操作如下:
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()
主要用來(lái)攔截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()
用來(lái)攔截對(duì)象自身屬性的讀取操作。具體來(lái)說(shuō),攔截以下操作。
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()
用來(lái)攔截Object.preventExtensions()
。該方法必須返回一個(gè)布爾值,否則會(huì)被自動(dòng)轉(zhuǎn)為布爾值。
這個(gè)方法有一個(gè)限制,只有目標(biāo)對(duì)象不可擴(kuò)展時(shí)(即Object.isExtensible(proxy)為false
),proxy.preventExtensions
才能返回true
,否則會(huì)報(bào)錯(cuò)。
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()
它接受三個(gè)參數(shù),分別是目標(biāo)對(duì)象、目標(biāo)對(duì)象的上下文對(duì)象(this)和目標(biāo)對(duì)象的參數(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
3.14 construct()
用于攔截new
命令,下面是攔截對(duì)象的寫(xiě)法:
const handler = { construct (target, args, newTarget) { return new target(...args); } };
它方法接受三個(gè)參數(shù)。
target
:目標(biāo)對(duì)象。args
:構(gòu)造函數(shù)的參數(shù)數(shù)組。newTarget
:創(chuàng)造實(shí)例對(duì)象時(shí),new命令作用的構(gòu)造函數(shù)。
注意:方法返回的必須是一個(gè)對(duì)象,目標(biāo)對(duì)象必須是函數(shù),否則就會(huì)報(bào)錯(cuò)。
const p = new Proxy(function() {}, { construct: function(target, argumentsList) { return 0; } }); new p() // 返回值不是對(duì)象,報(bào)錯(cuò) const p = new Proxy({}, { construct: function(target, argumentsList) { return {}; } }); new p() //目標(biāo)對(duì)象不是函數(shù),報(bào)錯(cuò)
4、觀察者模式
觀察者是一種很常用的模式,它的定義是當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
我們使用Proxy
來(lái)實(shí)現(xiàn)一個(gè)例子,當(dāng)觀察對(duì)象狀態(tài)變化時(shí),讓觀察函數(shù)自動(dòng)執(zhí)行。
觀察者函數(shù),包裹觀察目標(biāo),添加觀察函數(shù)。
observable
包裹觀察目標(biāo),返回一個(gè)Proxy
對(duì)象。observe
添加觀察函數(shù)到隊(duì)列。
const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); // 屬性改變時(shí),自動(dòng)執(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
到此這篇關(guān)于前端JavaScript
中的反射和代理的文章就介紹到這了,更多相關(guān)JavaScript反射和代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javascript實(shí)現(xiàn)的分頁(yè)函數(shù)
Javascript實(shí)現(xiàn)的分頁(yè)函數(shù)...2006-12-12JS中的every()對(duì)空數(shù)組總返回true原理分析
這篇文章主要為大家介紹了JS中的every()對(duì)空數(shù)組總返回true原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09詳解微信小程序開(kāi)發(fā)之——wx.showToast(OBJECT)的使用
本篇文章主要介紹了微信小程序開(kāi)發(fā)之——wx.showToast(OBJECT)的使用,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01用Move.js配合創(chuàng)建CSS3動(dòng)畫(huà)的入門(mén)指引
這篇文章主要介紹了用Move.js配合創(chuàng)建CSS3動(dòng)畫(huà)的入門(mén)指引,文中介紹了這個(gè)JavaScript庫(kù)中的一些基本方法的使用,需要的朋友可以參考下2015-07-07Javascript使用integrity屬性進(jìn)行安全驗(yàn)證
這篇文章主要介紹了Javascript使用integrity屬性進(jìn)行安全驗(yàn)證,在html中,script標(biāo)簽可以通過(guò)src屬性引入一個(gè)js文件,引入的js文件可以是本地的,也可以是遠(yuǎn)程的,下面我們一起來(lái)看看文章詳細(xì)內(nèi)容2021-11-11tree?shaking對(duì)打包體積優(yōu)化及作用
這篇文章主要為大家介紹了tree?shaking對(duì)打包體積優(yōu)化及作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07使用純JavaScript封裝一個(gè)消息提示條功能示例詳解
這篇文章主要為大家介紹了使用純JavaScript封裝一個(gè)消息提示條功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02微信小程序中使元素占滿整個(gè)屏幕高度實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序中使元素占滿整個(gè)屏幕高度實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-12-12