详解Vue响应式的部分实现
小婉子啊 人气:0什么是响应式
简单来说当数据发生变化时,对数据有依赖的代码会重新执行。
例如在Vue中,当我们的数据发生改变,界面上对该数据的引用组件会重新渲染
组件data的数据一旦变化,立即出发视图的更新;
computed属性在依赖发生变化时,自动重新计算新值;提供watch监听器,可以监听到数据的变化
Vue2与Vue3响应式之间的区别
- Vue2使用ES5的defineProperty实现
- Vue3使用的是ES6的propxy.(PS:这也就是为什么Vue2不支持IE7/8,而Vue3不支持IE11.)
使用Object.defineProperty监听对象
该方法允许精确地添加或修改对象的属性,并返回此对象。
Object.defineProperty()方法会在直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
备注:(应当直接在object构造器对象上调用此方法,而不是在任意一个object类型的实例上调用)
语法:Object.defineProperty(obj, prop, descriptor)
- obj:要设置属性的对象;
- prop:要设置的属性名,这个属性可以是已存在也可以是不存在的;
- descriptor:要定义或修改的属性描述符。该参数接收一个对象,用来对属性进行描述。如value(值),writable(是否可重写),enumerable(是否可枚举)等
枚举时使用for...in 或 Object.keys方法可以改变这些属性的值,默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符
- 数据描述符:是一个具有值的属性,该值是可写的,也可以是不可写的
- 存取描述符:由getter函数和setter函数所描述的属性
一个描述符只能是这两者其中之一,不能同时是两者
使用Object.defineProperty监听对象
利用 Object.defineProperty
重写 get
和 set
,将对象属性的赋值和获取变成函数,我们可以实现一个简单的双向绑定
- get: 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值会被用作属性的值。默认为 undefined。
- set: 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。默认为 undefined。
//实现一个简单的双向绑定 const data = {} const name = 'xiaowanzi' Object.defineProperty(data, 'name', { get: function () { console.log('get') return name }, set: function (newVal) { console.log('set') name = newVal } }) //测试 console.log(data.name)//get xiaowanzi data.name = 'list'//set
如果我们想让对象的所有属性都具有响应式,就需要对全部属性进行遍历,实现getter和setter:
//实现Vue响应式原理 let obj = { name: 'aaa', age: 18 } //获取obj对象的所有key const keys = Object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 //遍历Key数组,对obj对象的每一个属性进行处理 keys.forEach(key => { //使用value变量保存key对应的属性值 let value = obj[key] //使用Object.defineProperty Object.defineProperty(obj, key, { get() {//当获取属性时,回来到这里 console.log(`${key}属性被获取`) return value }, set(newValue) {//当修改属性时,会来到这里,并且设置的值会传给newValue console.log(`${key}属性被修改`) //这里不能写成obj[key]=newValue //如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了 value = newValue } }) }) //现在我们已经可以实现监听obj对象的读取与修改了 console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。 obj.name = 'bbb'//打印name属性被修改,也就是说监听到了属性的改变 //实现Vue响应式原理 let obj={ name:'aaa', age:18 } //获取obj对象的所有key const keys=object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 //遍历Key数组,对obj对象的每一个属性进行处理 keys.forEach(key=>{ //使用value变量保存key对应的属性值 let value = obj[key] //使用Object.defineProperty Object.defineProperty(obj,key,{ get(){//当获取属性时,回来到这里 console.log(`${key}属性被获取`) return value }, set(newValue){//当修改属性时,会来到这里,并且设置的值会传给newValue console.log(`${key}属性被修改`) //这里不能写成obj[key]=newValue //如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了 value=newValue } }) }) //现在我们已经可以实现监听obj对象的读取与修改了 console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。 obj.name='bbb'//打印name属性被修改,也就是说监听到了属性的改变
缺点
可以实现监听对象的属性,但是它没有办法做到对对象新增的属性进行监听,同时也没有办法做到对数据进行监听
使用ES6的Proxy实现监听对象
该API就是用来实现监听对象的,而且该API对数组同样也是有效果的,在使用Proxy
时,通常会搭配Reflect
一起使用 Proxy
用于创建代理对象,从而实现基本操作的拦截和自定义(如属性的查找,赋值,枚举,函数调用等)
术语:
- handler:包含捕捉器(trap)的占位符对象,可译为处理器对象。
- traps:提供属性访问的方法。这类似于操作系统中捕获器的概念。
- target:被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)
语法:
const p = new Proxy(target, handler)
1.第一个参数target:要包装的目标对象
2.第二个参数handle:接收一个对象,内部定义了操作目标对象时的方法;
参数:
- target:要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 - handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
- Proxy.revocable()
- 创建一个可撤销的
Proxy
对象。
Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect
不是一个函数对象,因此它是不可构造的。
通过给对象设置代理,我们可以拦截对象属性的取值/赋值操作。
举个例子:
const student = { age: 23 } const handler = { get(target, prop) { console.log("读值:", key, value); target[key] = value; return target[prop] }, set(target, key, value) { console.log("设置值", key, value); target[key] = value; return true } } const proxy = new Proxy(studengt, handler) console.log(proxy.age)//23 //proxy.age=32 //32
实现代码
//Proxy+Reflect let obj = { name: 'aaa', age: 18 } //第一个参数为要代理的对象,第二个参数位hander const proxy = new Proxy(obj, { //当访问第一个属性的时候会得到getter //同时会传递三个参数 //target要进行代理对象,这里就是obj //key被访问的属性 //receiver用来绑定this get(target, key, receiver) { console.log(`${key}属性被访问`) return Reflect.get(target, key, receiver) }, //当某一属性修改的时,回来到Setter // 同时会传递四个参数 // target要进行代理的对象,这里就是obj // 可以被访问的属性 // newValue新修改的值 // receiver用来绑定this set(target, key, newValue, receiver) { console.log(`${key}属性修改`) return Reflect.set(target, key, newValue, receiver) } }) // 以上代码执行完,得到的就是proxy对象就是obj对象的代理 // 我们只需要修改代理对象的就可以做到修改原型对象的效果 // 而且我们对代理对象的修改使我们能够监听到的 console.log(proxy.name) proxy.name = 'bbb'
加载全部内容