通过源码分析Vue的双向数据绑定详解(3)

注释看的出来Dep.target是全局唯一的watcher对象,也就是当前正在指令计算的订阅者,它会在计算时赋值成一个watcher对象,计算完成后赋值为null。而depend是用于对该订阅者添加依赖,告诉它你的值依赖于我,每次更新时应该来找我。另外还有notify()的函数,用于遍历所有的依赖,通知他们更新数据。

这里多看一下addDep()的源码:

/** * Add a dependency to this directive. */ Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { // 使用push()方法添加一个订阅者 dep.addSub(this); } } };

可以看到它有去重的机制,当重复依赖时保证相同ID的依赖只有一个。订阅者包含3个属性newDepIds/newDeps/depIds分别存储依赖信息,如果之前就有了这个依赖,那么反过来将该订阅者加入到这个依赖关系中去。

接着看get方法中的dependArray() :

/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value) { for (var e = (void 0), i = 0, l = value.length; i < l; i++) { e = value[i]; e && e.__ob__ && e.__ob__.dep.depend(); if (Array.isArray(e)) { dependArray(e); } } }

可以看到我们不能像对象一样监听数组的变化,所以如果获取一个数组的值,那么就需要将数组中所有的对象的观察者列表都加入到依赖中去。

这样get方法读取值就代理完成了,接下来我们看set方法代理赋值的实现,我们先获取原始值,然后与新赋的值进行比较,也叫脏检查,如果数据发生了改变,则对该数据进行重新建立观察者,并通知所有的订阅者更新。

接下来我们看下数组的更新检测是如何实现的:

/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var arguments$1 = arguments; // avoid leaking arguments: // var i = arguments.length; var args = new Array(i); while (i--) { args[i] = arguments$1[i]; } var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case 'push': inserted = args; break case 'unshift': inserted = args; break case 'splice': inserted = args.slice(2); break } if (inserted) { ob.observeArray(inserted); } // notify change ob.dep.notify(); return result }); });

看的出来我们模拟了一个数组对象,代理了push/pop/shift/unshift/splice/sort/reverse方法,用于检测数组的变化,并通知所有订阅者更新。如果有新建元素,会补充监听新对象。

这就是从代码上解释为什么Vue不支持数组下标修改和长度修改的原因,至于为什么这么设计,我后面会再次更新或再开篇文章,讲一些通用的设计问题以及Js机制和缺陷。

总结

从上面的代码中我们可以一步步由深到浅的看到Vue是如何设计出双向数据绑定的,最主要的两点:

使用getter/setter代理值的读取和赋值,使得我们可以控制数据的流向。

使用观察者模式设计,实现了指令和数据的依赖关系以及触发更新。

对于数组,代理会修改原数组对象的方法,并触发更新。

明白了这些原理,其实你也可以实现一个简单的数据绑定,造一个小轮子,当然,Vue的强大之处不止于此,我们后面再来聊一聊它的组件和渲染,看它是怎么一步一步将我们从DOM对象的魔爪里拯救出来的。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

参考资料

数据的响应化:https://github.com/Ma63d/vue-...
Vue v2.2.0 源代码文件
es6 Proxy: ...

您可能感兴趣的文章:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyzdyg.html