【大师课】Vue从入门到手撕

129 阅读2分钟

数据响应式: 当数据发生改变的时候, 会自动重新运行依赖该数据的函数

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div class="card">
      <div class="wrap">
        <p id="firstname">姓:</p>
        <p id="lastname">名:</p>
        <p id="age">年龄:</p>
      </div>

      <div class="ipt">
        <input type="text" onchange="user.name = this.value" placeholder="请输入姓名" />
        <input type="date" onchange="user.birth = this.value" placeholder="选择出生日期" />
      </div>
    </div>

    <script src="./euv.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

index.css

* {
  margin: 0;
  padding: 0;
}

input {
  outline: none;
  padding: 5px;
  border-radius: 5px;
  border: none;
}

.card {
  margin: 30px auto;
  padding: 20px;
  width: 350px;
  background-color: #1989fa;
  color: #fff;
  border-radius: 10px;
}

.card p {
  line-height: 2;
}

.card .ipt {
  margin-top: 10px;
}

index.js

let user = {
  name: '张三',
  birth: '2000-01-01',
}

// 观察器
observe(user)

function showFirstName() {
  const firstDom = document.querySelector('#firstname')
  firstDom.textContent = '姓: ' + user.name[0]
}

function showLastName() {
  const lastDom = document.querySelector('#lastname')
  lastDom.textContent = '名: ' + user.name.slice(1)
}

function showAge() {
  const ageDom = document.querySelector('#age')
  const now = new Date().getFullYear()
  const diff = now - new Date(user.birth).getFullYear()

  ageDom.textContent = '年龄: ' + diff
}

// 该函数会将依赖属性的函数 传递给观察器的依赖收集
autorun(showFirstName)
autorun(showLastName)
autorun(showAge)

// user.name = '李四'
// 问题: 当数据发生改变时界面不会自动更新?
// - 需要重新调用所依赖的函数
// showFirstName()
// showLastName()

// user.birth = '2020-01-01'
// 问题: 当数据发生改变时界面不会自动更新?
// - 需要重新调用所依赖的函数
// showAge()

// Object.defineProperty() 对数据进行劫持
// Vue 数据发生改变时自动调用依赖该属性的函数
// let internalName = user.name
// Object.defineProperty(user, 'name', {
//   get() {
//     return internalName
//   },
//   set(val) {
//     internalName = val
//     showFirstName()
//     showLastName()
//   },
// })

// user.name = '王五'

euv.js

/**
 * 观察某个对象的所有属性
 * @param {Object} obj
 */
function observe(obj) {
  for (const key in obj) {
    // 初始值
    let internalName = obj[key]

    // 将依赖属性的函数存放到一个数组中
    // 防止重复收集 使用 Set
    let funcs = new Set()

    Object.defineProperty(obj, key, {
      get() {
        // 依赖收集 记录: 哪些函数用到了该属性
        if (window.__fun) {
          funcs.add(window.__fun)
        }

        return internalName
      },
      set(val) {
        internalName = val

        // 派发更新 运行: 执行用到该属性的函数
        Array.from(funcs).forEach((fun) => fun())
      },
    })
  }
}

/**
 * 得到当前依赖属性的函数
 * @param {Function} fun
 */
function autorun(fun) {
  window.__fun = fun
  fun()
  window.__fun = null
}

// 数据响应式: 当数据发生变化的时候, 自动运行一些相关函数