Skip to content

响应式原理与proxy

vue2响应式核心Api:Object.defineProperty

推荐文章:https://yuchengkai.cn/docs/frontend/framework.html#数据劫持

如何深度监听对象

需要递归调用

WARNING

    1. writable&valuegetter&setter 不可同时或交叉使用,否则会报错:Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
    1. enumerable为false时,chrome输出对象时,查阅属性会呈现灰色。表示不可枚举
js
function observer(target) {
  if(typeof(target) !== 'object' || target === null) return;

  for(let key in target) {.
      watchObj(target, key, target[key])
  }
}

/*
数据描述符类(value,writable)
存取描述符类(get,set)
能与数据描述符或者存取描述符共存的共有属性(configurable,enumerable)
注意:数据描述符与存取描述符不能同时使用,否则会报错: TypeError: value appears only in data descriptors, get appears only in accessor descriptors
*/
function watchObj(target, key, value) {
  observer(value) // 循环值时触发一次监听,如果不是对象则会return【递归】
  Object.defineProperty(target, key, {
    enumerable: true, // 可枚举(是否可通过for...in 或 Object.keys()进行访问。为false时, chrome 控制台打印显灰色, JSON.stringify()时仅序列化可枚举的属性)
    configurable: true, // 可配置(是否可使用delete删除,是否可再次设置属性描述符【为false时, 可以设置其值;delete删除属性时返回false,且不会删除该属性;不可以再次设置defineProperty配置configurable:ture,否则报错】)
    // value: '', // 任意类型的值,默认undefined
    // writable: false, // 可重写. 为false时给属性赋值不会生效也不会报错
    // 主要关注下面两个属性
    get() {
      console.log(this === target) // true:this指向target
      return value // 通过闭包会一直缓存
    },
    set(newVal) {
      value = newVal
      updateView( ) // 伪代码,触发更新视图
    }
  })
}

监听数组的实现

【新建一个对象并执行Array原型,在对象本身扩展Array的7个方法,监听变动,最终触发的是Array原型方法】

js
let aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 7个:前后增删4,排序与反排序2、插入删除1

let nativeArrayPrototype = Array.prototype

let newObj = Object.create(nativeArrayPrototype); // Object.create(xxx):创建一个对象并将该对象原型指向xxx,再扩展新的方法不会影响到原型

aryMethods.forEach((method)=> {
   //将push, pop等封装好的方法定义在对象newObj的属性上。 注意:是属性而非原型属性
    newObj[method] = function () {
        console.log('监听到变动!');
        return nativeArrayPrototype[method].apply(this, arguments);   // 调用对应的原生方法并返回结果
    };
})

// 使用:
let list = [1, 2 , 3]
list.__proto__ = newObj // 别忘了将数组的隐式原型指向我们封装好的变异方法
list.push(4) // 监听到变动!
console.log(list ) // [1, 2, 3, 4]

Object.defineProperty 缺点

1、只能劫持对象的属性, 无法监听数组变化

2、添加删除属性只能通过$set、$delete。否则视图不会更新

3、深度监听需要一次性递归

vue3响应式核心Api:Proxy

proxy

优点:

  • 1、直接监听对象,而非属性
  • 2、可以监听到数组的变化
  • 3、Proxy返回新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。

缺点:兼容性问题,而且无法polyfill【IE全军覆没】