Object.defineProperty能否实现数组变化的监听功能?

2026-04-11 08:261阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2146个文字,预计阅读时间需要9分钟。

Object.defineProperty能否实现数组变化的监听功能?

原文简介:文本简介+点赞++关注++收藏=学会了解+首先,解答一下以下:Object.defineProperty不能监听原生数组的变异。如需监听数组,需将数组转换成对象。

Vue2时期使用了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 方法。

Object.defineProperty能否实现数组变化的监听功能?

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


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

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



监听数组

数组没有 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 => { 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 监听数据变化,虽然好用,但也有缺点。

缺点
  1. 深度监听,需要递归到底,一次计算量大
  2. 无法监听新增属性/删除属性(所以需要使用 Vue.set 和 Vue.delete)
  3. 无法原生监听数组,需要特殊处理

所以在 Vue 3 中,把 Object.defineProperty 改成 Proxy

Proxy 的缺点也很明显,就是兼容性问题。所以需要根据你的项目来选择用 Vue 2 还是 Vue 3



推荐阅读

本文共计2146个文字,预计阅读时间需要9分钟。

Object.defineProperty能否实现数组变化的监听功能?

原文简介:文本简介+点赞++关注++收藏=学会了解+首先,解答一下以下:Object.defineProperty不能监听原生数组的变异。如需监听数组,需将数组转换成对象。

Vue2时期使用了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 方法。

Object.defineProperty能否实现数组变化的监听功能?

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


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

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



监听数组

数组没有 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 => { 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 监听数据变化,虽然好用,但也有缺点。

缺点
  1. 深度监听,需要递归到底,一次计算量大
  2. 无法监听新增属性/删除属性(所以需要使用 Vue.set 和 Vue.delete)
  3. 无法原生监听数组,需要特殊处理

所以在 Vue 3 中,把 Object.defineProperty 改成 Proxy

Proxy 的缺点也很明显,就是兼容性问题。所以需要根据你的项目来选择用 Vue 2 还是 Vue 3



推荐阅读