注册

Object.defineProperty也能监听数组变化?


首先,解答一下标题: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.definePropertyset 里面我有调用了一次 observer(newValue)observer 会判断传入的值是不是对象,如果是对象就再次调用 defineReactive 方法。


这样可以模拟一个递归的状态。




以上就是 深度监听 的原理,其实就是递归。


但递归有个不好的地方,就是如果对象层次很深,需要计算的量就很大,因为需要一次计算到底。






监听数组


数组没有 key ,只有 下标。所以如果需要监听数组的内容变化,就需要将数组转换成对象,并且还要模拟数组的方法。


大概的思路和编码流程顺序如下:


  1. 判断要监听的数据是否为数组
  2. 是数组的情况,就将数组模拟成一个对象
  3. 将数组的方法名绑定到新创建的对象中
  4. 将对应数组原型的方法赋给自定义方法



代码如下所示


// 触发更新视图
function updateView() {
console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原形指向 oldArrayProperty,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);

['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName =

0 个评论

要回复文章请先登录注册