vue2响应式应用了Object.defineProperty,vue3中的响应式则是运用proxy。
目录标题
- 1、defineProperty
- 2、代码理解defineProperty
- 3、手写vue2响应式原理
- 4、vue2监听数组响应式
- 5、Proxy理解
- 6、总结
1、defineProperty
Object.defineProperty(obj, prop, descriptor)
obj:要定义属性的对象
prop:要定义或修改的属性的名称或 Symbol
descriptor:要定义或修改的属性描述符
返回值:被传递给函数的对象。
configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false。
enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false。
数据描述符还具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined。
writable
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。 默认为 false。
存取描述符还具有以下可选键值:
get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined。
set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined。
描述符默认值汇总
拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined。
2、代码理解defineProperty
监听obj数据变化
let obj={}
Object.defineProperty(obj,"key",{
value:1,
writable:true,//是否可以赋值
enumerable:true,//是否可循环
configurable:true //是否可删除
})
console.log(obj);
let obj1={}
Object.defineProperty(obj1,'key',{
get(){
console.log("获取到的值:",value);
return value
},
set(newVal){
console.log("设置值为",newVal);
value=newVal+"设置"
}
})
obj1.key="橙子很甜"
打印结果:
3、手写vue2响应式原理
let obj = {
name: "橙子",
job: {
code: 100
},
arr:[1,2,3]
}
//封装监听数据变化
function defineProperty(obj, key, val) {
//遍历监听数据每一项
observer(val);
Object.defineProperty(obj, key, {
get() {
//读取
console.log("读取key", key);
return val
},
set(newVal) {
//判断新老值
if (newVal === val) return
observer(newVal);
console.log("设置", newVal);
val = newVal
}
})
}
function observer(obj) {
//先判断是不是object
if (typeof obj !== 'object' || obj == null) {
return
}
for (const key in obj) {
//对象,当前的key,值
defineProperty(obj, key, obj[key])
}
}
observer(obj)
console.log(obj.name);
obj.name = "蛋挞排队"
//...
打印结果:
obj.job.code=300
打印结果:
obj.name = {
name1: ‘张三’
}
obj.name.name1 = “李四”
打印结果:
注意
- defineProperty中没有办法监听数组的变化
- 监听数组时读取成功但是没办法监听
- vue2用了重写了数组的方法监听数组,defineProperty本身是不能监听数组
- vue2中监听不到通过数组下标改变数组元素
obj.arr.push(4)
console.log(obj.arr);
Object.defineProperty 是对象的方法监听不到数组的变更的 Vue2.x的做法是重写数组的7个方法。
把数组原型上所有的方法拿出来,即先克隆一份Array的原型出来,给每一个方法都设置响应式
4、vue2监听数组响应式
// Object.defineProperty 是对象的方法监听不到数组的变更的 Vue2.x的做法是重写数组的7个方法
// 封装监听数据变化的函数
let obj = {
name: "橙子",
job: {
code: 100
},
arr:[1,2,3]
}
const arrayMethods = Array.prototype;
// 把数组原型上所有的方法拿出来,即先克隆一份Array的原型出来,这七个方法
const arrayProto = Object.create(arrayMethods);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
// 执行原始操作,给它添加监听,挂到原型链上,内部还是执行数组的方法
arrayMethods[method].apply(this, arguments)
console.log('监听赋值成功', method)
}
})
function defineProperty(obj, key, val) {
observer(val)
Object.defineProperty(obj, key, {
get() {
// 读取方法
console.log('读取', key, '成功')
return val
},
set(newval) {
// 赋值监听方法
if (newval === val) return
// 遍历监听数据的每一项
observer(newval)
console.log('赋值成功', newval)
val = newval
// 可以执行渲染操作
}
})
}
function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
if (Array.isArray(obj)) {
// 如果是数组, 重写原型
obj.__proto__ = arrayProto
// 传入的数据可能是多维度的,也需要执行响应式
for (let i = 0; i < obj.length; i++) {
observer(obj[i])
}
} else {
for (const key in obj) {
// 给对象中的每一个方法都设置响应式
defineProperty(obj, key, obj[key])
}
}
}
observer(obj)
obj.arr.push(4)
5、Proxy理解
const p = new Proxy(target, handler)
proxy(数据源对象,处理数据方法)
let obj = {
name: '小陈'
}
let handler = {
get(target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
proxy.name = '橙汁'
handler 对象的方法
handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
handler.getPrototypeOf()
Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()
Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible()
Object.isExtensible 方法的捕捉器。
handler.preventExtensions()
Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty()
Object.defineProperty 方法的捕捉器。
handler.has()
in 操作符的捕捉器。
handler.get()
属性读取操作的捕捉器。
handler.set()
属性设置操作的捕捉器。
handler.deleteProperty()
delete 操作符的捕捉器。
handler.ownKeys()
Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply()
函数调用操作的捕捉器。
handler.construct()
new 操作符的捕捉器。
在官网可以看到proxy的用法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
监听数组变化
let arr = [1, 2, 3]
let proxy = new Proxy(arr, {
get(target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
})
proxy.push(4)
6、总结
1、Proxy 的第二个参数可以有 13 种拦截方法, 比 Object.defineProperty() 要更加丰富,
Proxy 作为新标准受到浏览器厂商的重点关注和性能优化。
2、相比之下Object.defineProperty() 是一个已有的老方法。Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
3、Proxy 的兼容性不如 Object.defineProperty() 。
4、proxy可以直接监听数组变化,监听的目标为对象本身,不需要像defineProperty那样遍历每个属性