vue2+版本学习之使用defineProperty方式简单实现数据响应式

222 阅读1分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

此系列文章只是对自己学习做个总结与复盘,后面会将学到的vue2相关复盘文章链接更新到此处。感谢村长

  • 此处没有涉及到vue,只是学习前对defineProperty的一个了解 —— 修改数据后程序怎么知道,在哪里更新视图。

  • 思考:vue2中怎么对data中的数据进行响应式处理,怎么对模板进行编译,将数据初始化到数据对应的视图中,此处是全量更新,怎么在数据变化后精准更新相应的视图?

  • 后续文章1:vue2学习之记录一个简版vue的实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue2.6使用defineProperty方式简单实现数据响应式-即数据驱动视图变化</title>
</head>
<body>
  <div id="app">
    <div id="time"></div>
    <div id="arr"></div>
    <div>
      <label>数组操作</label>
      <div id="arr1"></div>
      <button onclick="myPush()">push</button>
      <button onclick="myPop()">pop</button>
      <button onclick="myUnshift()">unshift</button>
      <button onclick="myShift()">shift</button>
      <button onclick="myReverse()">reverse</button>
      <button onclick="mySplice()">splice</button>
      <button onclick="mySort()">sort</button>
    </div>
  </div>
  <script>
    // 数组响应式
      // 1、找到数组原型
      // 2、覆盖数组的7大方法,使其可以通知更新
      // 3、将新原型设置到数组的实例原型上
    const oldProto = Array.prototype;
    // 在备份后修改备份
    const newProto = Object.create(oldProto);
    ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
      newProto[method] = function () {
        // 进行原始操作
        oldProto[method].apply(this, arguments);
        // 覆盖操作,通知更新
        console.log('数组执行' + method + '操作');
      }
    })
    let obj = {
      arr1: [8,1,0,-1,6,41]
    }
    // 对象响应式
    function defineReactive(obj, key, val) {
      // 递归对树形结构进行处理
      observe(val)
      // 对数据进行拦截
      Object.defineProperty(obj, key, {
        get () {
          // console.log(`get: ${key} : ${val}`)
          return val
        },
        set (newVal) {
          if (val !== newVal) {
            // 在此处要对新增的属性是对象的情况做下响应式处理,eg: obj.data = { a: 1 }
            observe(newVal)
            // console.log('set: ' + key)
            // 此处不能使用obj[key] = newVal
            // 以上方式其实是在set方法中无限重复执行obj.key = 'xxx',即会重复触发set方法
            val = newVal
            update() // 自己先写个更新函数,在vue中其实是编译器对模板进行处理渲染的一个过程
          }
        }
      })
    }
    function set (obj, key, val) {
      // 对新增加的属性做响应式处理
      defineReactive(obj, key, val)
    }
    // 对某个对象进行遍历,对其中所有的属性做响应式处理,如果有嵌套对象,需要递归
    function observe (obj) {
      if (typeof obj !== 'object' || obj === null) {
        // 值不是对象的就出去走下一步-对数据进行拦截的步骤吧
        // 注意typeof null === 'object',所以要区分下null
        return obj
      }
      if (Array.isArray(obj)) {
        // 覆盖原型,替换7个方法
        obj.__proto__ = newProto
        // 对数组中可能存在的对象或数组执行响应化
        // const keys = Object.keys(obj)
        for (let i = 0; i < obj.length; i++) {
          observe(obj[i])
        }
      } else {
        Object.keys(obj).forEach(key => {
          defineReactive(obj, key, obj[key])
        })
      }
    }
    function update () {
      document.getElementById('time').innerText = obj.testSet.time
      document.getElementById('arr').innerHTML = obj.arr
      document.getElementById('arr1').innerHTML = obj.arr1
    }
    // 手动新增属性,值为对象
    set(obj, 'testSet', { time: new Date().toLocaleTimeString() })
    set(obj, 'arr', [])
    var i = 0
    setInterval(() => {
      obj.testSet.time = new Date().toLocaleTimeString()
      if (i < 10) {
        obj.arr.push(i++)
      }
    }, 1000)
    // 对整个obj不管存在的还是新来的,都做响应式处理
    observe(obj)

    function myPush () {
      obj.arr1.push(666)
    }
    function myPop () {
      if (obj.arr1.length) {
        obj.arr1.pop()
      } else {
        console.log('没有啦~');
      }
    }
    function myUnshift () {
      obj.arr1.unshift(666)
    }
    function myShift () {
      if (obj.arr1.length) {
        obj.arr1.shift()
      } else {
        console.log('没有啦~');
      }
    }
    function myReverse () {
      obj.arr1.reverse()
    }
    function mySplice () {
      obj.arr1.splice(1, 0, 13)
    }
    function mySort () {
      obj.arr1.sort((a, b) => { return a - b })
    }
  </script>
</body>
</html>