You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionCompile(el,vm){this.el=this.isElementNode(el) ? el : document.querySelector(el);this.vm=vm;if(this.el){//将真实DOM移入内存 fragment 中letfragment=this.node2Fragment(this.el);this.compile(fragment);//将编译后的 fragment 再次转化为 DOM 塞回到页面中this.el.appendChild(fragment);}}
Compile 的构造函数中主要做了如下工作:
将 DOM 结构树转化为 fragment (文档碎片),之所以这样转化,主要是因为在编译指令中,必然涉及到将 data 里面的数据塞到 DOM 对应的指令元素中,也就涉及到对 DOM 的更新操作,如果直接修改原生 DOM,将产生频繁的页面重绘,导致页面性能降低,所以需要将 DOM 树存储到内存中。
//将 DOM 转化为 fragmentnode2Fragment: function(el){letfragment=document.createDocumentFragment();//每次获取DOM节点树中的第一个元素并移除,直到移除完毕为止while(el.firstChild){fragment.appendChild(el.firstChild);}//返回一个文档碎片容器,存储DOM树的所有节点returnfragment;},
双向绑定的概念
所谓双向绑定,是指:用户在视图(View)层触发更改时能让数据模型(Model)检测到其更新并发生变化,同时数据模型(Model)层对数据的改变也能实时更新到视图层。也就是 MVVM 的核心概念,MVVM 的示意图如下:
双向绑定的三种实现方式
实现双向绑定有如下几种方式:
发布-订阅
这种方式是通过使用 get 和 set 的方式获取数据然后更新数据,其原理就是监听页面中某个具体元素的事件,然后将其最新的值手动 set 到 数据中,同时订阅 model 层的改变,然后触发页面的渲染更新,具体详见这里,具体的示意图如下所示:
这种方式虽然实现了双向绑定的功能,但是不能通过设置 model: vm.data = 'value' 的形式修改数据, 进而更新视图,存在一定的劣势。
脏检查
赃检查的主要原理是在将数据绑定到 View 的时候,就在监听器列表(scope 作用域中的监听队列 watchList) 中插入一条监听器,当触发 UI 事件或者 Ajax 请求时,就会触发脏检查($digest cycle), 在 $digest 流程中,将遍历每个数据变量的 watcher,比较它的新旧值。当新旧值不同时,触发 listener 函数,执行相关的更新逻辑。这个过程将会一直重复,直到所有数据指令的新旧值都相同为止。具体详见 这里,脏检查的原理示意图如下所示:
脏检查虽然可以达到实现双向绑定,但是当页面中绑定的 watcher 过多时,就会引发性能问题。所以 angular 在进行 $digest 检测时,会限制循环检查的次数最少2次,最多10次,防止无效的检查。
数据劫持
这种方式是利用 ES5 的 Object.defineProperty() 来劫持数据属性的 getter 和 setter, 在数据变动时触发订阅者,从而触发相应的监听回调。vue 也是使用的这种思路实现的双向绑定,下面来详细讲述一下这种方法的原理和实现。
原理
先上一张图:
如上图所示:数据劫持主要实现了如下几个功能:
具体原理如下:
Observer 劫持了所有数据属性的 getter 和 setter,当数据发生改变时,就会通知 deps 中所有 watcher 的更新操作,进而触发页面的重新渲染,这是修改 Model 层从而引发 View 层的重新渲染。
在 Compile 中监听可输入元素的事件,然后将新值更新到 model 的数据中,这是修改 View 层触发的 Model 层的修改。
如何实现
下面分别从上图中涉及到的一些类来从代码层面介绍一下其实现细节:
MVVM 入口类
一般在 html 中,会实例化 MVVM 类,从而传递一些参数,如下所示:
这里面只是向 MVVM 传递了一些参数,包括 el(页面对应的元素),data( 修改的数据),method(要传递的方法),下面是 MVVM 的定义:
如代码所示,MVVM 的构造函数中只是执行了 Observer 类的实例化和 Compile 类的实例化,并且执行了原型方法中的 proxyData,该方法主要是做了一层数据的代理,也就是可以直接通过设置 vm.message.a.b 的形式进行 get 和 set 操作,相当于对 vm.$data.message.a.b 进行相应的操作。
Compile
Compile 主要是做了编译指令的工作,指令类型包括 v-html、v-class、v-model、v-on:click、{{}} 的多种形式。
Compile 的构造函数中主要做了如下工作:
编译模块主要是针对 v-html、v-class、v-model、v-on:click、{{}} 这几种情况做分别编译处理。这里不详述,注释也写得比较清楚,直接戳文末的代码链接吧。
Observe
这个类主要是劫持数据所有属性的 setter 和 getter 方法,具体代码如下:
这里面在 Observer 的构造函数里面就执行了 observe 的遍历方法,遍历传进数据的所有属性,然后使用 ES5 的 defineProperty() 进行劫持。重点关注 get 和 set 的方法,有一个 Dep 的概念,我们先来看看 Dep 都干了啥
Dep
Dep 的作用是一个存储 watcher 的容器,代码如下:
里面有一些对数组的添加、删除和通知的方法,比较简单,不详述。这里面的 Dep.target 是用来存储当前操作的 Watcher 的,是一个全局变量。
Watcher
Watcher 作为 Compile 和 Observer 的桥梁,是用来监听数据层的变化,并触发页面更新的开关。代码如下:
原型中主要是定义了一些方法,比较简单,这里也不详述。
现在我们要串一下这些类是怎么关联起来的了。流程如下:
上面实现了 Watcher 的添加。
当数据改变时,页面如何改变的,也来串一下整体流程:
当页面改变时,数据是如何改变的呢:
这段代码是在 Compile 中,对页面元素进行事件监听,从而触发 Model 层的数据更新。
至此整个流程串起来了,一气呵成!
但是上面的处理还是没有考虑到当 data 是 数组的情况,所以还不是很完善,需要进一步加强,以上功能所有源码地址
The text was updated successfully, but these errors were encountered: