Vue.js双向绑定实现原理详解


Posted in Javascript onDecember 22, 2016

Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。本文仅探究几乎所有Vue的开篇介绍都会提到的hello world双向绑定是怎样实现的。先讲涉及的知识点,再参考源码,用尽可能少的代码实现那个hello world开篇示例。

参考文章:https://3water.com/article/100819.htm

一、访问器属性

访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义。

var obj = { };

// 为obj定义一个名为hello的访问器属性

Object.defineProperty(obj, "hello", {

get: function () {return sth},

set: function (val) {/* do sth */}

})

obj.hello // 可以像普通属性一样读取访问器属性

访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。

obj.hello // 读取属性,就是调用get函数并返回get函数的返回值

obj.hello = "abc" // 为属性赋值,就是调用set函数,赋值其实是传参

Vue.js双向绑定实现原理详解

get和set方法内部的this都指向obj,这意味着get和set函数可以操作对象内部的值。另外,访问器属性的会"覆盖"同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略(也就是所谓的被"劫持"了)。

二、极简双向绑定的实现

Vue.js双向绑定实现原理详解

此例实现的效果是:随文本框输入文字的变化,span中会同步显示相同的文字内容;在js或控制台显式的修改obj.name的值,视图会相应更新。这样就实现了model =>view以及view => model的双向绑定,并且是响应式的。

Vue.js双向绑定实现原理详解

以上就是Vue实现双向绑定的基本原理。

三、分解任务

上述示例仅仅是为了说明原理。我们最终要实现的是:

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

首先将该任务分成几个子任务:

1、输入框以及文本节点与data中的数据绑定

2、输入框内容变化时,data中的数据同步变化。即view => model的变化。

3、data中的数据变化时,文本节点的内容同步变化。即model => view的变化。

要实现任务一,需要对DOM进行编译,这里有一个知识点:DocumentFragment。

四、DocumentFragment

DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用DocumentFragment处理节点,速度和性能远远优于直接操作DOM。Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

五、数据初始化绑定

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

以上代码实现了任务一,我们可以看到,hello world已经呈现在输入框和文本节点中。

Vue.js双向绑定实现原理详解

六、响应式的数据绑定

再来看任务二的实现思路:当我们在输入框输入数据的时候,首先触发input事件(或者keyup、change事件),在相应的事件处理程序中,我们获取输入框的value并赋值给vm实例的text属性。我们会利用defineProperty将data中的text劫持为vm的访问器属性,因此给vm.text赋值,就会触发set方法。在set方法中主要做两件事,第一是更新属性的值,第二留到任务三再说。

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

任务二也就完成了,text属性值会与输入框的内容同步变化:

Vue.js双向绑定实现原理详解

七、订阅/发布模式(subscribe&publish)

text属性变化了,set方法触发了,但是文本节点的内容没有变化。如何让同样绑定到text的文本节点也同步变化呢?这里又有一个知识点:订阅发布模式。

订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作

Vue.js双向绑定实现原理详解

之前提到的,当set方法触发后做的第二件事就是作为发布者发出通知:“我是属性text,我变了”。文本节点则是作为订阅者,在收到消息后执行相应的更新操作。

八、双向绑定的实现

回顾一下,每当new一个Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译HTML:nodeToFragement(id)。

在监听数据的过程中,会为data中的每一个属性生成一个主题对象dep。

在编译HTML的过程中,会为每个与数据绑定相关的节点生成一个订阅者watcher,watcher会将自己添加到相应属性的dep中。

我们已经实现:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的set方法。

接下来我们要实现的是:发出通知dep.notify() => 触发订阅者的update方法 => 更新视图。

这里的关键逻辑是:如何将watcher添加到关联属性的dep中。

Vue.js双向绑定实现原理详解

在编译HTML过程中,为每个与data关联的节点生成一个Watcher。Watcher函数中发生了什么呢?

Vue.js双向绑定实现原理详解

首先,将自己赋给了一个全局变量Dep.target;

其次,执行了update方法,进而执行了get方法,get的方法读取了vm的访问器属性,从而触发了访问器属性的get方法,get方法中将该watcher添加到了对应访问器属性的dep中;

再次,获取属性的值,然后更新视图。

最后,将Dep.target设为空。因为它是全局变量,也是watcher与dep关联的唯一桥梁,任何时刻都必须保证Dep.target只有一个值。

Vue.js双向绑定实现原理详解

Vue.js双向绑定实现原理详解

