阅读 686

重学ES6 | Proxy和Reflect

前言

在Vue3.0中,Proxy起到了至关重要的作用,它改善了之前版本使用Object.defineProperty的诸多限制,作为一个前端工程师,必须来学习学习Proxy,而和Proxy形影不离的兄弟Reflect同样也值得我们学习

什么是Proxy呢?

MDN中的定义:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

官方的描述其实挺难理解的,言简意赅,很精准,但是还是需要大白话来仔仔细细的解释下这段话。

其实是这样的,Proxy又叫拦截器,就是在你操作对象或函数前,进行拦截,拦截的同时提供了做了某些操作,通过Proxy,我们可以不操作对象,通过Proxy代理对象来间接操作对象或函数来达到我们的目的。

感觉描述的还不是很清楚,我们来看个例子吧,看完一定会一目了然。

let obj = {
	name:'Juejin'
}
let customProxy = new Proxy(obj,{
	get(target,property){
    	console.log(`我在获取${property}属性的值`);
        return target[property]
    }
})

console.log(customProxy.name)
复制代码

例子运行结果如下

在上述例子中,我们定义了一个对象obj,并用new Proxy生成了它的拦截器用来代理这个obj对象,get行为做了修改,就是说我们获取name这个属性时,会执行get函数里的console.log(我在获取${property}属性的值)这个方法。

现在是不是对Proxy有一点点理解了呀。

Proxy的语法

定义如下:const p = new Proxy(target, handler)

  • target:就是你要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:是一个对象,里面有各种你可以代理的方法

结合上述例子看一看,是不是很清晰明了呢,这个时候你肯定有疑问了,那么handler参数里都有什么方法呢,我们接下来来看一看

handler里参数的方法

handler里参数的方法大概包括代理对象的各种默认行为。

handler.get(target,property,receiver)

该方法用于拦截对象的读取属性操作。就是当读取对象某个属性的时候,可以做一些操作

参数如下

  • target:就是你代理的对象
  • property:你要读取的属性
  • receiver:Proxy或者继承Proxy的对象

该函数可以返回任何值

例子

let obj = {
	age:10
}
let handler = {
	get(target,property,receiver){
    	console.log(target,property,receiver)
        return '我改变了值'
    }
}
const objp = new Proxy(obj,handler)
console.log(objp.age);
复制代码

运行结果如下

在这个例子中我们打印了三个参数,可以清晰的看出来与语法中描述的一致,然后我们改变了get函数的返回值,使其返回了'我改变了值'这句话,然后获取age属性得到的就是'我改变了值'而不是10。如果我们想获取原有的属性,只需要将其返回就好了return target[property]

handler.get的应用

大概我能想到就是,当你封装的对象的某个属性不想被外界访问的时候可以使用get来拦截。

let obj = {
  secret:'我是密码,我不想被外面拿到'
}
//定义proxy
const objp = new Proxy(obj,{
	get(target,property){
    	if(property==='secret'){
       		throw Error(`${property}属性不允许被访问`) 	
        }else{
        	return target[property]
        }
    }
})
console.log(objp.secret)
复制代码

运行结果如图所示

handler.set(target, property, value, receiver)

该方法用于设置属性值得时候进行拦截,就是当读取对象某个属性的时候,可以做一些操作

参数如下

  • target:就是你代理的对象
  • property:你要读取的属性
  • value:你要设置的新值
  • receiver:Proxy或者继承Proxy的对象

例子

let obj = {
	name:'juejin',
    age:100
}
const objp =  new Proxy(obj,{
	set(target, property, value, receiver){
    	console.log(target,property,value,receiver)
        //当设置的只重複時,抛出警告
        if(value===target[property]){
        	console.warn('該值不可重複設置');
            throw Error('該值不可重複設置')
        }else{
            target[property] = value
        	return  target[property]
        }
    }
})
objp.name = "juejin";
复制代码

运行结果

该例子在设置name属性的时候判断了新设置的值是否一致,然后抛出错误

handler.apply(target, thisArg, argumentsList)

该方法用于拦截函数的调用,参考object的apply的作用

参数如下

  • target:就是你代理的对象(必须是函数)
  • thisArg:被调用时的上下文对象。(this)
  • argumentsList:被调用时的参数数组

它也可以返回任何值哦

其实就是跟Object的apply一个道理,我们来看个例子 需要求每个值

function sum(a,b){
	return a+b
}
const sump = new Proxy(sum,{
	apply(target,thisArg,argumentsList){
    	console.log(target,thisArg,argumentsList)
     	if(argumentsList.length>2){
        	throw new Error('该方法只接收两个参数,请避免输入过多参数');
        }
        return Reflect.apply(target, thisArg, argumentsList);
        /
    }
})
sump(1,2,2)
复制代码

上述例子使用apply拦截了函数的调用用来限定了参数的个数,

运行结果

总结

Proxy的handlder参数里,还有各种api,可以自行查找翻阅,总之就是一句话,Proxy可以拦截,并改变对象的默认行为,利用这一点我们可以做很多事情,例如vue3的双向绑定。

在apply函数的例子中可以看到在返回值得时候,我们使用了Reflect来进行调用,那么这个Reflect到底是什么呢?下一部分我们来看看它

Reflect部分

到底什么是Reflect呢,官方定义翻译为反射,我们先来看看官方定义是怎样的

MDN中定义:Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。

个人理解,就是Reflect是ES6为了操作对象而提供的新API,未来的新方法将只部署在Reflect对象上,也就是Reflect的原型是Object. 我们先来验证一下:

let obj = {};
console.log(Reflect.__proto__ === Object.prototype); 
console.log(obj.__proto__ === Reflect.__proto__); 

let str = '111';

console.log(str.__proto__); 
复制代码

运行结果如下: 上述例子可以证明,Object就是Reflect的原型

那么为什么,有了Object还要设计Reflect这个东西呢,有以下几个原因:

1.函数操作,如果要判断一个obj有定义或者继承了属性name, 在ES5中这样判断:name in obj ; 或者删除一个属性 :delete obj[name], 虽然这些很好用, 很简短, 很明确, 但是要使用的时候也要封装成一个类;

有了Reflect, 它帮你封装好了, Reflect.has(obj, name), Reflect.deleteProperty(obj, name);

2.想要得到更加明确的返回值,比如在使用对象的 Object.defineProperty(obj, name, {})时,如果出现异常的话,会抛出一个错误,需要使用try catch去捕获,但是使用 Reflect.defineProperty(obj, name, desc) 则会返回false

//ES5
try {
  Object.defineProperty(target, property, attributes);
} catch(e) {
  // 失败
}

// 使用Reflect
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}
复制代码

还有等等一些方面,才有了Reflect

和Proxy的关系

Reflect与Proxy是相辅相成的,Reflect拥有所有Proxy对象的方法

文章分类
前端
文章标签