Proxy理解以及vue3响应式原理

Proxy的应用可以使函数更加强大,业务逻辑更加清楚,而且在编写自己的框架或者通用组件时非常好用。Proxy涉及的内容非常多,那这里我就带你入门并且介绍给你后续的学习方法。

在学习新知识之前,先来回顾一下定义对象的方法。

var obj={
    add:function(val){
        return val+10;
    },
    name:'I am 大鲨鱼'
 
};
console.log(obj.add(100));
console.log(obj.name);

声明了一个obj对象,增加了一个对象方法add和一个对象属性name,然后在控制台进行了打印。

声明Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

我们用new的方法对Proxy进行声明。可以看一下声明Proxy的基本形式。

var proxy = new Proxy(target, handler);

需要注意的是这里是两个花括号,第一个花括号就相当于我们方法的主体,后边的花括号就是Proxy代理处理区域,相当于我们写钩子函数的地方。

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

现在把上边的obj对象改成我们的Proxy形式。

var pro = new Proxy({
    add: function (val) {
        return val + 10;
    },
    name: 'I am 大鲨鱼'
}, {
        get:function(target,key,property){
            console.log('come in Get');
            return target[key];
        }
    });
 
console.log(pro.name);

可以在控制台看到结果,先输出了come in Get。相当于在方法调用前的钩子函数。

get属性

get属性是在你得到某对象属性值时预处理的方法,他接受三个参数

target:得到的目标值
key:目标的key值,相当于对象的属性
property:这个不太常用, proxy 实例本身(严格地说,是操作行为所针对的对象),参数可选。
set属性

set属性是值你要改变Proxy属性值时,进行的预先处理。它接收四个参数。

  • target:目标值。
  • key:目标的Key值。
  • value:要改变的值。
  • receiver:改变前的原始值。
var pro = new Proxy({
    add: function (val) {
        return val + 10;
    },
    name: 'I am qyz'
}, {
        get:function(target,key){
            console.log('come in Get');
            return target[key];
        },
        set:function(target,key,value,receiver){
            console.log(`    setting ${key} = ${value}`);
            return target[key] = value;
        }
 
    });
 
console.log(pro.name);
pro.name='大鲨鱼';
console.log(pro.name);

apply的使用

apply的作用是调用内部的方法,它使用在方法体是一个匿名函数时。看下边的代码。

let target = function () {
    return 'I am 大鲨鱼';
};
var handler = {
    apply(target, ctx, args) {
        console.log('do apply');
        return Reflect.apply(...arguments);
    }
}
 
var pro = new Proxy(target, handler);
 
console.log(pro());

vue3的响应式原理

// 使用Proxy实现完美劫持
			const obj = {name: "vue", arr: [1, 2, 3]};
			function proxyData(value) {
			    const proxy = new Proxy(value, {
			        get(target, key) {
			            console.log(`get key is ${key}`);
			            const val = target[key];
			            if (typeof val === "object") {
			                return proxyData(val);
			            }
			            return val;
			        },
			        set(target, key, value) {
			            console.log(`set key is ${key}, value is ${value}`);
			            return target[key] = value;
			        },
			        deleteProperty(target, key) {
			            console.log(`delete key is ${key}`);
			        }
			    });
			    return proxy;
			}
			const proxy = proxyData(obj);
			proxy.age = 18; // 可对新增属性进行劫持
			delete proxy.name; // 可对删除属性进行劫持
			proxy.arr.push(4); // 可对数组的push等方法进行劫持
			proxy.arr[3] = 4; // 可对象数组的索引操作进行劫持

Vue3的响应式系统被放到了一个单独的@vue/reactivity模块中,其提供了reactive、effect、computed等方法,其中reactive用于定义响应式的数据,effect相当于是Vue2中的watcher,computed用于定义计算属性。
源码目录结构:

import {reactive, effect, computed} from "@vue/reactivity";
const state = reactive({
    name: "qyz",
    age: 24,
    arr: ['a','b','c']
});
console.log(state); // 这里返回的是Proxy代理后的对象
effect(() => {
    console.log("effect run");
    app.innerHTML = state.name + '==' + state.age + '_' + state.arr.length
    console.log(state.name); // 每当name数据变化将会导致effect重新执行
});
 
 
setTimeout(() => {
  state.name = "vue"; // 数据发生变化后会触发使用了该数据的effect重新执行
  state.arr.length = 2
}, 1000)
 
 
const user = computed(() => { // 创建一个计算属性,依赖name和age
    return `name: ${state.name}, age: ${state.age}`;
});
effect(() => { // name和age变化会导致计算属性的value发生变化,从而导致当前effect重新执行
    console.log(`user is ${user.value}`);
});

Vue3中采用proxy实现数据代理, 核心就是拦截get方法和set方法,当获取值时收集effect函数,当修改值时触发对应的effect重新执行

 reactive() 方法本质是传入一个要定义成响应式的target目标对象,然后通过Proxy类去代理这个target对象,最后返回代理之后的对象,如:

export function reactive(target) {
    return new Proxy(target, {
        get() {
 
        },
        set() {
            
        }
    });
}
 

源码:

// 创建响应式数据
function reactive(target) { 
    if (target && target["__v_isReadonly" /* IS_READONLY */]) {
        return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
 
// 创建浅的响应式数据
function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);
}
 
// 创建仅读
function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}
 
// 创建浅的仅读数据
function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);
}
 
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // 对目标数据进行判断,如果不是对象 直接返回
    if (!isObject(target)) {
        return target;
    }
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
    // 目标对象是否有被代理过
    const existingProxy = proxyMap.get(target);
    // 被代理过了 直接返回缓存中的代理数据
    if (existingProxy) {
        return existingProxy;
    }
    // 是否需要被跳过
    const targetType = getTargetType(target);
    if (targetType === 0 /* INVALID */) {
        return target;
    }
    // 对目标对象进行代理 并判断是普通对象、数组还是集合类型数据
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    // 把代理后的数据进行缓存
    proxyMap.set(target, proxy);
    return proxy;
  • 通过Reflect(反射): 对源对象的属性进行操作。
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script>
			let obj = {zhangsan:100,lisi:200}
			Object.defineProperty(obj,'wangwu',{
				get(){
					return 300
				}
			})
			Object.defineProperty(obj,'wangwu',{
				get(){
					return 400
				}
			})
		</script>
	</body>
</html>

 换成Reflect:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script>
			let obj = {zhangsan:100,lisi:200}
			Reflect.defineProperty(obj,'wangwu',{
				get(){
					return 300
				}
			})
			Reflect.defineProperty(obj,'wangwu',{
				get(){
					return 400
				}
			})
		</script>
	</body>
</html>

不报错 兼容性更好 用Object.defineProperty不好捕获错误 要用try…catch很麻烦

用Reflect可以很好捕获错误

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script>
			let obj = {zhangsan:100,lisi:200}
			const a = Reflect.defineProperty(obj,'wangwu',{
				get(){
					return 300
				}
			})
			const b = Reflect.defineProperty(obj,'wangwu',{
				get(){
					return 400
				}
			})
			console.log(a);
			console.log(b);
		</script>
	</body>
</html>

 由此可见 vue3 使用Proxy和Reflect结合实现响应式原理。 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值