关于前端反射与代理的那点事

118 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

前言

js认为xx.xx等调用行为是不安全的,而应该基于内聚的API集中去处理,所以提出了反射的概念,而代理则可以基于反射的基础去实现符合规范的API调用。

属性描述符

Object.defineProperty

属性描述符一般是指Object.defineProperty,通过该方法可以对对象的属性进行描述操作。同时这个属性也是vue2响应式系统的关键。 具体用法如下:

var obj = {
	a:1,
}
	        
Object.defineProperty(obj, 'a', {
	value: 2,//值赋值成2,当设置了get、set时不可以写该属性
	writable: true,//是否可以被重新赋值,当设置了get、set时不可以写该属性
	enumerable: true,//是否可枚举,不可枚举的话通过for in循环不出来
	configurable: true,//该属性的描述是否可以修改
	get(){
     	//可以在这里做一些事情,当读取obj.a时会运行该函数,不可以在该函数里操作obj.a,否则会无限递归
     },
	set(val){
     	//可以在这里做一些事情,当对obj.a进行赋值时会运行该函数,不可以在该函数里操作obj.a,否则会无限递归
     	//参数将要赋值的值
     }
    }
})

Object.defineProperties

该属性可以同时对多个属性进行描述

var obj = {
	a: 1
}
		        
Object.defineProperties(obj, {
	a: {
         value: 2,
         writable: true,
         enumerable: false,
         configurable: true
     },
    b: {
         value: 3,
         writable: true,
         enumerable: false,
         configurable: true
     }
 })

反射

Reflect

Reflect是一个内置的js对象,可以调用该对象里面的方法访问一些js底层功能。 Reflect对象将各种数据操作高度聚合。

常见用法

var obj ={a:1}

function method(a,b){
	return a + b;
}

//赋值
//es6认为例如obj.a直接赋值是一种魔法。可以通过Reflect.set进行赋值。
Reflect.set(obj,'a',2)

//取值
Reflect.get(obj,'a')

//函数调用
//es6认为例如method(1,2)的函数调用一种魔法,可以通过Reflect.apply(函数名,传递绑定的this,参数列表)
Reflect.apply(method,null,[1,2])

//删除
//es6认为delete这个关键字是一种魔法,可以通过Reflect.deleteProperty进行处理
Reflect.deleteProperty(obj,a)

//存储器属性
//与Object.defineProperty相对应的,Reflect.defineProperty(target,propertyKey)也可以拥有存储器功能,不同的是如果配置出错不会报错,而是返回false

//创建对象
//es6认为new关键字是魔法,所以提供了Reflect.construct(构造函数,参数列表)

//判断对象是否存在某个属性
Reflect.has(obj,'a')

更多Reflect API

developer.mozilla.org/zh-CN/docs/…

代理

Proxy

Proxy提供了修改底层实现的方式

var obj = {
	a:1,
	b:2
}
//代理一个目标对象,
//targe:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象

var proxy = new Proxy(obj,{
	get(target,targeyKey,value,receiver){
		return Reflect.get(target,propertyKey)
		//通过return返回值
	},
	
	set(target,targeyKey,value,receiver){
		console.log(target,targetKey,value,receiver)
		
		//修改值的操作
		target[propertyKey] = value
		Reflect.set(target,propertyKey,value)
	}
})

proxy.a = 1 //触发了代理里的set方法


关于Proxy,如其名,作为代理,可以把它理解为你的代理律师,通过配置你的代理律师在接收到任何访问你的处理时都会先通过代理配置去操作。你与第三方之间的通信只能通过代理律师这个媒介。

观察者模式

//基于Object.defineProperty的实现
function observer(targetObject){
	const app = document.getElementById('#app')
	const ob = {}
	for(const prop of props){
		Object.defineProperty(ob,prop,{
			enumerable:true,
			configurable:true,
			get(){
				return target[prop]
			},
			set(val){
				target[prop] = val
				render() //其他操作
			}
		})
	}
}

//基于Proxy的实现
function observer(targetObject){
	const app = document.getElementById('#app')
	const proxy = new Proxy(target,{
		get(){
			return Reflect.get(target,prop)
		},
		set(target,prop,value){
			Reflect.set(target,prop,value)
			render()
		}
	})
}

可以看出,object.defineProperty的观察者创建了ob的观察者对象,相比Proxy多了更多的内存占用,而Proxy基于底层的实现,在性能上也比前者更好。

小结

在理解了反射的基础上去理解代理,有助于我们去遵守一些常用的设计规范,而代理的概念在vue3源码里得到了应用,当遇到特殊的业务场景需要实现拦截监听时,用代理去实现也是一种解决策略。