本文简介

点赞 + 关注 + 珍藏 = 学会了


首先,解答一下题目: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.nameconsole.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)// 测试1data.name = {  lastName: '鲨鱼辣椒'}// 测试2data.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 => {  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



举荐浏览

《『Three.js』腾飞!》

《Vue3 过10种组件通信形式》

《Fabric.js 从入门到旁若无人》

《Vite 搭建 Vue2 我的项目(Vue2 + vue-router + vuex)》

《console.log也能插图!》

点赞 + 关注 + 珍藏 = 学会了