使用 Proxy 实现简易的 Vue 双向数据绑定

发布于 2023-12-14 18:42:44 字数 4708 浏览 24 评论 0

proxy 的基本使用

可以直接看这个链接: #8

使用 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>
    {{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)
      }
    })
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
710 人气
更多

推荐作者

无悔心

文章 0 评论 0

ぽ尐不点ル

文章 0 评论 0

mb_rgrUPueh

文章 0 评论 0

妄断弥空

文章 0 评论 0

一刻暧昧

文章 0 评论 0

无敌元气妹

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文