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 相关文章推荐
音乐播放用的的几个函数
Sep 07 Javascript
setAttribute 与 class冲突解决
Feb 17 Javascript
ExtJS 学习专题(一) 如何应用ExtJS(附实例)
Mar 11 Javascript
jQuery实现设置、移除文本框默认值功能
Jan 13 Javascript
javascript获取select标签选中的值
Jun 04 Javascript
jQuery简单设置文本框回车事件的方法
Aug 01 Javascript
利用jquery正则表达式在页面验证url网址输入是否正确
Apr 04 jQuery
JavaScript寄生组合式继承实例详解
Jan 06 Javascript
详解在React中跨组件分发状态的三种方法
Aug 09 Javascript
JS实现根据数组对象的某一属性排序操作示例
Jan 14 Javascript
15 分钟掌握vue-next响应式原理
Oct 13 Javascript
一小时迅速入门Mybatis之bind与多数据源支持 Java API
Sep 15 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入门学习的几个不错的实例代码
2008/07/13 PHP
PHP实现货币换算的方法
2014/11/29 PHP
php实现高效获取图片尺寸的方法
2014/12/12 PHP
php实现的二叉树遍历算法示例
2017/06/15 PHP
Laravel 批量更新多条数据的示例
2017/11/27 PHP
PHP设计模式之模板方法模式实例浅析
2018/12/20 PHP
PHP常用正则表达式精选(推荐)
2019/05/28 PHP
js实现收缩菜单效果实例代码
2013/10/30 Javascript
Javascript中Date类型和Math类型详解
2016/02/27 Javascript
AngularJS基础 ng-class-odd 指令示例
2016/08/01 Javascript
jQuery实现拖动剪裁图片作为头像
2016/12/28 Javascript
js的OOP继承实现(必看篇)
2017/02/18 Javascript
AngularJS基于factory创建自定义服务的方法详解
2017/05/25 Javascript
Bootstrap响应式导航由768px变成992px的实现代码
2017/06/15 Javascript
jquery.validate表单验证插件使用详解
2017/06/21 jQuery
微信小程序自定义多选事件的实现代码
2018/05/17 Javascript
微信小程序中换行空格(多个空格)写法详解
2018/07/10 Javascript
Nuxt配合Node在实际生产中的应用详解
2018/08/07 Javascript
nodejs通过钉钉群机器人推送消息的实现代码
2019/05/05 NodeJs
vue开发简单上传图片功能
2020/06/30 Javascript
解决vue自定义指令导致的内存泄漏问题
2020/08/04 Javascript
[00:23]DOTA2群星共贺开放测试 25日无码时代来袭
2013/09/23 DOTA
[01:08:33]OG vs VGJ.T 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python配置文件解析模块ConfigParser使用实例
2015/04/13 Python
简单易懂的python环境安装教程
2017/07/13 Python
Python将DataFrame的某一列作为index的方法
2018/04/08 Python
python GUI库图形界面开发之PyQt5中QMainWindow, QWidget以及QDialog的区别和选择
2020/02/26 Python
Keras实现支持masking的Flatten层代码
2020/06/16 Python
Python 没有main函数的原因
2020/07/10 Python
马来西亚网上购物平台:ezbuy
2018/02/13 全球购物
幼儿园义卖活动方案
2014/01/17 职场文书
高一物理教学反思
2014/01/24 职场文书
高中生第一学年自我鉴定2015
2014/09/28 职场文书
运动会新闻报道稿
2015/07/22 职场文书
MySQL 百万级数据的4种查询优化方式
2021/06/07 MySQL
mysql创建存储过程及函数详解
2021/12/04 MySQL