前言

最近看了一下vue2,对vue2的响应式有了更深刻的印象,对vue的实现响应式的原理有了一些思考,所以学习并简易实现一下vue2的更新原理。话不多说,进入正题。

定义代码

我们先看一下定义好的HTMLCSSJS

HTML代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>HTML + CSS</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="container">
      <div>
        <span>姓:</span>
        <span class="surname"></span>
      </div>
      <div>
        <span>名:</span>
        <span class="name"></span>
      </div>
      <div>
        <span>电话:</span>
        <span class="phone"></span>
      </div>
    </div>
    <script src="./myvue.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

CSS定义:

.container {
  margin: 0 auto;
  display: grid;
  width: 300px;
  height: 200px;
  padding: 0 20px;
  background-color: #1791f8;
  border-radius: 10px;
}

.container > div {
  margin: 20px 0;
}

index.js 代码:

let user = {
  name: "刘备",
  phone: 12312312312,
};

myvue(user);

function setSurname(params) {
  document.querySelector(".surname").innerText = user.name[0];
}

function setName(params) {
  document.querySelector(".name").innerText = user.name.substring(1);
}

function setPhone(params) {
  document.querySelector(".phone").innerText = user.phone;
}

setName();
setSurname();
setPhone();

js的代码也是比较简单的,定义了三个函数,分别是设置姓、名、电话的函数。然后进行初始化调用,就是vue的初始化展示。

初始状态下,我们看到的效果是这样的,一个盒子进行居中,里边展示相关信息。

实现响应式效果

我们在myvue.js里定义这样一个函数,对传入的对象进行属性遍历,然后拿到初始值,通过Object.defineProperty进行对象包装,定义对象每个属性的getter和setter,每次获取值调用getter时,都会进行依赖获取,并对每次重新赋值进行派发更新操作。

function myvue(obj) {
  for (const key in obj) {
    let initvalue = obj[key];
    // 待执行响应的函数 改变UI
    let funcArr = [];
    Object.defineProperty(obj, key, {
      get: function get() {
        // 收集依赖
        if (window.publicFunc && !funcArr.includes(window.publicFunc)) {
          funcArr.push(window.publicFunc);
        }
        return initvalue;
      },
      set: function set(val) {
        initvalue = val;
        // 派发更新
        if (funcArr.length > 0) {
          funcArr.map((fn) => fn());
        }
      },
    });
  }
}

然后 调整一下index.js 的代码,对myvue引入并注册,然后在window上设置变量保存函数,并执行相应的代码,最后设置为null

let user = {
  name: "刘备",
  phone: 12312312312,
};

myvue(user);

function setSurname(params) {
  document.querySelector(".surname").innerText = user.name[0];
}

function setName(params) {
  document.querySelector(".name").innerText = user.name.substring(1);
}

function setPhone(params) {
  document.querySelector(".phone").innerText = user.phone;
}

setPhone()


user.name = "张翼德"
window.publicFunc = setName;
setName();
window.publicFunc = setSurname;
setSurname();
window.publicFunc = null;

添加保存,执行,我们可以看到数据有了变化,UI并且更新了

优化

通过上面调整的代码,我们可以进一步提炼,初始化时自动执行函数调用,设置为null。

代码如下:

myvue.js代码:

function myvue(obj) {
  for (const key in obj) {
    let initvalue = obj[key];
    // 待执行响应的函数 改变UI
    let funcArr = [];
    Object.defineProperty(obj, key, {
      get: function get() {
        // 收集依赖
        if (window.publicFunc && !funcArr.includes(window.publicFunc)) {
          funcArr.push(window.publicFunc);
        }
        return initvalue;
      },
      set: function set(val) {
        initvalue = val;
        // 派发更新
        if (funcArr.length > 0) {
          funcArr.map((fn) => fn());
        }
      },
    });
  }
}

// 定义一个autoRun函数用来处理这些固定的逻辑
function autoRun(fn) {
  window.publicFunc = fn;
  fn();
  window.publicFunc = null;
}

index.js 代码函数调用调整:

let user = {
  name: "刘备",
  phone: 12312312312,
};

myvue(user);

function setSurname(params) {
  document.querySelector(".surname").innerText = user.name[0];
}

function setName(params) {
  document.querySelector(".name").innerText = user.name.substring(1);
}

function setPhone(params) {
  document.querySelector(".phone").innerText = user.phone;
}

// 初始化调用属性getter和setter 响应函数。
autoRun(setSurname);
autoRun(setName);
autoRun(setPhone);

好了,这样我们就简单完成了一个简易的vue2响应代码。