Vue双向绑定实现原理与方法详解


Posted in Javascript onMay 07, 2020

本文实例讲述了Vue双向绑定实现原理与方法。分享给大家供大家参考,具体如下:

昨天接到一个电话面试,上来第一个问题就是Vue双向绑定的原理。当时我并不知道如何监听数据层到视图层的变化,于是没答上来,挂电话后,我赶忙查了下资料,主要思路有如下三种。

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,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue双向绑定实现原理与方法详解

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';//控制台打印set val:111
obj.hello; //控制台打印get val:111

当获取hello属性时,触发get;设置hello值时,触发set;这就是vue实现双向绑定的核心

完整代码如下

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model</title>
</head>

<body>
<div id='app'>
<h2>{{title}}</h2>
<input id='i' v-model='text' type="text">
<h1>{{text}}</h1>
<button v-on:click='clickMe'>click me</button>
</div>
<script>
//Dom类 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图
//并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
class Doms {
constructor(node, vm) {
if (node) {
this.$frag = this.nodeToFragment(node, vm)
return this.$frag
}
}
nodeToFragment(node, vm) {//将dom转换成fragment
var frag = document.createDocumentFragment()
var child;
while (child = node.firstChild) {
this.compileElement(child, vm)
frag.appendChild(child)
}
return frag
}
compileElement(node, vm) {//获取v-model属性,给dom赋值
var reg = /\{\{(.*)\}\}/ //匹配双括号里面的任何字符
if (node.nodeType === 1) {//element元素
var attr = node.attributes;
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue//获取绑定的key
node.addEventListener('input', function (e) {
vm[name] = e.target.value//触发set方法
})
new Watcher(vm, node, name, 'value')
} else if (attr[i].nodeName.includes(':')) {
var eventType = attr[i].nodeName.split(':')[1]//事件名
var cb = vm.methods && vm.methods[attr[i].nodeValue]
if (eventType && cb) {
node.addEventListener(eventType, cb.bind(vm), false)
}
}
}
if (node.childNodes && node.childNodes.length) {//如果还有子节点 递归
[...node.childNodes].forEach(n => this.compileElement(n, vm))
}
}
if (node.nodeType === 3) {//text
if (reg.test(node.nodeValue)) {
var name = RegExp.$1
name = name.trim()
new Watcher(vm, node, name, 'nodeValue')
}
}
}
}
class Vue {//Vue类
constructor(params) {
this.data = params.data //获取属性
this.methods = params.methods //获取方法
this.observe(params.data, this)//监听属性
var id = params.el;
var dom = new Doms(document.getElementById(id), this)
document.getElementById(id).appendChild(dom)
params.mounted.call(this)
}
observe(obj, vm) {//读取data内属性,并监听
if (!obj || typeof obj !== 'object') return
Object.keys(obj).forEach(key => this.defineReactive(vm, key, obj[key]))
}
defineReactive(obj, key, val) {//利用Object.defineProperty监听属性改变
var dep = new Dep()
Object.defineProperty(obj, key, {
get: function () {
if (Dep.target) {//添加订阅者watcher到主题对象Dep
dep.addSub(Dep.target)
}
return val
},
set: function (newVal) {
if (newVal === val) return
val = newVal
console.log(val)
//作为发布者发布通知
dep.notify()
}
})
}

}
class Dep {//收集订阅者的容器类
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => sub.update())
}
}

class Watcher {
constructor(vm, node, name, type) {
Dep.target = this
this.name = name
this.node = node
this.vm = vm
this.type = type
this.update()
Dep.target = null
}
update() {
this.get()
this.node[this.type] = this.value//订阅者执行响应操作
}
get() {
this.value = this.vm[this.name]//触发响应属性的get
}
}

var vm = new Vue({
el: 'app',
data: {
text: 'lyl',
title: 'hello world'
},
methods: {
clickMe() {
this.title = 'hello world'
}
},
mounted() {
setTimeout(() => {
this.title = '你好'
}, 1000);
}
})
</script>
</body>
</html>

