主流的双向绑定方法 1.发布-订阅模式 通过使用 get 和 set 的方式获取数据然后更新数据,其原理就是监听页面中某个具体元素的事件,然后将其最新的值手动 set 到 数据中,同时订阅 model 层的改变,然后触发页面的渲染更新
2.脏检测 通过对比数据是否有变更,来决定是否更新视图。最简单的可以通过定时轮询去检测数据的变动。Angular 只有在指定事件触发时进入脏检测:
- DOM事件,比如用户输入文本点击按钮等(ng-click)
- XHR响应事件 浏览器 Location 变更
- Timer事件
- 执行 $digest() 或 $apply();
脏检查的主要原理是在将数据绑定到 View 的时候,就在监听器列表(scope 作用域中的监听队列 watchList)中插入一条监听器,当触发 UI 事件或者 Ajax 请求时,就会触发脏检查($digest cycle), 在 $digest 流程中,将遍历每个数据变量的 watcher,比较它的新旧值。当新旧值不同时,触发 listener 函数,执行相关的更新逻辑。这个过程将会一直重复,直到所有数据指令的新旧值都相同为止。
脏检查虽然可以达到实现双向绑定,但是当页面中绑定的 watcher 过多时,就会引发性能问题。所以 angular 在进行 $digest 检测时,会限制循环检查的次数最少2次,最多10次,防止无效的检查。
3.数据劫持 通过 ES5 的 Object.defineProperty() 来劫持数据属性的 getter 和 setter, 在数据变动时触发订阅者(watcher),从而触发相应的监听回调更新视图。
- Observer 对数据的所有属性进行监听其 getter 和 setter
- Compile 是一个指令解析器,对 MVVM 实例的所有元素指令进行解析,并渲染成 model 中的绑定数据,当数据进行更新时,也能替换为更新后的值。
- Watcher 作为 Compile 和 Observer 的桥梁,能够订阅数据属性的更新,然后执行相应的监听回调
- Deps 用于存放监听器数组,主要用来保存 Watcher
- Updater 执行更新操作,针对不同的指令进行不同的更新操作,如 v-model, v-class, v-html 等类型的指令。
- MVVM 作为入口函数,整合以上所有的功能。
Observer 劫持了所有数据属性的 getter 和 setter,当数据发生改变时,就会通知 deps 中所有 watcher 的更新操作,进而触发页面的重新渲染,这是修改 Model 层从而引发 View 层的重新渲染。 在 Compile 中监听可输入元素的事件,然后将新值更新到 model 的数据中,这是修改 View 层触发的 Model 层的修改。
- 用户名或者邮箱跟github没有关联上, github认为不是你提交的, 不统计。
- fork 的项目, 不统计
- 没有在版本库的master【默认分支】上提交
defineProperty实现
目前支持双向绑定的Vue中的实现就是这种方法。但是这种方法不太好的地方就是对于数组之类的对象,类似修改数组的length,直接用索引设置元素如items[0] = {},以及数组的push,pop等变异方法是无法触发setter的。针对这些,vue中的实现是在Object和Array的原型添加了定制方法来处理这些特殊操作,可以实现上述要求。
1 | /** |
proxy实现
怎么理解reflect reflect 是es6新增的一个全局对象。顾名思义,反射,类似于Java里面的反射机制。在Java里面,反射是个很头疼的概念。简单理解为:通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。对于Java来说,程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
1 | /** |
mvvm
mvvm
1 | function MVVM(options) { |
observer
1 | function Observer(data) { |
watcher
1 | /** |
dep
1 | // 订阅事件的唯一标识 |
compile
1 | function Compile(el, vm) { |