Vue 中 Object.defineProperty 与 Proxy 实现双向绑定的原理

实现双向绑定的三要素

  1. 响应式: 如何监听数据变化
  2. 模板引擎: 如何解析HTML代码
  3. 渲染: 如何将监听到的数据变化和解析后的HTML进行渲染

一、Object.defineProperty

通过 Object.defineProperty监听对象属性的改变

实例定义的属性,vue 内部通过 Object.defineProperty重新定义,生成 Observer

// 实例定义的属性
const obj = {
  message: '哈哈哈',
  name: 'jack'
}

// vue 内部流程
Object.keys(obj).forEach(key => {
  let value = obj[key]
  
  Object.defineProperty(obj, key, {
    set(newValue) {
      // 监听 key 的改变
      value = newValue
    },
    get() {
      // 获取 key 对应的值
      return value
    }
  })
})

当数据发生改变时,vue是如何知道?

  1. 利用Object.defineProperty生成的Observer针对对象的属性进行"劫持",一个属性创建一个 Dep 对象,在属性发生变化后通知订阅者

  2. 解析器(Compile)解析模板中的 Directive(指令),获取到哪里用到了属性(订阅者),比如{{name}} {{message}},创建一个观察者watcher添加到对应的Dep 对象中,同时初始化view,在界面上显示

  3. Watcher属于ObserverCompile桥梁,将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化

Object.defineProperty的缺陷

  1. 无法监听数组变化
    只有以下七种方法可以检测到数组变化

    push()
    pop()
    shift()
    unshift()
    splice()
    sort()
    reverse()
    
  2. 只能劫持对象的属性,因此需要对对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择

二、Proxy

const model = {}
const p = new Proxy(model, {
  get(model, prop) { // 读
    return Reflect.get(model, prop)
  },
  set(model, prop, value) { // 增、改
    Reflect.set(model, prop, value)
    document.querySelector('[v-bind]').innerHTML = value
  },
  deleteProperty(model, prop) {
    return Reflect.deleteProperty(model, prop)
  }
})

// 当 view 中的数据发生改变时, model 中的数据自动改变
document.querySelector('[v-model]').oninput = function (e) {
  p.msg = e.target.value
}    

Proxy 的优点

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

你可能感兴趣的:(前端常见面试题,#,VUE篇,vue,js)