Vue双向数据绑定原理

什么是数据双向绑定?

vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。这也算是vue的精髓之处了。数据双向绑定,一定是对于UI控件来说的。

前端常见的框架 Vue angular ,具体实现不一样 angular 使用 脏值检测 。 Vue使用的 数据劫持,发布订阅 基于Object.defineProperty,这也就导致不能兼容ie8一下低版本浏览器。

Object.defineProperty

MDN详细描述

发布订阅

发布订阅模式顾名思义 分成两部分,发布和订阅.并且是先订阅,后发布

举个例子

class Dep {
  //订阅,维护一个订阅数组
  constructor() {
    this.subs = [];
  }
  //订阅
  addSub(sub) {
    this.subs.push(sub);
  }
  //发布
  notify() {
    this.subs.forEach(item => {
      item.update();
    });
  }
}
//统一update方法
class Watcher {
  constructor(fn) {
    this.fn = fn;
  }
  update() {
    this.fn();
  }
}

let w1 = new Watcher(function() {
  console.log("1");
});
let w2 = new Watcher(function() {
  console.log("2");
});
let w3= new Watcher(function() {
  console.log("3");
});

let dep = new Dep();

// 添加订阅
dep.addSub(w1);
dep.addSub(w2);
dep.addSub(w3);
dep.notify();

实现

function Vue(options) {
  this.$options = options;  //挂载到options
  var data = (this._data = this.$options.data); //挂载data名字任意
  observe(data); //添加get,set 数据劫持
  for (const key in data) {
    Object.defineProperty(this, key, {
      enumerable: true,
      get() {
        return this._data[key];
      },
      set(newVal) {
        this._data[key] = newVal;
      }
    });
  }
  /** 
   * vue => this[key] 取到值
   * 将数组中的key挂载到this上
  */ 
  initComputed.call(this);  //初始化监听属性
  new Complile(options.el, this);
}
function initComputed() {
  let vm = this;
  let {computed} = this.$options
  Object.keys(computed).forEach(key => {
    Object.defineProperty(vm, key,{
      get:typeof computed[key] === 'function'?computed[key]:computed[key].get,
    });
  });
  /**
   * Object.keys获得数组,遍历添加get 
   * 因为计算属性只需要获取值,它的依赖取决于里面 内部使用的 数据
   * 
  */
}
function Observe(data) {  //数据劫持类
  let dep = new Dep(); //初始化订阅
  for (const key in data) {
    let val = data[key];
    observe(val);
    Object.defineProperty(data, key, {
      enumerable: true, //可枚举
      get() {
        Dep.target && dep.addSub(Dep.target); //Dep.target也可以用全局变量代替
        return val;
      },
      set(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        observe(newVal);
         /***
          vue 早期版本存在的问题,新添加的数组没有get,set,(现在修复了)
          对象处罚set时候,对新数据添加劫持
         */
        dep.notify();//发布
      }
    });
  }
}

function observe(data) {
  if (typeof data !== "object") {
    return;
  }
  return new Observe(data);
}

function Complile(el, vm) {
  /**
   编译模板类主要就是完成,
   MVVM实例数据的替换
  */
  vm.$el = document.querySelector(el);

  let fragment = document.createDocumentFragment();
  while ((child = vm.$el.firstChild)) {
    fragment.appendChild(child);
  }
  replace(fragment);
  function replace(fragment) {
    Array.from(fragment.childNodes).forEach(node => {
      let text = node.textContent;
      let reg = /\{\{(.*)\}\}/;
      if (node.nodeType === 3 && reg.test(text)) {
        let val = getVal(vm,RegExp.$1)
        new Watcher(vm, RegExp.$1, function(newVal) {
          node.textContent = text.replace(/\{\{(.*)\}\}/, newVal);
        });
        node.textContent = text.replace(/\{\{(.*)\}\}/, val);
      }
      if (node.nodeType === 1) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
          let { name } = attr;
          let exp = attr.value;
          if (name.indexOf("v-") === 0) {
            node.value = getVal(vm,exp);
          }
          new Watcher(vm, exp, function(newVal) {
            node.value = newVal;
          });
          node.addEventListener("input", function(e) {
            let newVal = e.target.value;
            let val = vm;
            let arr = exp.split(".");
            if (arr.length >= 2) {
              let valList = arr.map(key => {
                val = val[key];
                return val;
              });
              valList[valList.length - 2][arr.pop()] = newVal;
            } else {
              vm[exp] = newVal;
            }
          });
        });
      }
      if (node.childNodes) {
        replace(node);
      }
    });
  }

  vm.$el.appendChild(fragment);
}

class Dep {
  //订阅,维护一个订阅数组
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  notify() {
    this.subs.forEach(item => {
      item.update();
    });
  }
}
//统一update方法
class Watcher {
  constructor(vm, exp, fn) {
    this.fn = fn;
    this.vm = vm;
    this.exp = exp;
    Dep.target = this;
    let val = vm;
    let arr = exp.split(".");
    arr.forEach(key => {
      val = val[key];
    });
    //触发get 方法,添加watcher
    Dep.target = null;
  }
  update() {
    let val = getVal(this.vm,this.exp)
    this.fn(val);
  }
}

function getVal(vm,exp){
  let val = vm;
  let arr = exp.split(".");
  arr.forEach(key => {
    val = val[key];
  });
  return val
}

实现地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值