Vue双向绑定实现原理(一) 数据劫持

9 篇文章 0 订阅

 

1.1 数据劫持

1.1.1 如何监控一个数据

vue可以直接通过v-model这个指令来实现双向绑定,这是react和小程序都没有,小程序是单向绑定,只能将data中的对象和基本数据类型展示在视图上,却没有办法通过视图来控制data中的数据,需要通过this.setData({})给出一个对象,重新设置数据,达到视图更新。

双向绑定展示图

要达到如图1-1的效果,就要对数据进行监控,只有监控了数据的变化,在数据变化之后,通知视图去自主更新,这就是双向绑定的思路。这个思路很明显涉及到“监控” “更新”两个关键词,就可以联想到观察者模式。

观察一个数据,一旦数据变化,就通知视图执行更新操作。

思路一下子就明了,数据变化还好说,就是拿出一个变量存储旧值,一旦获取到新值,新值与旧值不同时,数据就发生了变化。可问题在于,不可能随时随地对数据进行监控,每分每秒都在取得数据的值去与旧值做对比。

只有当这个数据在被使用时,我们才监控他,拿旧值与新值做对比。

这个过程叫做让数据变为可观察,是通过Object.defineProperty() 来实现。

1.1.2 如何使用Object的静态方法定义属性

Object.defineProperty(obj, prop, descriptor)
  • obj 要在其上定义属性的对象。

  • prop 要定义或修改的属性的名称。

  • descriptor 将被定义或修改的属性描述符。

被这样定义的属性,所有的数据描述符默认为false,也就是不可删除,不可写,不可枚举

属性描述符

MDN文档上有提到

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
let obj = {
            name:1
        }
        Object.defineProperty(obj,'school',{
            configurable:true,//表示configurable可以被删除
            writable:true,//为true之后,便可以修改
            // enumerable:true,//修改之后才可以被枚举,在遍历时被访问到
            value:'zfpx'
        })
        // delete obj.school;
        obj.school = "修改值"
        console.log(obj)
        for(var key in obj){
            console.log(key)
        }

只有开启数据描述符为true之后,属性才可被删除,被写入,被枚举打印

getter-setter

这就是数据可监控的关键,使用Object.defineProperty(obj, prop, descriptor)定义的属性,一旦属性被使用,就会被读取,就会调用get函数,一旦属性被写入,就会调用set函数,即可以知道数据一旦发生写入,变化,就可以在set函数中通知视图更新。

由于,

存储描述符get set参数和数据描述符的writeable value存在冲突,二选其一

 Object.defineProperty(obj, "school", {
        configurable: true, //表示configurable可以被删除
        // writable: true, 
        enumerable: true, //修改之后才可以被枚举,在遍历时被访问到
        // value: "zfpx",
        get() {
          console.log("调用了get方法");
          return value;
        },
        set(newVal) {
          console.log("调用了set方法");
          value = newVal;
        }
      });

数据发生变化,调用set

如图 1-2

1.1.3 数据劫持

知道了get和set的妙用,就可以对数据进行劫持了。

劫持的概念

说白了,就是拿到某数据,持有这个数据,可以操作增删改,也可以不操作,重点在持有他

监听

一旦数据被传入Vue实例就需要对data整个对象实行监听,

这里需要对data中的数据类型进行判断

如果是data中的属性是基本数据类型,只需要监控就好了

如果data中的属性是对象,则需要遍历对象下的所有属性,进行监控

可又有一个疑问,data的属性是对象A,A的属性还包含对象B,B有对象C,所以不能是遍历,而是递归,递归整个对象属性树

​
<body>
    <div id="app">
      <p>姓名是{{ name.firstName }}</p>
      <div>年龄是{{ age }}</div>
      {{ name }}
    </div>
    <script type="vue.js"></script>
    <script type="text/javascript">
      let vm = new Vue({
        el: "#app",
        data: {
          name: {
            firstName: "姓氏章",
            lastName: "名字"
          },
          age: 12 //通过Obj.defineProperty实现()或者Obj.defineProperties()实现
        }
      });
    </script>
 </body>

​

 数据绑定(传入{ 对象的data挂载在vue实例上)

/**
*Vue入口
*@{options} 限定为一个对象,接受这个{}对象
* */
function Vue(options = {}) {
this.$options = options; // 将所有属性挂载在vue实例$options上
var data = (this._data = this.$options.data); //将{}对象的data挂载vue实例上
observe(data);
}
  /**
   *观察对象变化,如果最开始传入的data是基本数据类型,已经被劫持了,不需要递归再去对属性进行监控
   *@{data} 被观察的对象或属性
   */
  function observe(data) {
    if (typeof data !== "object") return null;
    return new Observe(data);
  }
 class Observe {
        constructor(data) {
          this.start(data);
        }

        start(data) {
          for (let key in data) {
            let val = data[key];
            // 如果data中包含属性是对象,则需要递归对象的中属性,进行数据劫持
            // 如果data中的属性就是普通数据类型,递归退出 -- 递归出口
            Object.defineProperty(data, key, {
              enumrable: true,
              get() {
                console.log("调用get方法");

                return val;
              },
              // 会在数据改变的时候直接设置
              set(newVal) {
                console.log("调用set方法");

                //数据并没有改变
                if (newVal === val) {
                  return;
                }
                val = newVal;
              }
            });
          }
        }
      }

上述代码实现了数据劫持和监控数据的功能

接下来是

数据代理(this代理传入的{ }对象去调用data)

编译模板(读取文本节点中的字符串,抽成属性名,通过字面量的形式访问到属性值,去掉双大括号,显示到节点上)

到编译模板这一步,是实现了单向绑定,也就是data中的数据被显示在网页上,如同又通过视图,譬如input输入框,改变data的值。

往后看,谢谢。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值