本文简介
点赞 + 关注 + 珍藏 = 学会了
首先,解答一下题目:Object.defineProperty
不能监听原生数组的变动。如需监听数组,要将数组转成对象。
在 Vue2
时是应用了 Object.defineProperty
监听数据变动,但我查了下 文档,发现 Object.defineProperty
是用来监听对象指定属性的变动。没有看到能够监听个数组变动的。
但 Vue2
有确实能监听到数组某些办法扭转了数组的值。本文的指标就是解开这个结。
根底用法
Object.defineProperty() 文档
对于 Object.defineProperty()
的用法,能够看官网文档。
根底局部本文只做简略的解说。
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
要定义属性的对象。prop
要定义或批改的属性的名称或Symbol
。descriptor
要定义或批改的属性描述符。
const data = {}
let name = '雷猴'
Object.defineProperty(data, 'name', {get() {console.log('get')
return name
},
set(newVal) {console.log('set')
name = newVal
}
})
console.log(data.name)
data.name = '鲨鱼辣椒'
console.log(data.name)
console.log(name)
下面的代码会输入
get
雷猴
set
鲨鱼辣椒
鲨鱼辣椒
下面的意思是,如果你须要拜访 data.name
,那就返回 name
的值。
如果你想设置 data.name,那就会将你传进来的值放到变量 name
里。
此时再拜访 data.name
或者 name
,都会返回新赋予的值。
还有另一个根底用法:“解冻”指定属性
const data = {}
Object.defineProperty(data, 'name', {
value: '雷猴',
writable: false
})
data.name = '鲨鱼辣椒'
delete data.name
console.log(data.name)
这个例子,把 data.name
解冻住了,不论你要批改还是要删除都不失效了,一旦拜访 data.name
都一律返回 雷猴
。
以上就是 Object.defineProperty
的根底用法。
深度监听
下面的例子是监听根底的对象。但如果对象里还蕴含对象,这种状况就能够应用递归的形式。
递归须要创立一个办法,而后判断是否须要反复调用本身。
// 触发更新视图
function updateView() {console.log('视图更新')
}
// 从新定义属性,监听起来(外围)function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 外围 API
Object.defineProperty(target, key, {get() {return value},
set(newValue) {if (newValue != value) {
// 深度监听
observer(newValue)
// 设置新值
// 留神,value 始终在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发视图更新
updateView()}
}
})
}
// 深度监听
function observer(target) {if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 从新定义各个属性(for in 也能够遍历数组)for (let key in target) {defineReactive(target, key, target[key])
}
}
// 筹备数据
const data = {name: '雷猴'}
// 开始监听
observer(data)
// 测试 1
data.name = {lastName: '鲨鱼辣椒'}
// 测试 2
data.name.lastName = '蟑螂恶霸'
下面这个例子会输入 2 次“视图更新”。
我创立了一个 updateView
办法,该办法模仿更新 DOM
(相似 Vue
的操作),但我这里简化成只是输入“视图更新”。因为这不是本文的重点。
测试 1 会触发一次“视图更新”; 测试 2 也会触发一次。
因为在 Object.defineProperty
的 set
外面我有调用了一次 observer(newValue)
,observer
会判断传入的值是不是对象,如果是对象就再次调用 defineReactive
办法。
这样能够模仿一个递归的状态。
以上就是 深度监听 的原理,其实就是递归。
但递归有个不好的中央,就是如果对象档次很深,须要计算的量就很大,因为须要一次计算到底。
监听数组
数组没有 key
,只有 下标
。所以如果须要监听数组的内容变动,就须要将数组转换成对象,并且还要模仿数组的办法。
大略的思路和编码流程程序如下:
- 判断要监听的数据是否为数组
- 是数组的状况,就将数组模仿成一个对象
- 将数组的办法名绑定到新创建的对象中
- 将对应数组原型的办法赋给自定义办法
代码如下所示
// 触发更新视图
function updateView() {console.log('视图更新')
}
// 从新定义数组原型
const oldArrayProperty = Array.prototype
// 创立新对象,原形指向 oldArrayProperty,再扩大新的办法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {arrProto[methodName] = function() {updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
}
})
// 从新定义属性,监听起来(外围)function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 外围 API
Object.defineProperty(target, key, {get() {return value},
set(newValue) {if (newValue != value) {
// 深度监听
observer(newValue)
// 设置新值
// 留神,value 始终在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发视图更新
updateView()}
}
})
}
// 监听对象属性(入口)function observer(target) {if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 数组的状况
if (Array.isArray(target)) {target.__proto__ = arrProto}
// 从新定义各个属性(for in 也能够遍历数组)for (let key in target) {defineReactive(target, key, target[key])
}
}
// 筹备数据
const data = {nums: [10, 20, 30]
}
// 监听数据
observer(data)
data.nums.push(4) // 监听数组
下面的代码之所以没有间接批改数组的办法,如
Array.prototype.push = function() {updateView()
...
}
因为这样会净化原生 Array
的原型办法,这样做会得失相当。
以上就是应用 Object.defineProperty
的办法。
如需监听更多办法,能够在数组 ['push', 'pop', 'shift', 'unshift', 'splice']
中增加。
综合代码
// 深度监听
function updateView() {console.log('视图更新')
}
// 从新定义数组原型
const oldArrayProperty = Array.prototype
// 创立新对象,原形指向 oldArrayProperty,再扩大新的办法不会影响原型
const arrProto = Object.create(oldArrayProperty);
// arrProto.push = function () {}
// arrProto.pop = function() {}
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {arrProto[methodName] = function() {updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
}
})
// 从新定义属性,监听起来(外围)function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 外围 API
// Object.defineProperty 不具备监听数组的能力
Object.defineProperty(target, key, {get() {return value},
set(newValue) {if (newValue != value) {
// 深度监听
observer(newValue)
// 设置新值
// 留神,value 始终在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发视图更新
updateView()}
}
})
}
// 监听对象属性(入口)function observer(target) {if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
if (Array.isArray(target)) {target.__proto__ = arrProto}
// 从新定义各个属性(for in 也能够遍历数组)for (let key in target) {defineReactive(target, key, target[key])
}
}
总结
下面的代码次要是模仿 Vue 2
监听数据变动,尽管好用,但也有毛病。
毛病
- 深度监听,须要递归到底,一次计算量大
- 无奈监听新增属性 / 删除属性(所以须要应用 Vue.set 和 Vue.delete)
- 无奈原生监听数组,须要非凡解决
所以在 Vue 3
中,把 Object.defineProperty
改成 Proxy
。
但 Proxy
的毛病也很显著,就是兼容性问题。所以须要依据你的我的项目来抉择用 Vue 2
还是 Vue 3
。
举荐浏览
👍《『Three.js』腾飞!》
👍《Vue3 过 10 种组件通信形式》
👍《Fabric.js 从入门到旁若无人》
👍《Vite 搭建 Vue2 我的项目(Vue2 + vue-router + vuex)》
👍《console.log 也能插图!》
点赞 + 关注 + 珍藏 = 学会了