关于vue2响应式缺陷的问题

 

vue2响应式缺陷

响应式 : 数据改变 ==> 视图跟着改变

vue2响应式缺陷

1.对象新增的属性没有响应式

对象,新增属性b,修改b的值,值改变但视图并未更新

解决方案 : 使用vue提供的 api $set(对象,属性名,值) 效果如属性c

2.数组的部分操作没有响应式

数组中有7种操作有响应式

  • array.pop()
  • array.push()
  • array.shift()
  • array.unshift()
  • array.sort()
  • arry.reverse()
  • array.splice()

以上7中API会修改原数组(vue2的内部重写了这7个API)

其他的操作都不会有响应式

示例:

1.修改数组的第一个元素的值

// 直接通过下标来修改,没有响应式
fn1() {
  this.arr[0] = 100
}

通过下标直接赋值,没有响应式

2.修改数组的长度为0

//修改数组的length
fn2 () {
  this.arr.length = 0
}

修改数组的length,没有响应式

如何修改数组的值有响应式

fn1 () {
  // 方法1: 先删除,再添加
  this.arr.splice(0,1,100)
},
fn2 () {
  // 方法2: $set
  this.$set(this.arr, "0", 100)
}

 

vue2与vue3的响应式原理

给大家分享一下Vue2和Vue3的响应式原理;说到响应式无非就是监听属性的获取,修改及删除...,了解逻辑之后再去实现底层代码岂不是快了许多=_=

vue2响应式

原理:利用defineReactive方法,通过defineProperty对属性进行劫持,数组则是通过重写其方法来进行劫持,每个属性值都拥有自己的dep属性,用来存取所依赖的watch,当数据发生改变时,触发相应的watch去更新数据

代码实现:

const { arrayMethods } = require('./array')

class Observer {
  constructor(value) {
      Object.defineProperty(value, '__ob__', {
          value: this,
          enumerable: false,
          writable: true,
          configurable: true
      })
      if(Array.isArray(value)) {
          value.__proto__ = arrayMethods
          this.observeArray(value)
      } else {
          this.walk(value)
      }
  }

  walk(data) {
      let keys = Object.keys(data)
      for(let i = 0; i < keys.length; i++) {
          const key = keys[i]
          const value = data[key]
          defineReactive(data, key, value)
      }
  }

  observeArray(items) {
      for(let i = 0; i < items.length; i++) {
          observe(items[i])
      }
  }
}

function defineReactive(data, key, value) {
  const childOb = observe(value)

  const dep = new Dep()

  Object.defineProperty(data, key, {
      get() {
          console.log('获取值')
          if (Dep.target) {
              dep.depend()

              if (childOb) {
                  childOb.dep.depend()

                  if (Array.isArray(value)) {
                      dependArray(value)
                  }
              }
          }
          return value
      },
      set(newVal) {
          if (newVal === value) return
          observe(newVal)
          value = newVal
          dep.notify()
      }
  })
}

function observe(value) {
  if (Object.prototype.toString.call(value) === '[object Object]' || Array.isArray(value)) {
      return new Observer(value)
  }
}

function dependArray(value) {
  for(let e, i = 0, l = value.length; i < l; i++) {
      e = value[i]

      e && e.__ob__ && e.__ob__.dep.depend()

      if (Array.isArray(e)) {
          dependArray(e)
      }
  }
}

// array.js
const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'reverse',
  'sort'
]

methodsToPatch.forEach(method => {
  arrayMethods[method] = function (...args) {
      const result = arrayProto[method].apply(this, args)

      const ob = this.__ob__

      var inserted

      switch (method) {
          case 'push':
          case 'unshift':
              inserted = args
              break;
          case 'splice':
              inserted = args.slice(2)
          default:
              break;
      }

      if (inserted) ob.observeArray(inserted)

      ob.dep.notify()

      return result
  }
})

但是呢,Vue2的响应式还存在一些缺陷:1.对象新增属性,删除属性界面不会更新 2.通过数组下标修改数组内容界面不会更新

原因:1.Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

2.通过数组下标修改数组不会触发响应,因为尤雨溪用了重写数组的方法来实现数据的响应绑定,当vue遇到push pop shift unshift splice sort reverse 的时候数组才会改变

解决方案:1.对象:对象新增属性无法更新视图,通过Vue.$set(obj,key,value),组件中通过this.$set(obj,key,value)

2.数组:通过数组下标修改数组不会触发视图更新,可以通过Vue.$set(obj,key,value),也可以通过push pop shift unshift splice sort reverse方法来实现响应

Vue3的响应式弥补了Vue2响应式的缺陷,并且还带来了许多优化,下面让我们来了解一下Vue3响应式的基本雏形

vue3响应式雏形

原理:利用了Proxy和Reflect来代替Vue2的Object.defineProperty()方法来重写响应式

这只是基本的代码:

        let person = {
          name: '张三',
          age: 18
      }
      const p = new Proxy(person, {
          get(target, propName) {
              console.log(`我的${propName}值被获取了`);
              return Reflect.get(target, propName)
          },
          set(target, propName, value) {
              console.log(`我的${propName}值被修改了`);
              Reflect.set(target, propName, value)
          },
          deleteProperty(target, propName) {
              console.log(`我的${propName}值被删除了`);
              Reflect.deleteProperty(target, propName)
          }
      })

target指的是整个person,propName指的是person中的某个属性,value指的是新值。

运行结果:

vue3的响应式相较于vue2的优势

用 Proxy 和 Reflect 来代替 vue2 中的 Object.definepeoperty()方法来重写响应式

vue3 中可以监听动态新增的属性

vue3 中可以监听删除的属性

vue3 中可以监听数组的索引和 length 属性

代码的执行效果更快

Proxy 可以直接监听对象而非属性

Proxy 可以直接监听数组的变化

Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的

Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改

Proxy 不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程宝库

 vuepush()和splice()的使用解析push()使用push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。注意:1. 新元素将添加在数组的末尾。2.此方法 ...