ECMAScript 6 入门笔记(八)Proxy,Reflect

18 篇文章 0 订阅

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值