vue的双向绑定的原理是什么?

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()或者Proxy来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue2.0整体思路

核心:通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的

Vue遍历data对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,在属性被访问和修改时通知变更。

通过如下代码实现对属性的监听:

var data = {name: 'Pat'};
observe(data);
data.name = 'PatWu'; // 哈哈哈,监听到值变化了 kindeng --> dmq
function observe(data) {
 if (!data || typeof data !== 'object') { return; } // 遍历所有属性 Object.keys(data).forEach(function(key) {
 defineReactive(data, key, data[key]);
 });};
function defineReactive(data, key, val) {
 observe(val); // 递归处理子属性
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: false,
 get: function() {
 console.log('监听到了取值: ', val);
 return val; }, set: function(newVal) {
 console.log('监听到了值变化: ', val, ' --> ', newVal);
 val = newVal; } });}

在渲染过程中会对 vm 上的数据访问,这个时候会触发了据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,
从而实现依赖的收集:

// ...省略
function defineReactive(data, key, val) {
 observe(val);
 var dep = new Dep(); // 实例化依赖收集对象
 Object.defineProperty(data, key, {
 get: function() {
 // 依赖收集 dep.depend(); // Dep.target.addDep(this) return val; } // ... 省略 });}

Dep.target 渲染过程中已经被赋值为渲染 watcher,那么就执行到 addDep 方法, 接着把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,
这个目的是为后续数据变化时候能通知到哪些 subs 做准备。

// watcher.js
function addDep(dep) {
 // ...省略 dep.addSub(this)}

收集依赖并监听到变化之后就是怎么通知订阅者了,数据变动触发数据对象的setter,setter中会执行dep.notify(),再调用订阅者的update方法实现数据更新渲染。

// ... 省略
function defineReactive(data, key, val) {
 observe(val);
 var dep = new Dep(); // 实例化依赖收集对象
 Object.defineProperty(data, key, {
 // ... 省略 set: function(newVal) {
 if (val === newVal) return; console.log('监听到了值变化: ', val, ' --> ', newVal);
 val = newVal; dep.notify(); // 通知所有订阅者
 } });}
function Dep() {
 this.subs = [];}
Dep.prototype = { addSub: function(sub) {
 this.subs.push(sub);
 }, notify: function() {
 this.subs.forEach(function(sub) {
 sub.update(); // 触发更新渲染
 }); }};

Vue3.0实现方式

Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变。

function observer(data) {
 const that = this; for(var key in data){ that.deps[key] = []; //初始化所有订阅者对象{msg: [订阅者], info: []} } let handler = { get(target, property) {
 return target[property]; }, set(target, key, value) {
 let res = Reflect.set(target, key, value);
 var watchers = that.deps[key]; watchers.map(item => {
 item.update();
 }); return res; } } // 通过Proxy实现对数据的劫持 this.$data = new Proxy(data, handler);
}

Object.defineProperty 和 Proxy的优缺点:
1.Object.defineProperty的缺点
不能监听数组:因为数组没有getter和setter,因为数组长度不确定,如果太长性能负担太大
只能监听属性,而不是整个对象,需要遍历循环属性
只能监听属性变化,不能监听属性的删减
2.proxy的好处
可以监听数组
监听整个对象不是属性
13种来截方法,强大很多
返回新对象而不是直接修改原对象;
3.proxy的缺点
兼容性不好,而且无法用polyfill磨平;

你可能感兴趣的:(vue的双向绑定的原理是什么?)