Vue: vue2 对象属性拦截原理 Object.defineProperty()Vue: vue2 对象属性拦截原理 Object.defineProperty()Vue: vue2 对象属性拦截原理 Object.defineProperty()Vue: vue2 对象属性拦截原理 Object.defineProperty()
  • 首页
  • 博客
  • 文件
  • 书签
  • 分析
  • 登录
Search
Generic filters

Vue: vue2 对象属性拦截原理 Object.defineProperty()

Published by admin at 2022年11月4日
Categories
  • Vue
Tags

1. 对象属性拦截

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
<!--
  v-model  双向绑定的实现
  1. M - V
  2. V - M
 -->
<div id="app">
  <p v-text="name"></p>
  <input type="text" v-model="name">
</div>


<!--
  实现思路:
    1. M -> V 和v-text的实现非常类似  无非是把指令名换一下 然后把操作dom的api换一下即可
    2. V -> M  做事件监听 在事件触发的回调函数中拿到当前最新的输入框值 赋值到绑定的数据上
  -->
<script>
  let data = {
    name: '柴柴老师',
    age: 29
  }
  // 把data中的属性变成响应式的
  Object.keys(data).forEach((key) => {
    defineReactive(data, key, data[key])
  })

  function defineReactive(data, key, value) {
    // 进行转换操作
    Object.defineProperty(data, key, {
      get() {
        console.log('您访问了属性', key)
        return value
      },
      set(newValue) {
        // set函数的执行 不会自动判断俩次修改的值是否相等
        // 显然如果相等 不应该执行变化的逻辑
        if (newValue === value) {
          return
        }
        console.log('您修改了属性', key)
        value = newValue
        // 这里我们把最新的值 反映到视图中  这里是关键的位置
        // 核心:操作dom  就是通过操作dom api 把最新的值设置上去
        compile()
      }
    })
  }

  // 1.通过标识查找把数据放到对应的dom上显示出来
  function compile() {
    let app = document.getElementById('app')
    // 拿到所有节点
    const childNodes = app.childNodes // 所有类型的节点包括文本节点和标签节点
    console.log(childNodes)
    // 刷选出来目标节点 -> p
    childNodes.forEach(node => {
      console.log(node.nodeType)
      if (node.nodeType === 1) {
        // 这里拿到的是标签节点
        console.log(node)
        // 刷选v-text属性 p id class (v-text)
        // 拿到所有的标签属性
        const attrs = node.attributes
        console.log(attrs)
        Array.from(attrs).forEach(attr => {
          console.log(attr)
          const nodeName = attr.nodeName
          const nodeValue = attr.nodeValue
          console.log(nodeName, nodeValue)
          // 实现v-text
          if (nodeName === 'v-text') {
            console.log('设置值', node)
            node.innerText = data[nodeValue]
          }
          // 实现v-model
          if (nodeName === 'v-model') {
            // 调用dom操作给input标签绑定数据
            node.value = data[nodeValue]
            // 监听input事件 在事件回调中 拿到最新的输入值 赋值给绑定的属性
            node.addEventListener('input', (e) => {
              let newValue = e.target.value
              // 反向赋值
              data[nodeValue] = newValue
            })
          }
        })
      }
    })
  }

  compile()
</script>


</body>

</html>

2. 发布订阅模式优化

上面代码的问题是无法做到精准更新,因此需要进行优化:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <p v-text="name"></p>
    <p v-text="name"></p>
    <span v-text="age"></span>
    <input type="text" v-model="age">
  </div>

  <script>
    // 引入发布订阅模式
    const Dep = {
      map: {},
      // 收集事件的方法
      collect(key, fn) {
        // 如果当前map中已经初始化好了 click:[]
        // 就直接往里面push  如果没有初始化首次添加  就先进行初始化
        if (!this.map[key]) {
          this.map[key] = []
        }
        this.map[key].push(fn)
      },
      // 触发事件的方法
      trigger(key) {
        this.map[key].forEach(fn => fn())
      }
    }

    let data = {
      name: '柴柴老师',
      age: 29
    }
    // 把data中的属性变成响应式的
    Object.keys(data).forEach((key) => {
      defineReactive(data, key, data[key])
    })
    function defineReactive(data, key, value) {
      // 进行转换操作
      Object.defineProperty(data, key, {
        get() {

          return value
        },
        set(newValue) {
          // set函数的执行 不会自动判断俩次修改的值是否相等
          // 显然如果相等 不应该执行变化的逻辑
          if (newValue === value) {
            return
          }
          value = newValue
          // 这里我们把最新的值 反映到视图中  这里是关键的位置
          // 核心:操作dom  就是通过操作dom api 把最新的值设置上去
          // 在这里进行精准更新 -> 通过data中的属性名找到对应的更新函数依次执行
          Dep.trigger(key)
        }
      })
    }
    // 1.通过标识查找把数据放到对应的dom上显示出来
    function compile() {
      let app = document.getElementById('app')
      // 拿到所有节点
      const childNodes = app.childNodes // 所有类型的节点包括文本节点和标签节点
      childNodes.forEach(node => {
        if (node.nodeType === 1) {
          const attrs = node.attributes
          Array.from(attrs).forEach(attr => {
            const nodeName = attr.nodeName
            const nodeValue = attr.nodeValue
            // 实现v-text
            if (nodeName === 'v-text') {
              node.innerText = data[nodeValue]
              // 收集更新函数
              Dep.collect(nodeValue, () => {
                console.log(`当前您修改了属性${nodeValue}`)
                node.innerText = data[nodeValue]
              })
            }
            // 实现v-model
            if (nodeName === 'v-model') {
              // 调用dom操作给input标签绑定数据
              node.value = data[nodeValue]
              // 收集更新函数
              Dep.collect(nodeValue,()=>{
                node.value = data[nodeValue]
              })
              // 监听input事件 在事件回调中 拿到最新的输入值 赋值给绑定的属性
              node.addEventListener('input', (e) => {
                let newValue = e.target.value
                // 反向赋值
                data[nodeValue] = newValue
              })
            }
          })
        }
      })
    }
    compile()
    console.log(Dep)
  </script>
  <!--
    存在的问题:更新太过粗暴
    不管你修改了哪个属性,其它属性也会一起跟着进行更新 哪怕你根本就没有动他
    只修改了name的时候 正常逻辑  应该只有name相关的更新操作才进行 而不是粗暴的吧所有的都更新一遍

    期望:
      哪个属性进行了实质性的修改 哪个属性对应的‘编译’部分才得到执行,这个更新优化
      我们称之为‘精准更新’

    发布订阅优化的思路
    1. 针对每一个响应式属性 收集与之相关的更新函数
    2. 响应式属性更新之后 通过属性名称找到与之绑定在一起的所有更新函数进行处罚执行

  -->

</body>

</html>

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Categories

  • 猫
  • Python
  • MySQL
  • Django
  • Html/CSS
  • JavaScript
  • Vue
  • RegExp
  • php
  • Practice
  • Virtualization
  • Linux
  • Windows
  • Android
  • NAS
  • Software
  • Hardware
  • Network
  • Router
  • Office
  • WordPress
  • SEO
  • English
  • Games
  • Recipes
  • living
  • Memorandum
  • Essays
  • 未分类

归档

©2015-2022 Alaica Blog support@alaica.com