乐趣区

关于前端:前端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 中 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)

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

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

Reflect.has(obj, name)

Reflect.has办法相当于 name in obj 外面的 in 运算符。

const obj = {name: 'jack',}
obj in name // true
Reflect.has(obj, 'name') // true

Reflect.deleteProperty(obj, name)

Reflect.deleteProperty办法相当于delete obj[name],用于删除对象的属性。如果删除胜利,或者被删除的属性不存在,返回true;删除失败,被删除的属性仍然存在,返回false

const obj = {name: 'jack',}
delete obj.name 
Reflect.deleteProperty(obj, 'name')

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__ 属性。

Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf办法用于设置指标对象的原型(prototype)。返回一个布尔值,示意是否设置胜利。

const obj = {name: 'jack',}
Reflect.setPrototypeOf(obj, Array.prototype);
obj.length // 0 

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

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
});

Reflect.getOwnPropertyDescriptor(target, propertyKey)

获取指定属性的形容对象。

Reflect.isExtensible (target)

返回一个布尔值,示意以后对象是否可扩大。

Reflect.preventExtensions(target)

用于让一个对象变为不可扩大。它返回一个布尔值,示意是否操作胜利。

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']

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'

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)

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.

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

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

defineProperty()

defineProperty()办法拦挡了 Object.defineProperty() 操作。

deleteProperty()

用于拦挡 delete 操作,如果这个办法抛出谬误或者返回 false,以后属性就无奈被delete 命令删除。

getOwnPropertyDescriptor()

getOwnPropertyDescriptor()办法拦挡Object.getOwnPropertyDescriptor(),返回一个属性形容对象或者undefined

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

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

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

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"

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

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

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() // 指标对象不是函数,报错

观察者模式

观察者是一种很罕用的模式,它的定义是当一个对象的状态产生扭转时,所有依赖于它的对象都失去告诉并被自动更新。

咱们应用 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 中的内置 Reflect。
  • JavaScript 中的内置 Proxy。
  • Proxy 实现观察者模式。
退出移动版