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 相关文章推荐
jQuery 研究心得 取得属性的值
Nov 30 Javascript
JavaScript DOM 学习第二章 编辑文本
Feb 19 Javascript
javascript放大镜效果的简单实现
Dec 09 Javascript
jquery如何通过name名称获取当前name的value值
Dec 20 Javascript
教你用AngularJS框架一行JS代码实现控件验证效果
Jun 23 Javascript
js实时获取窗口大小变化的实例代码
Nov 18 Javascript
JS中使用media实现响应式布局
Aug 04 Javascript
微信小程序wx.request实现后台数据交互功能分析
Nov 25 Javascript
animate.css在vue项目中的使用教程
Aug 05 Javascript
vue项目中在外部js文件中直接调用vue实例的方法比如说this
Apr 28 Javascript
JavaScript布尔运算符原理使用解析
May 06 Javascript
JavaScript继承的三种方法实例
May 12 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
PL-880隐藏功能
2021/03/01 无线电
php快速url重写 更新版[需php 5.30以上]
2010/04/20 PHP
经典PHP加密解密函数Authcode()修复版代码
2015/04/05 PHP
php使用Imagick生成图片的方法
2015/07/31 PHP
thinkphp3.x中display方法及show方法的用法实例
2016/05/19 PHP
详解PHP多个进程配合redis的有序集合实现大文件去重
2019/03/06 PHP
Prototype 工具函数 学习
2009/07/23 Javascript
jQuery 学习第六课 实现一个Ajax的TreeView
2010/05/17 Javascript
jQuery通过控制节点实现仅在前台通过get方法完成参数传递
2015/02/02 Javascript
jquery实现select下拉框美化特效代码分享
2015/08/18 Javascript
JavaScript简单生成 N~M 之间随机数的方法
2017/01/13 Javascript
详解jQuery事件
2017/01/13 Javascript
微信小程序picker组件下拉框选择input输入框的实例
2017/09/20 Javascript
javascript 判断用户有没有操作页面
2017/10/17 Javascript
微信小程序出现wx.navigateTo页面不跳转问题的解决方法
2017/12/26 Javascript
Element中的Cascader(级联列表)动态加载省\市\区数据的方法
2019/03/27 Javascript
javascript设计模式 ? 单例模式原理与应用实例分析
2020/04/09 Javascript
Openlayers绘制聚合标注
2020/09/28 Javascript
[47:12]TFT vs Secret Supermajor小组赛C组 BO3 第三场 6.3
2018/06/04 DOTA
[43:26]完美世界DOTA2联赛PWL S2 Forest vs Rebirth 第二场 11.20
2020/11/23 DOTA
[07:25]DOTA2-DPC中国联赛2月5日Recap集锦
2021/03/11 DOTA
Python实现telnet服务器的方法
2015/07/10 Python
Python微信企业号开发之回调模式接收微信端客户端发送消息及被动返回消息示例
2017/08/21 Python
Python验证文件是否可读写代码分享
2017/12/11 Python
python+rsync精确同步指定格式文件
2019/08/29 Python
Python range、enumerate和zip函数用法详解
2019/09/11 Python
Python类super()及私有属性原理解析
2020/06/15 Python
使用Python提取文本中含有特定字符串的方法示例
2020/12/09 Python
资金主管岗位职责范本
2014/03/04 职场文书
幼儿园清明节活动总结
2014/07/04 职场文书
市政工程技术专业自荐书
2014/07/06 职场文书
2015年副班长工作总结
2015/05/15 职场文书
团组织推荐意见
2015/06/05 职场文书
用JS实现飞机大战小游戏
2021/06/09 Javascript
详解Java实践之建造者模式
2021/06/18 Java/Android
MySQL中的全表扫描和索引树扫描
2022/05/15 MySQL