vue数据劫持双向绑定原理

在Vue中

template 模板:

<div><span>{{text}}</span></div>

虚拟 DOM 的变化:

<div><span>before</span></div>
<div><span>after</span></div>

第一步,转换为响应式

思考:当 data 下的某个属性发生变化时,如何触发相应的函数

解决:ES5 中提供了 Object.defineProperty方法,可以自定义 settergetter 函数,在设置和获取对象属性的时候可以触发相应的回调函数。

步骤:把数据变成可观察的,遍历每个属性递归属性的子属性,通过 Object.defineProperty 劫持每个属性,给属性加上 setter 和 getter 函数,当属性发生改变的时候,调用回调函数,当重新设置属性的时候触发 render 渲染。

var demo1 = new Vue({
  el: "#demo1",
  data: {
    text: "before",
    o: {
      text: "o-before"
    }
  },
  render() {
    console.log("我要渲染了");
  }
});
// Vue构造函数
class Vue {
  constructor(options) {
    this.$options = options;
    this._data = options.data;
    // observer方法:将data中的数据转换响应式的,把数据变成可观察的
    observer(options.data, this._update.bind(this));
    // 页面第一次更新
    this._update();
  }
  _update() {
    this.$options.render();
  }
}
function observer(obj, cb) {
  Object.keys(obj).forEach(key => {
    // 递归每个子属性
    if (typeof obj[key] === "object") {
      new observer(obj[key], cb);
    }
    defineReactive(obj, key, obj[key], cb);
  });
}
function defineReactive(obj, key, val, cb) {
  // 核心原理
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      console.log("你访问了" + key);
      return val;
    },
    set: newVal => {
      if (newVal === val) {
        return;
      }
      console.log("你设置了" + key);
      console.log("新的属性" + key + " = " + newVal);
      val = newVal;
      // data属性发生变化后,触发回调函数
      cb();
    }
  });
}

第二步:解决触发 render 函数准确度的问题,引入虚拟 DOM

思考渲染准确度问题

new Vue({
  template: `<div><span>name:</span>{{name}}</div>`,
  data: {
    name: "js",
    age: 13
  }
});
setTimeout(() => {
  demo1.age = 20; // 思考:修改age属性,会触发渲染么?
}, 200);

介绍虚拟 DOM

new Vue({
  data: {
    text: "before"
  },
  render(h) {
    return (
      <div>
        <span>{{ text }}</span>
      </div>
    );
  }
});

转化格式后:

new Vue({
  data: {
    text: "before"
  },
  render() {
    return this.__h__("div", {}, [
      this.__h__("span", {}, [this.__toString__(this.text)])
    ]);
  }
});

当声明一个 Vue 对象,在执行 render 函数时获取虚拟 DOM:

// 虚拟 DOM:使用js对象结构表示一棵对象树
function VNode(tag, data, children, text) {
  return {
    tag, // html 标签名
    data, // 标签上的 class 和 style 等属性
    children, // 子节点
    text // 文本节点
  };
}
class Vue {
  constructor(options) {
    this.$options = options;
    const vdom = this._update();
  }
  _update() {
    return this._render.call(this);
  }
  _render() {
    const vnode = this.$options.render.call(this);
    return vnode;
  }
  __h__(tag, attr, children) {
    return VNode(
      tag,
      attr,
      children.map(child => {
        if (typeof child === "string") {
          return VNode(undefined, undefined, undefined, child);
        } else {
          return child;
        }
      })
    );
  }
}

收集依赖

原因:每一个 data 属性都有可能被依赖,因此在每个属性做响应式转化的时候都应该初始化收集依赖的类。

add方法:执行render的时候,依赖到的变量的get就会执行,然后把这个render函数添加到subs里面

notify方法:当set的时候,就执行notify,也就是执行所有subs数组里面的函数,其中包括render的执行。

Dep.target:用来区分是普通的get还是收集依赖时的get

// 收集依赖的类
class Dep {
  constructor() {
    this.subs = [];
  }
  add(cb) {
    this.subs.push(cb);
  }
  notify() {
    this.subs.forEach(cb => cb());
  }
}
function defineReactive(obj, key, val, cb) {
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    // ...
  });
}

总结

Vue中:用Object.defineProperty这个特性可以精确的写出发布订阅模式,渲染机制更加准确。

react中:写好 shouldComponentUpdate 是非常重要的。

参考PPT展示: http://sirm2z.github.io/vue-demo-ppt/index.html#/your-first-slideshow

结合MVVM理解

数据劫持 + 发布者-订阅者模式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

  1. 对需要observe的数据对象进行递归遍历,给属性都加上 setter和getter,当改变属性时,就会触发setter,监听数据变化
  2. compile解析模板指令,给节点绑定更新函数,添加监听数据的订阅者,一旦数据变更,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁:
    1. 在自身实例化时往属性订阅器(dep)里面添加自己
    2. 自身必须有一个update()方法
    3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,

MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

Observer其实是需要观察的数据本身model,内部有setter函数监听自己的变化。
Compile其实是视图view,监听用户操作,响应数据变化
Watcher其实是ViewModel,本质是监听的函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值