理解Vue3 为什么使用 Proxy

前言

Vue 3.0 开始 Proxy 代替 Object.defineProperty 那么 Proxy 是什么,Proxy能干什么,Vue中 Object.defineProperty 和 Proxy的区别是什么,为什么要替换

Proxy是什么?

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

通俗的讲Proxy是一个对象操作的拦截器,拦截对目标对象的操作,进行一些自定义的行为。

Proxy怎么用?

let p = new Proxy(target, handler);

target 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler一个对象,其属性是当执行一个操作时定义代理的行为的函数。

【举个例子】

var proxy = new Proxy({
    
    }, {
    
    
  get: function(target, property) {
    
    
    return 35; 
  }
});

let obj = Object.create(proxy);
obj.time // 35

无操作转发代理

代理对象p会将所有应用到它的操作转发到目标对象target上,可以直接对代理对象进行操作,操作会转发到目标对象上。

【举个例子】

let target = {
    
    };
let p = new Proxy(target, {
    
    });

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发

可以拦截的操作

一共有 13 种可代理操作,每种操作的代号(属性名/方法名)和触发这种操作的方式列举如下。注意,如果没有定义某种操作,那么这种操作会被转发到目标对象身上。

handler.getPrototypeOf()

在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。

handler.setPrototypeOf()

在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。

handler.isExtensible()

在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。

handler.preventExtensions()

在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。

handler.getOwnPropertyDescriptor()

在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, “foo”) 时。

handler.defineProperty()

在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, “foo”, {}) 时。

handler.has()

在判断代理对象是否拥有某个属性时触发该操作,比如在执行 “foo” in proxy 时。

handler.get()

在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时.

handler.set()

在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。

handler.deleteProperty()

在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。

handler.ownKeys()

在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。

handler.apply()

当目标对象为函数,且被调用时触发。

handler.construct()

在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。

this的指向

需要特别注意代理对象和拦截操作中的this指向问题

一旦对象被代理之后,他的this就指向了代理对象

const target = {
    
    
  m: function () {
    
    
    console.log(this === proxy);
  }
};
const handler = {
    
    };

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

handler定义的拦截操作中this指向handler,receiver指向的才是proxy

const target = {
    
    
	m: 100
};
const handler = {
    
    
	get(target, property,receiver){
    
    
		console.log(this === handler)
		console.log(receiver === proxy)
		return target[property]
	}
};

const proxy = new Proxy(target, handler);
console.log(proxy.m)

Proxy能干什么?

Proxy 各种常用的拦截都能干什么,常用的四种操作

get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身。所有的属性调用都会进入同一个get即使是没有的属性。

  • 可以创造一些本来没有的属性
  • 可以在取数据时对数据进行验证和转换
  • 可以自定义一些语法糖操作
  • get返回一个函数的话可以把一个属性转换成方法

【举个例子】

var base = {
    
    
	a:100,
	small:"hello world!!"
}

var proxy = new Proxy(base,{
    
    
	get(target, property,receiver){
    
    
		//属性转换成函数
		if("fn" === property ){
    
    
			return function(value){
    
    
	      		console.log(value)
	      	}
		}
		
		if("ghost" === property ){
    
    
			return "ghost"
		}
		
		
		
		if("fn" === property ){
    
    
			return function(value){
    
    
	      		console.log(value)
	      	}
		}
		//自定义语法糖
		if(property.includes("_")){
    
    
			const direct = property.split("_")[1]
			const propertyBase = property.split("_")[0]
			switch (direct){
    
    
				case "Big":
					return receiver[propertyBase].toLocaleUpperCase()
				default:
					break;
			}
		}
		
		if(!(property in target)){
    
    
			throw new ReferenceError("Property \"" + property + "\" does not exist.");//验证属性值
		}
		
		return target[property]

	}
})

console.log(proxy.a)//输出100  正常访问
proxy.fn("fn")//输出fn  属性转换为方法
console.log(proxy.small_Big)//输出 HELLO WORLD!!   自定义语法糖
console.log(proxy.ghost)//输出ghost   创建属性
console.log(proxy.ghost_Big)//输出GHOST  receiver的使用
console.log(proxy.b)//数据验证  抛出错误

set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

  • 可以用来验证属性是否符合要求
  • 可以用来更改数据格式
  • 可以用来监听数据更改事件
  • 屏蔽一些赋值操作比如以“_”开头的私有变量

【举个例子】

var base = {
    
    
	a:100,
	small:"hello world!!"
}


const A_Change_Event="aChangeEvent";
window.addEventListener(A_Change_Event,(e)=>{
    
    
	console.log(e.data.value)
})

var proxy = new Proxy(base,{
    
    
	set(obj, property, value,receiver){
    
    

		if("a" === property ){
    
    
			if (!Number.isInteger(value)) {
    
     
				throw new TypeError('The'+property+'is not an integer');
			} 
			if (value > 100){
    
     
				throw new RangeError('The '+property+' seems invalid'); 
			}
			
			
			obj[property] = value;
			
			//事件
			const dataChangeEvent = new Event(A_Change_Event);//创建一个事件
			dataChangeEvent.data = {
    
    value}
			window.dispatchEvent(dataChangeEvent)
			
		}
		
	}
})
proxy.a = 80;
console.log(proxy.a)
proxy.a = 120;

apply()

函数对象也可以作为代理的目标对象

apply方法拦截函数的调用、call和apply操作。

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

【举个例子】

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"

construct ()

construct 拦截的是new操作

接受三个参数

  • target:目标对象
  • args:构造函数的参数对象
  • newTarget:创造实例对象时,new命令作用的构造函数

Vue中的Proxy和Object.defineProperty

Object.defineProperty 实现observe

递归遍历所有属性,使用Object.defineProperty挨个定义监听

var data = {
    
    name: 'kindeng'};
observe(data);
data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq

function observe(data) {
    
    
    if (!data || typeof data !== 'object') {
    
    
        return;
    }
    // 取出所有属性遍历
    Object.keys(data).forEach(function(key) {
    
    
        defineReactive(data, key, data[key]);
    });
};

function defineReactive(data, key, val) {
    
    
    observe(val); // 监听子属性
    Object.defineProperty(data, key, {
    
    
        enumerable: true, // 可枚举
        configurable: false, // 不能再define
        get: function() {
    
    
            return val;
        },
        set: function(newVal) {
    
    
            console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
            val = newVal;
        }
    });
}

Proxy实现observe

不用提前挨个遍历绑定绑定。每次根据key来判断

observe(data) {
    
    
    const that = this;
    let handler = {
    
    
    get(target, property) {
    
    
        return target[property];
    },
    set(target, key, value) {
    
    
        let res = Reflect.set(target, key, value);
        that.subscribe[key].map(item => {
    
    
          item.update();
        });
        return res;
      }
    }
    this.$data = new Proxy(data, handler);
  }

为什么用Proxy代替Object.defineProperty?

vue3.0使用了Proxy替换了原先遍历对象使用Object.defineProperty方法给属性添加set,get访问器的笨拙做法。也就是说不需要遍历了,而是直接监控data对象。

Guess you like

Origin blog.csdn.net/qq_52151772/article/details/120102438