Vue 双向数据绑定原理

一句话概括

基于 ES5 的 Object.defineProperty() 这个方法对数据做劫持。劫持数据的 setter 与 getter 。然后结合
发布订阅模式在数据发生数据,通知页面进行更新。实现时通过三个类:Vue类、Compile(解析双花括号的类)、发布订阅类(Event)
由于 ES5 的 Object.defineProperty() 这个方法不兼容 IE8 。所以我们的 vue 的兼容性也是不兼容 IE8 既以下版本

核心:

     // 取值: this.name 的时候,会进入到 get 函数中
     // 赋值: this.name = xxxx 的时候,会进入到 set 函数中
 Object.defineProperty(this, 'name', {
    get () {
      console.log('取值')
      return '我的天'
    },
    set (value) {
      console.log('赋值', value)
    }
  })
具体代码实现:

//模拟vue创建一个vue类
class Vue {
  constructor(option) {
    //option : 实例化时传入的全部参数。如下例子则是{el、data···}
    /**
         * const vm = new Vue({
            el:"#app",
            data:{
                name:"张三",
                age:16,
                sex:"男"
            }
        })*/
    //实现vue的实例属性 => vm.$el、vm.$el
    this.$el = document.querySelector(option.el);
    this.$data = option.data;
    this.observe(this.$data);
    //把监听对象挂载到vue实例中的_el上。然后在下面newCompile里把vue实例传下去,则在compile中能通过this.vm._el.$on监听事件
    this._el = new Event();
    new Compile(this.$el, this);
  }
  //这个方法是实现Vue实例对象中的data属性里的每个对象属性直接挂到实例上(实现Vue代理属性)即
  // this.name === this.$data.name
  // this.age  === this.$data.age
  //Object.defineProperty()也是实现双向数据绑定的重要原理
  observe(data) {
    Object.keys(data).forEach((key) => {
      Object.defineProperty(this, key, {
        get() {
          //当vm.name 访问时会进入这个方法
          return data[key];
        },
        set(value) {
          //vm.name = "李四" 进行设置值时会进入这个set方法
          data[key] = value;
          //当修改实例属性的时候发布消息。通知修改页面{{}}的值
          this._el.$emit(key);
        },
      });
    });
  }
}
//解析{{}}的类,这个类需要两个参数,一个是Vue实例对象(让能知道实例中的数据改变了),一个是dom元素el(能够在知道vm的数据改变了之后,操作el,修改dom中的值)
class Compile {
  constructor(el, vm) {
    //把vue实例也挂载在Compile实例上。因为获取这个实例上的值来改变dom的值
    this.vm = vm;
    this.compile(el);
  }
  compile(el) {
    // 遍历 el 这个 DOM 对象 子节点
    el.childNodes.forEach((node) => {
      // 得到所有的文本节点
      // if (node.nodeType === 3) {
      //   console.log(node.textContent)
      // }
      // 得到所有的文本节点,节点的文本内容类似 {{ xxx }} 这种格式的
      if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)) {
        //取出dome{{}}中的表达式。$1则是能达到(.*)中的表达式。是正则的一个属性
        const exp = RegExp.$1.trim();
        //将页面的{{name}}中的name赋值为vm中的name属性的值
        node.textContent = this.vm[exp];
        //this.vm === new Vue。订阅exp(data中的key)这个事件,当exp发布消息时,执行回调,改变页面{{}}号里面的内容
        this.vm._el.$on(exp, () => {
          node.textContent = this.vm[exp];
        });
      }
      //递归。取出el中的所有文本节点
      if (node.childNodes) {
        this.compile(node);
      }
    });
  }
}
//发布订阅类,实时监听。在实例数据改变的时候发布消息,告知compile类,改变页面dom的数据
class Event {
  constructor() {
    this.dep = {};
  }
  $on(evetName, callBack) {
    if (!this.dep[evetName]) {
      this.dep[evetName] = [];
    }
    this.dep[evetName].push(callBack);
  }
  $emit(evetName) {
    if (this.dep[evetName]) {
      this.dep[evetName].forEach((cb) => {
        cb();
      });
    }
  }
}
页面结果

<body>
    <div id="app">
        <p>{{name}}</p>
        <p>{{age}}</p>
        <p>{{sex}}</p>
    </div>
    
    <script src="./index1.js"></script>
    <script>
        const vm = new Vue({
            el:"#app",
            data:{
                name:"张三",
                age:16,
                sex:"男"
            }
        })
    </script>
    
</body>

实例化后页面显示:
在这里插入图片描述

然后改变实例vm的name和age属性都会引起页面的变化
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值