Vue2源码解读(4) - 响应式原理及简单实现

2021-10-24

Vue2源码解读 - 响应式原理及简单实现

直接进入主题了,想必大家都知道实现vue响应式核心方法就是 Object.defineProperty,那就从它开始说

Object.defineProperty

缺点:

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增、删除属性(需要vue.set 和 vue.delete)
  • 无法原生监听数组,需要特殊处理

实现响应式

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

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象原型指向 Array.prototype,在扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
  arrProto[methodName] = function () {
    updateView()
    oldArrayProperty[methodName].call(this, ...arguments)
  } 
});

// 监听data传入的属性
function defineReactive(target, key, value) {
  // 深度监听 多层对象嵌套
  observer(value)
  // 核心api
  Object.defineProperty(target, key, {
    get() {
      return value
    },

    set(newVal) {
      // 设置新值也要监听 比如{age:27}
      observer(newVal)
      if (newVal !== value ) {
        value = newVal
        updateView()
      }
    }
  })
}
// 监听对象属性
function observer(target) {
  if (typeof target !== 'object' || target === null) {
    // 不是对象或数组
    return target
  }
  // 监听数组 把原数组的隐式原型赋值给我们定义好的数组对象
  if (Array.isArray(target)) {
    target.__proto__ = arrProto
  }
  // 重新定义各个属性,加getter、setter属性
  for(let key in target) {
    defineReactive(target, key, target[key])
  }
}

const data = {
  name: 'zk',
  age: 26,
  info: {
    address: 'city'  // 需深度监听
  },
  nums: [1, 2, 3]
}
observer(data)
// data.info.address = 'beijing' // 需要深度监听
// data.info = {address:'beijing'} // 需要深度监听
// data.x = 666                  // 新增属性,监听不到  需要vue.set方法 
// delete data.name              // 删除属性,监听不到  需要vue.delete方法 
data.nums.push(21)

vue2简单的数据双向绑定实现

<div>内容:<span id="content"></span></div>
<input id="iptName" />
const iptName = document.getElementById('iptName')
const content = document.getElementById('content')
let obj = {
  name: ''
}
let newObj = JSON.parse(JSON.stringify(obj))
Object.defineProperty(obj, 'name', {
  get() {
    return newObj.name
  },

  set(val) {
    if (val === newObj.name) return
    newObj.name = val
    observer()
  }
})
function observer () {
  iptName.innerText = obj.name
  content.innerText = obj.name
}
iptName.oninput = function () {
  obj.name = this.value
}