乐趣区

关于vue.js:使用Proxy实现简易的vue双向数据绑定

在实现 vue 双向数据绑定之前,先理解 Proxy 相干的概念和用法

proxy 概念

Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

一些术语

  • handle

蕴含捕获器(trap)的占位符对象,可译为处理器对象

  • traps

提供属性拜访的办法。这相似于操作系统中捕捉器的概念。

  • target

被 Proxy 代理虚拟化的对象。

语法

const p = new Proxy(target, handler)

  • target

要应用 Proxy 包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理)。

  • handle

一个通常以函数作为属性的对象,各属性中的函数别离定义了在执行各种操作时代理 p 的行为。

应用 proxy 实现数据劫持

let data = {
    name: YoLinDeng,
    height: '176cm'
}

const p = new Proxy(data, {get(target, prop) {return Reflect.get(...arguments)
    },
    set(target, prop, newValue) {return Reflect.set(...arguments)
    }
})

对于 vue 中数据响应式的原理

对数据进行侦测

  • 在 vue2.X 中,实现一个 observe 类,对于对象数据,通过 Object.defineProperty 来劫持对象的属性,实现 gettersetter办法,这样就能够在 getter 的时候晓得谁(订阅者)读取了数据,即谁依赖了以后的数据,将它通过 Dep 类(订阅器)收集对立治理,在 setter 的时候调用 Dep 类中的notify 办法告诉所以相干的订阅者进行更新视图。如果对象的属性也是一个对象的话,则须要递归调用 observe 进行解决。
  • 对于数组则须要另外解决,通过实现一个拦截器类,并将它挂载到数组数据的原型上,当调用 push/pop/shift/unshift/splice/sort/reverse 批改数组数据时候,相当于调用的是拦截器中从新定义的办法,这样在拦截器中就能够侦测到数据扭转了,并告诉订阅者更新视图。
  • vue3 中应用 Proxy 代替了 Object.defineProperty,长处在于能够间接监听对象而非属性、能够间接监听数组的变动、多达 13 种拦挡办法。毛病是兼容性还不够好。Proxy 作为新规范将受到浏览器厂商重点继续的性能优化。

对模板字符串进行编译

  • 实现 Compile 解析器类,将 template 中的模板字符串通过正则等形式进行解决生成对应的 ast(形象语法树),通过调用定义的不同钩子函数进行解决,包含开始标签(start)并判断是否自闭和以及解析属性、完结标签(end)、文本(chars)、正文(comment
  • 将通过 html 解析与文本解析的 ast 进行优化解决,在动态节点上打标记,为前面 dom-diff 算法中性能优化应用,即在比照前后 vnode 的时候会跳过动态节点不作比照。
  • 最初依据解决好的 ast 生产 render 函数,在组件挂载的时候调用 render 函数就能够失去虚构 dom。

虚构 dom

  • vnode 的类型包含正文节点、文本节点、元素节点、组件节点、函数式组件节点、克隆节点,VNode能够形容的多种节点类型,它们实质上都是 VNode 类的实例,只是在实例化的时候传入的属性参数不同而已。
  • 通过将模板字符串编译生成虚构 dom 并缓存起来,当数据发生变化时,通过比照变动前后虚构 dom,以变动后的虚构 dom 为基准,更新旧的虚构 dom,使它和新的一样。把 dom-diff 过程叫做 patch 的过程,其次要做了三件事,别离是创立 / 删除 / 更新节点。
  • 对于子节点的更新策略,vue 中为了防止双重循环数据量大时候造成工夫复杂度高带来的性能问题,而抉择先从子节点数组中 4 个非凡地位进行比照,别离是:新前与旧前,新后与旧后,新后与旧前,新前与旧后。如果四种状况都没有找到雷同的节点,则再通过循环形式查找。

实现繁难的 vue 双向数据绑定

vue 的双向数据绑定次要是指,数据变动更新视图变动,视图变动更新数据。
实现代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width= , initial-scale=1.0">
  <title>Document</title>
  <script src="myVue.js"></script>
</head>
<body>
  <div id="app">
    {{name}}
    <div>{{message}}</div>
    <input type="text" v-model="test">
    <span>{{test}}</span>
  </div>
  <script>
    let vm = new vue({
      el: '#app',
      data: {
        name: 'YoLinDeng',
        message: '打篮球',
        test: '双向绑定数据'
      }
    })
    // console.log(vm._data)
  </script>
</body>
</html>
class vue extends EventTarget {constructor(option) {super()
    this.option = option
    this._data = this.option.data
    this.el = document.querySelector(this.option.el)
    this.compileNode(this.el)
    this.observe(this._data)
  }
  // 实现监听器办法
  observe(data) {
    const context = this
    // 应用 proxy 代理,劫持数据
    this._data = new Proxy(data, {set(target, prop, newValue) {
        // 自定义事件
        let event = new CustomEvent(prop, {detail: newValue})
        // 公布自定义事件
        context.dispatchEvent(event) 
        return Reflect.set(...arguments)
      }
    })
  }
  // 实现解析器办法,解析模板
  compileNode(el) {
    let child = el.childNodes
    let childArr = [...child]
    childArr.forEach(node => {if (node.nodeType === 3) {
        let text = node.textContent
        let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g
        if (reg.test(text)) {
          let $1 = RegExp.$1
          this._data[$1] && (node.textContent = text.replace(reg, this._data[$1]))
          // 监听数据更改事件
          this.addEventListener($1, e => {node.textContent = text.replace(reg, e.detail)
          })
        }
      } else if (node.nodeType === 1) { // 如果是元素节点
        let attr = node.attributes
        // 判断属性中是否含有 v -model
        if (attr.hasOwnProperty('v-model')) {let keyName = attr['v-model'].nodeValue
          node.value = this._data[keyName]
          node.addEventListener('input', e => {this._data[keyName] = node.value
          })
        }
        // 递归调用解析器办法
        this.compileNode(node)
      }
    })
  }
}
退出移动版