ECMAScript 6 入门原文 – 阮一峰
ECMAScript 6 入门笔记(一)let,const,解构
ECMAScript 6 入门笔记(二)String,RegExp
ECMAScript 6 入门笔记(三)数值,Array
ECMAScript 6 入门笔记(四)函数,对象
ECMAScript 6 入门笔记(五)异步promise,Generator,async
ECMAScript 6 入门笔记(六)Class
ECMAScript 6 入门笔记(七)Symbol,set和map
ECMAScript 6 入门笔记(八)Proxy,Reflect
Proxy
proxy用于修改某些操作的默认行为,等同于在语言层面作出修改,属于”元编程”。可以理解成架设一层“拦截”,外界对该对象访问都必须通过这层拦截。
var obj = new Proxy({},{
get: function(){target, key, receiver}{
console.log(`getting ${key}`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver){
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1
// setting count
++obj.count;
//getting count
//setting count
//2
var proxt = new Proxy({},{
get: function(target,property){
return 35;
}
});
proxy.time //35
proxy.name //35
Proxy对象
Proxy实例也可以作为其他对象的原型对象
var proxy = new Proxy({},{
get: function(target,property){
return 35;
}
});
let obj = Object.create(proxy);
obj.time //35
同一个拦截器函数,可以设置拦截多个操作
var handler = {
get: function(target,name){
if(name === ‘prototype’){
return Object.prototype;
}
return ‘Hello, ‘+name;
},
apply: function(target, thisBinding, args){
reutrn args[0];
},
construct: function(target,args){
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x,y){
return x + y;
},handler);
fproxy(1,2); //1
new fproxy(1,2); // {value:2}
fproxy.prototype === Object.prototype //true
fproxy.foo
Proxy支持的操作
get()
var person = {
name : ‘张三’
};
var proxy = new Proxy(person,{
get: function(target, property){
if(prototype in target){
return target[property];
}else{
throw new ReferenceError(“Property \”” + property + “\” does not exist.”);
}
}
});
proxy.name //张三
proxy.age //抛出错误
如果一个属性不可配置和不可写,则不能通过Proxy对象访问该属性会报错
const target = Object.defineProperties({},{
foo:{
value : 123,
writable: false,
configurable:false
}
});
const handler = {
get(target, propKey){
return ‘abc’;
}
};
const proxy = new Proxy(target, handler);
proxy.foo //typeError
set()
set方法用来拦截某个属性的赋值操作
假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的属性值符合要求
假定Person对象有一个age属性,不大于200的整数
let validator = {
set: function(obj, prop, value){
if(prop === ‘age’){
if(!Number.isInteger(value)){
throw new TypeError(‘The age is not an interger’);
}
if(value>200){
throw new TypeError(‘The age seems invalid’);
}
}
obj[prop] = value;
}
};
let person = new Proxy({},validator);
person.age = 1000;
person.age // 1000
person.age = ‘123’ //报错
apply()
apply方法拦截函数的调用,call和apply操作,可以接收三个参数,分别是目标对象,目标对象的上下文对象this和目标对象的参数数组
var handler = {
apply(target, ctx, args){
return Reflect.apply(…arguments);
}
};
var target = function(){ return ‘I am the target’;};
var handler = {
apply: function(){
return ‘I am the proxy’;
}
};
var p = new Proxy(target,handler);
p() //I am the proxy
var twice = {
apply (target, ctx, args){
return Reflext.apply(…arguments) *2;
}
};
function sum(left, right){
return left + right;
}
var proxy = new Proxy(sum, twice);
proxy(1,2);
proxy.call(null,5,6) //22
proxy.apply(null,[7,8]) //30
has()
has方法用来拦截HasProperty操作
下面的例子使用has方法隐藏某些属性,不被iin元素运算符发现
var 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
construct()
用于拦截new()命令,下面是拦截对象的写法
var p = new Proxy(function(){},{
construct: function(target, args){
console.log(‘called:’ + args.join(‘, ‘));
return {value: args[0] * 10};
}
});
(new p(1)).value;
deleteProperty()
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
var handler = {
deleteProperty (target, key) {
invariant(key, ‘delete’);
return true;
}
};
function invariant (key, action){
if(key[0] === ‘_’){
throw new Error(invalid attempt to ${action} private "${key}" property
);
}
}
var target = {_prop:”foo”};
var proxy = new Proxy(target, handler);
delete.proxy._prop;
getOwnPropertyDescriptor()
拦截Object.getOwnPropertyDescriptor()
getPrototypeOf()
用来拦截获取对象原型
var proto = {};
var p = new Proxy({},{
getPrototypeOf(target){
return proto;
}
});
Object.getPrototypeOf(p) === proto; //true
isExtensible()
拦截Object.isExtensible操作
var p = new Proxy({},{
isExtensible: function(target){
console.log(“called”);
return true;
}
});
Object.isExtensible(p);
//”called”
//true
ownKeys()
拦截对象自身属性的读取操作
let target = {
a:1,
b:2,
c:3
};
let handler = {
ownKeys(target){
return [‘a’];
}
};
let proxy = new Proxy(target,handler);
Object.keys(proxy); //[‘a’]
preventExtensions()
拦截Object.preventExtensions().这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
setPrototypeOf()
setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。
var handler = {
setPrototypeOf (target, proto) {
throw new Error(‘Changing the prototype is forbidden’);
}
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
Proxy.revocable()
Proxy.revocable方法返回一个可取消的 Proxy 实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this 问题
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target。
Reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
‘assign’ in Object // true
// 新写法
Reflect.has(Object, ‘assign’) // true
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
log(‘property ’ + name + ’ on ’ + target + ’ set to ’ + value);
}
return success;
}
});
上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
下面是另一个例子。
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log(‘get’, target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log(‘delete’ + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log(‘has’ + name);
return Reflect.has(target, name);
}
});
上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。
有了Reflect对象以后,很多操作会更易读。
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
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)