vue双向数据绑定原理

vue响应式原理

数据驱动

数据响应式:据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了频繁的DOM操作,提高开发效率,这与Jquery不一样,Jquery是频繁的操作Dom

双向绑定:

数据改变,视图改变,视图改变,数据也随之改变( 双向绑定中是包含了数据响应式的内容)

我们可以使用`v-model` 在表单元素上创建双向数据绑定

数据驱动是Vue最独特的特性之一

 开发过程中仅仅需要关注数据本身,不需要关心数据是如何渲染到视图中的。主流的`MVVM`框架都已经实现了数据响应式与双向绑定,所以可以将数据绑定到`DOM`上。

响应式核心原理

vue2原理

官方文档:https://cn.vuejs.org/v2/guide/reactivity.html

Vue2.x中响应式的实现是通过Object.defineProperty来完成的,注意该属性无法降级(shim)处理,所以Vue不支持IE8以及更低版本的浏览器的原因。

数据变化反应到导视图

声明式改变视图:

将data中name属性的值作为文本渲染到标记了v-text的p标签内部,在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>Document</title>
</head>

<body>
  <div id="app">
    <p v-text='name'></p>
    <p v-text='age'></p>
  </div>
  <script>

    let data = { name: '李白', age: 12 }

    Object.keys(data).forEach(key => {
      define(data, key, data[key])

    });
    function define(data, key, value) {

      Object.defineProperty(data, key, {
        get() {
          return value
        },
        set(newValue) {
          value = newValue
          compile()
        }
      })
    }

    function compile() {
      let app = document.querySelector('#app')
      let nodes = app.childNodes
      // console.log(nodes);
      nodes.forEach(item => {
        if (item.nodeType === 1) {
          let arr = item.attributes
          console.log(arr);
          Array.from(arr).forEach(node => {
            let name = node.nodeName
            let value = node.nodeValue
            if (name === 'v-text') {
              item.innerHTML = data[value]
            }
          })
        }
      })
    }
    compile()
  </script>
</body>

</html>

命令式改变视图:

通过原始的操作dom的方式让每一次的name的最新值都能显示到p元素内部

<!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>Document</title>
</head>

<body>
  <div id="app">
    <p></p>
  </div>
  <script>
    let p = document.querySelector("#app p")
    let data = { name: '李白', age: 12 }
    // console.log(Object.keys(data));

    Object.keys(data).forEach(key => {
      console.log(data);
      console.log(key);
      define(data, key, data[key])

    });
    function define(data, key, value) {
      // console.log(data, key, value);
      Object.defineProperty(data, key, {
        get() {
          return value
        },
        set(newValue) {
          value = newValue
          p.innerHTML = value
        }
      })
    }
    p.innerHTML = data.name
  </script>
</body>

</html>

视图变化反应到数据

<!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>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="name">
  </div>
  <script>
    let data = {
      name: '李白',
      age: 18
    }
    Object.keys(data).forEach(key => {
      defineReactiveProperty(data, key, data[key])
    })
    function defineReactiveProperty(data, key, value) {
      console.log();
      Object.defineProperty(data, key, {
        get() {
          return value
        },
        set(newValue) {
          if (newValue === value) {
            return
          }
          value = newValue
          // 使用声明式渲染,就要便利所有节点,看哪个节点有我们的自定义绑定属性
          compile()
        }
      })
    }
    function compile() {
      let app = document.querySelector('#app')
      // 拿到 app 下所有子元素
      const nodes = app.childNodes
      nodes.forEach(node => {
        if (node.nodeType === 1) {
          const attrs = node.attributes
          Array.from(attrs).forEach(attr => {
            const nodeName = attr.nodeName
            const nodeValue = attr.nodeValue
            if (nodeName === 'v-model') {
              // 当data中数据发生变化时,改变文本框的value属性,以更新文本框的值
              node.value = data[nodeValue]
              // 当文本框的内容发生变化时,更新 data 中属性的值
              node.addEventListener('input', e => {
                data[nodeValue] = e.target.value
              })
            }
          })
        }
      })
    }
    compile()
  </script>

</html>

Vue3响应式原理

Vue3的响应式原理是通过Proxy来完成的。

Proxy直接监听对象,而非属性,所以将多个属性转换成getter/setter的时候,不需要使用循环。

ProxyES6课程中新增的,IE不支持

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proxy</title>
  </head>
  <body>
    <div id="app">hello</div>
    <script>
      //模拟Vue中的data选项
      let data = {
        msg: "hello",
        count: 0,
      };
      //模拟Vue实例
      //为data创建一个代理对象vm,这样就可以通过vm.msg来获取data中的msg属性的值,而这时候会执行get方法
      let vm = new Proxy(data, {
        // 当访问vm的成员时会执行
        //target表示代理的对象(这里为data对象),key表示所代理的对象中的属性
        get(target, key) {
          console.log("get key:", key, target[key]);
          return target[key];
        },
        //当设置vm的成员时会执行
        set(target, key, newValue) {
          console.log("set key:", key, newValue);
          if (target[key] === newValue) {
            return;
          }
          target[key] = newValue;
          document.querySelector("#app").textContent = target[key];
        },
      });
      //测试
      vm.msg = "aaaa";
      console.log(vm.msg);
    </script>
  </body>
</html>

双向数据绑定

双向数据绑定包含两部分内容,数据变化更新视图,视图变化更新数据。

实现双向数据绑定

基本的思路就是,我们可以给文本框(第一个文本框)添加一个input事件,在输入完数据后触发该事件,同时将用户在文本框中输入的数据赋值给data中的属性(视图变化,更新数据,而当数据变化后,会执行行observer.js中的set方法,更新视图,也就是触发了响应式的机制)。

//处理v-model
  modelUpdater(node, value, key) {
    //v-model是文本框的属性,给文本框赋值需要通过value属性
    node.value = value;
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    });
    //实现双向绑定
    node.addEventListener("input", () => {
      this.vm[key] = node.value;
    });
  }

为当前的文本框节点添加了input事件,当在文本框中输入内容的时候会触发该事件,同时,将用户在文本框节点中输入的值重新赋值给了data中对应的属性。

在文本框中输入值,对应的差值表达式和v-text中的内容都会发生改变。同时在控制台中输出vm.msg的值会发现数据也发生了变化。

当给data中的属性赋值后,会执行observer.js中的set方法,更新视图,也就是触发了响应式的机制。

上一篇:js实现对sessionStorage中的值进行监听


下一篇:初步认识对象中属性的类型(对set set getter setter的认识)