Vue2.0源码阅读笔记 您所在的位置:网站首页 vue2双向数据绑定底层原理 Vue2.0源码阅读笔记

Vue2.0源码阅读笔记

2024-04-29 03:41| 来源: 网络整理| 查看: 265

  上一篇 文章 了解了Vue.js的生命周期。这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理。

一、实现双向绑定的做法

  前端MVVM最令人激动的就是双向绑定机制了,实现双向数据绑定的做法大致有如下三种:

1.发布者-订阅者模式(backbone.js)

思路:使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的JavaScript对象以及DOM元素都将“订阅”一个发布者对象。任何时候如果JavaScript对象或者一个HTML输入字段被侦测到发生了变化,我们将代理事件到发布者-订阅者模式,这会反过来将变化广播并传播到所有绑定的对象和元素。

2.脏值检查(angular.js)

思路:angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,angular只有在指定的事件触发时进入脏值检测,大致如下:

DOM事件,譬如用户输入文本,点击按钮等。( ng-click )

XHR响应事件 ( $http )

浏览器Location变更事件 ( $location )

Timer事件( $timeout , $interval )

执行 $digest() 或 $apply()

3.数据劫持(Vue.js)

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

由此可见,Object.defineProperty() 这个API是Vue实现双向数据绑定的关键,我们先简单了解下这个API,了解更多戳这里

二、Object.defineProperty()

简单例子:

var obj = {}; Object.defineProperty(obj, 'hello', { get: function() { console.log('get val:'+ val); return val;   },   set: function(newVal) { val = newVal; console.log('set val:'+ val); } });obj.hello='111';obj.hello;

结果:

如果去掉 obj.hello=‘111’ 这行代码,则get的返回值val会报错val is not defined。可见Object.defineProperty() 监控对数据的操作,可以自动触发数据同步。下面我们先用Object.defineProperty()来实现一个非常简单的双向绑定。

三、实现简单的双向绑定

 最简单例子:

DOCTYPE html> var obj = {}; Object.defineProperty(obj, 'hello', { get: function() { console.log('get val:'+ val); return val; }, set: function(newVal) { val = newVal; console.log('set val:'+ val); document.getElementById('a').value = val; document.getElementById('b').innerHTML = val; } }); document.addEventListener('keyup', function(e) { obj.hello = e.target.value; });

实现效果如下:

上面例子直接用了dom操作改变了文本节点的值,而且是在我们知道是哪个id的情况下,通过document.getElementById 获取到相应的文本节点,然后直接修改文本节点的值,这种做法是最简单粗暴的。

封装成一个框架,肯定不能是这种做法,所以我们需要一个解析dom,并能修改dom中相应的变量的模块。

四、实现简单Compile

首先我们需要获取文本中真实的dom节点,然后再分析节点的类型,根据节点类型做相应的处理。

在上面例子我们多次操作了dom节点,为提高性能和效率,会先将所有的节点转换城文档碎片fragment进行编译操作,解析操作完成后,再将fragment添加到原来的真实dom节点中。

DOCTYPE html> {{text}}     function Compile(node, vm) { if(node) {this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 将所有子节点添加到fragment中,child是指向元素首个子节点的引用。将child引用指向的对象append到父对象的末尾,原来child引用的对象就跳到了frag对象的末尾,而child就指向了本来是排在第二个的元素对象。如此循环下去,链接就逐个往后跳了 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //节点类型为元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析属性 for(var i = 0; i DOCTYPE html> {{text}}   function Compile(node, vm) { if(node) { this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 将所有子节点添加到fragment中 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //节点类型为元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析属性 for(var i = 0; i 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作

 首先我们要一个收集订阅者的容器,定义一个Dep作为主题对象

然后定义订阅者Watcher

添加订阅者Watcher到主题对象Dep,发布者发出通知放到属性监听里面

最后需要订阅的地方

至此,比较简单地实现了我们第三步用dom操作实现的双向绑定效果,代码:

DOCTYPE html> {{text}}   function Compile(node, vm) { if(node) { this.$frag = this.nodeToFragment(node, vm); return this.$frag; } } Compile.prototype = { nodeToFragment: function(node, vm) { var self = this; var frag = document.createDocumentFragment(); var child; while(child = node.firstChild) { self.compileElement(child, vm); frag.append(child); // 将所有子节点添加到fragment中 } return frag; }, compileElement: function(node, vm) { var reg = /\{\{(.*)\}\}/; //节点类型为元素 if(node.nodeType === 1) { var attr = node.attributes; // 解析属性 for(var i = 0; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有