至此,hello world双向绑定就基本实现了。文本内容会随输入框内容同步变化,在控制器中修改vm.text的值,会同步反映到文本内容中(但输入框还没有绑定,读者可以自己试试)。

完整代码:https://github.com/bison1994/two-way-data-binding

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
EasyUi tabs的高度与宽度根据IE窗口的变化自适应代码
Oct 26 Javascript
利用cookie记住背景颜色示例代码
Nov 04 Javascript
setInterval()和setTimeout()的用法和区别示例介绍
Nov 17 Javascript
使用js检测浏览器是否支持html5中的video标签的方法
Mar 12 Javascript
再谈Jquery Ajax方法传递到action(补充)
May 12 Javascript
React Native react-navigation 导航使用详解
Dec 01 Javascript
JavaScript引用类型Array实例分析
Jul 24 Javascript
Element-Ui组件 NavMenu 导航菜单的具体使用
Oct 24 Javascript
React学习之JSX与react事件实例分析
Jan 06 Javascript
element-ui树形控件后台返回的数据+生成组织树的工具类
Mar 05 Javascript
详解如何在vue+element-ui的项目中封装dialog组件
Dec 11 Vue.js
解读Vue组件注册方式
May 15 Vue.js
BootStrap Table对前台页面表格的支持实例讲解
Dec 22 #Javascript
JS实现的简单轮播图运动效果示例
Dec 22 #Javascript
基于BootStrap与jQuery.validate实现表单提交校验功能
Dec 22 #Javascript
详解js的事件代理(委托)
Dec 22 #Javascript
HTML页面定时跳转方法解析(2种任选)
Dec 22 #Javascript
vue双向绑定的简单实现
Dec 22 #Javascript
jQuery使用Layer弹出层插件闪退问题
Dec 22 #Javascript
You might like
创建配置文件 用PHP写出自己的BLOG系统 2
2010/04/12 PHP
如何解决CI框架的Disallowed Key Characters错误提示
2013/07/05 PHP
PHP实现的防止跨站和xss攻击代码【来自阿里云】
2018/01/29 PHP
javascript下4个跨浏览器必备的函数
2010/03/07 Javascript
Jquery 插件开发笔记整理
2011/01/17 Javascript
js Dialog 实践分享
2012/10/22 Javascript
最好用的省市二级联动 原生js实现你值得拥有
2013/09/22 Javascript
document节点对象的获取方式示例介绍
2013/12/24 Javascript
javascript实现的弹出层背景置灰-模拟(easyui dialog)
2013/12/27 Javascript
JavaScript实现动态创建CSS样式规则方案
2014/09/06 Javascript
jQuery中DOM操作实例分析
2015/01/23 Javascript
JavaScript基础函数整理汇总
2015/01/30 Javascript
jQuery中noConflict()用法实例分析
2015/02/08 Javascript
javascript封装简单实现方法
2015/08/11 Javascript
Node.js与Sails ~项目结构与Mvc实现及日志机制
2015/10/14 Javascript
Vue.js 利用v-for中的index值实现隔行变色
2018/08/01 Javascript
js事件on动态绑定数据,绑定多个事件的方法
2018/09/15 Javascript
Angular2 自定义表单验证器的实现方法
2018/12/14 Javascript
Angular7.2.7路由使用初体验
2019/03/01 Javascript
[02:43]2018DOTA2亚洲邀请赛主赛事首日TOP5
2018/04/04 DOTA
python迭代器与生成器详解
2016/03/10 Python
Python实现的归并排序算法示例
2017/11/21 Python
Python面向对象类继承和组合实例分析
2018/05/28 Python
python实战串口助手_解决8串口多个发送的问题
2019/06/12 Python
Python基础学习之时间转换函数用法详解
2019/06/18 Python
Python使用贪婪算法解决问题
2019/10/22 Python
Django之腾讯云短信的实现
2020/06/12 Python
python 通过 pybind11 使用Eigen加速代码的步骤
2020/12/07 Python
迎新晚会主持词
2014/03/24 职场文书
小学教师寄语大全
2014/04/03 职场文书
悬崖上的金鱼姬观后感
2015/06/15 职场文书
2016年大学生党员承诺书
2016/03/24 职场文书
redis实现排行榜功能
2021/05/24 Redis
Matplotlib可视化之添加让统计图变得简单易懂的注释
2021/06/11 Python
MySQL系列之六 用户与授权
2021/07/02 MySQL
2022新作动画《福星小子》释出宣传影片 加入内田真礼&宫野真守配音演出
2022/04/08 日漫