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(jquery)利用函数修改全局变量的代码
Nov 02 Javascript
animate动画示例(泪奔的小孩)及stop和delay的使用
May 06 Javascript
JS和JQUERY获取页面大小,滚动条位置,元素位置(示例代码)
Dec 14 Javascript
JQuery中绑定事件(bind())和移除事件(unbind())
Feb 27 Javascript
JS实现文件动态顺序载入的方法
Mar 07 Javascript
动态加载js、css的简单实现代码
May 26 Javascript
jQuery实现拖拽页面元素并将其保存到cookie的方法
Jun 12 Javascript
jQuery中页面返回顶部的方法总结
Dec 30 Javascript
完美实现js选项卡切换效果(一)
Mar 08 Javascript
详解Vue-cli 创建的项目如何跨域请求
May 18 Javascript
jquery实现简单实用的轮播器
May 23 jQuery
vue视图不更新情况详解
May 16 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实现的一个很好用HTML解析器类可用于采集数据
2013/09/23 PHP
php array_map使用自定义的函数处理数组中的每个值
2016/10/26 PHP
php中array_fill函数的实例用法
2021/03/02 PHP
JQuery 风格的HTML文本转义
2009/07/01 Javascript
jsPDF导出pdf示例
2014/05/02 Javascript
对JavaScript中this指针的新理解分享
2015/01/31 Javascript
JQuery实现鼠标滚轮滑动到页面节点
2015/07/28 Javascript
JS Canvas定时器模拟动态加载动画
2016/09/17 Javascript
HTML5 JS压缩图片并获取图片BASE64编码上传
2020/11/16 Javascript
jQuery 选择符详细介绍及整理
2016/12/02 Javascript
vue2.0父子组件间通信的实现方法
2017/04/19 Javascript
gulp安装以及打包合并的方法教程
2017/11/19 Javascript
深入理解node.js http模块
2018/01/24 Javascript
jQuery+ajax实现动态添加表格tr td功能示例
2018/04/23 jQuery
微信小程序实现倒计时补零功能
2018/07/09 Javascript
解决vue组件props传值对象获取不到的问题
2019/06/06 Javascript
json_decode 索引为数字时自动排序问题解决方法
2020/03/28 Javascript
jQuery+ThinkPHP实现图片上传
2020/07/23 jQuery
vue动态加载SVG文件并修改节点数据的操作代码
2020/08/17 Javascript
Python使用PIL库实现验证码图片的方法
2016/03/11 Python
python构建指数平滑预测模型示例
2019/11/21 Python
python实现飞机大战游戏(pygame版)
2020/10/26 Python
Python实现井字棋小游戏
2020/03/09 Python
python 实现仿微信聊天时间格式化显示的代码
2020/04/17 Python
python代码能做成软件吗
2020/07/24 Python
使用Django的JsonResponse返回数据的实现
2021/01/15 Python
CSS3只让背景图片旋转180度的实现示例
2021/03/09 HTML / CSS
学习型党组织建设经验材料
2014/05/26 职场文书
视光学专业自荐信
2014/06/24 职场文书
写给同学的新学期寄语
2015/02/27 职场文书
2015年国税春训心得体会
2015/03/09 职场文书
应急管理工作总结2015
2015/05/04 职场文书
工作调动申请报告
2015/05/18 职场文书
红与黑读书笔记
2015/06/29 职场文书
公司保洁员管理制度
2015/08/04 职场文书
MySQL系列之一 MariaDB-server安装
2021/07/02 MySQL