GitHub地址:https://github.com/ChrisLuckComes/Vue2WayBind

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
javascript radio 联动效果
Mar 04 Javascript
Javascript的各种节点操作实例演示代码
Jun 27 Javascript
jQuery渐变发光导航菜单的实例代码
Mar 27 Javascript
Extjs4 Treegrid 使用心得分享(经验篇)
Jul 01 Javascript
js动态修改表格行colspan列跨度的方法
Mar 30 Javascript
js获取数组的最后一个元素
Apr 14 Javascript
微信小程序 欢迎界面开发的实例详解
Nov 30 Javascript
15个非常实用的JavaScript代码片段
Dec 18 Javascript
详解vue前后台数据交互vue-resource文档
Jul 19 Javascript
vue webpack打包优化操作技巧
Feb 22 Javascript
解决vue中修改了数据但视图无法更新的情况
Aug 27 Javascript
vue单页面实现当前页面刷新或跳转时提示保存
Nov 02 Javascript
JavaScript设计模式之观察者模式与发布订阅模式详解
May 07 #Javascript
微信小程序pinker组件使用实现自动相减日期
May 07 #Javascript
简单了解JavaScript弹窗实现代码
May 07 #Javascript
angular组件间传值测试的方法详解
May 07 #Javascript
Node.js API详解之 timer模块用法实例分析
May 07 #Javascript
JS面试题中深拷贝的实现讲解
May 07 #Javascript
javascript 代码是如何被压缩的示例代码
May 06 #Javascript
You might like
优化PHP程序的方法小结
2012/02/23 PHP
PHP数组排序函数合集 以及它们之间的联系分析
2013/06/27 PHP
Yii2使用自带的UploadedFile实现的文件上传
2016/06/20 PHP
PHP单例模式与工厂模式详解
2017/08/29 PHP
php实现生成PDF文件的方法示例【基于FPDF类库】
2018/07/21 PHP
Laravel 5.1 框架Blade模板引擎用法实例分析
2020/01/04 PHP
JS 的应用开发初探(mootools)
2009/12/19 Javascript
利用jquery操作select下拉列表框的代码
2010/06/04 Javascript
js字符串截取函数substr substring slice使用对比
2013/11/27 Javascript
jquery选择器之基本过滤选择器详解
2014/01/27 Javascript
JavaScript中的异常捕捉介绍
2014/12/31 Javascript
jquery控制显示服务器生成的图片流
2015/08/04 Javascript
jQuery实现的登录浮动框效果代码
2015/09/26 Javascript
创建自己的jquery表格插件
2015/11/25 Javascript
JS常见疑难点分析之match,charAt,charCodeAt,map,search用法分析
2016/12/25 Javascript
Bootstrap下拉菜单更改为悬停(hover)触发的方法
2017/05/24 Javascript
微信小程序之滚动视图容器的实现方法
2017/09/26 Javascript
微信小程序图片轮播组件gallery slider使用方法详解
2018/01/31 Javascript
在node环境下parse Smarty模板的使用示例代码
2019/11/15 Javascript
vue移动端弹起蒙层滑动禁止底部滑动操作
2020/07/22 Javascript
[13:55]Newbee vs Team Spirit
2018/06/07 DOTA
Python实现的飞速中文网小说下载脚本
2015/04/23 Python
Python3.5 创建文件的简单实例
2018/04/26 Python
Linux 修改Python命令的方法示例
2018/12/03 Python
Python计算时间间隔(精确到微妙)的代码实例
2019/02/26 Python
详解python中GPU版本的opencv常用方法介绍
2020/07/24 Python
python实现扫雷游戏的示例
2020/10/20 Python
全球度假村:Club Med
2017/11/27 全球购物
工会主席岗位责任制
2014/02/11 职场文书
高中军训感言500字
2014/02/24 职场文书
大学军训感言1500字
2014/03/09 职场文书
优秀班主任材料
2014/12/16 职场文书
详解盒子端CSS动画性能提升
2021/05/24 HTML / CSS
Spring Security中用JWT退出登录时遇到的坑
2021/10/16 Java/Android
PostgreSQL自动更新时间戳实例代码
2021/11/27 PostgreSQL
在Oracle表中进行关键词搜索的过程
2022/06/10 Oracle