溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何掌握前端JavaScript中的反射和代理

發(fā)布時間:2021-10-21 17:06:36 來源:億速云 閱讀:116 作者:iii 欄目:開發(fā)技術

本篇內容介紹了“如何掌握前端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

JavaScriptES6提供了反射內置對象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參數(shù),在調用userInfo()函數(shù)時,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參數(shù),在調用updateAge()函數(shù)時,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í)行給定函數(shù)。

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

代理在編程中很有用,它可以在目標對象之前增加一層“攔截”實現(xiàn)一些通用邏輯。

Proxy 構造函數(shù) Proxy(target, handler) 參數(shù):

  • target:代理的目標對象,它可以是任何類型的對象,包括內置的數(shù)組,函數(shù),代理對象。

  • handler:它是一個對象,它的屬性提供了某些操作發(fā)生時的處理函數(shù)。


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()

用于攔截某個屬性的讀取操作,可以接受三個參數(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 // 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()

用來攔截某個屬性的賦值操作,可以接受四個參數(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

3.4 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

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()

它接受三個參數(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

3.14 construct()

用于攔截new命令,下面是攔截對象的寫法:

const handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

它方法接受三個參數(shù)。

  • target:目標對象。

  • args:構造函數(shù)的參數(shù)數(shù)組。

  • newTarget:創(chuàng)造實例對象時,new命令作用的構造函數(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ù),報錯

4、觀察者模式

觀察者是一種很常用的模式,它的定義是當一個對象的狀態(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

“如何掌握前端JavaScript中的反射和代理”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI