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 addBookmark 加入收藏 多浏览器兼容
Aug 15 Javascript
Javascript 面向对象 重载
May 13 Javascript
jQuery学习笔记之jQuery动画效果
Sep 09 Javascript
jQuery extend 的简单实例
Sep 18 Javascript
Jquery.Form 异步提交表单的简单实例
Mar 03 Javascript
JS+CSS实现滑动切换tab菜单效果
Aug 25 Javascript
Node.js实用代码段之获取Buffer对象字节长度
Mar 17 Javascript
js判断checkbox是否选中个数的方法(超简单)
Aug 19 Javascript
jQuery使用$获取对象后检查该对象是否存在的实现方法
Sep 04 Javascript
微信小程序 图片加载(本地,网路)实例详解
Mar 10 Javascript
JavaScript输入分钟、秒倒计时技巧总结(附代码)
Aug 17 Javascript
SSM+layUI 根据登录信息显示不同的页面方法
Sep 20 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 购物车实例(申精)
2009/05/11 PHP
php中flush()、ob_flush()、ob_end_flush()的区别介绍
2013/02/17 PHP
php下载文件,添加响应头的简单实例
2016/09/22 PHP
详解配置 Apache 服务器支持 PHP 文件的解析
2017/02/15 PHP
PHP如何防止XSS攻击与XSS攻击原理的讲解
2019/03/22 PHP
jQuery实现的点赞随机数字显示动画效果(附在线演示与demo源码下载)
2015/12/31 Javascript
jQuery获取父元素节点、子元素节点及兄弟元素节点的方法
2016/04/14 Javascript
jQuery EasyUI菜单与按钮详解
2016/07/13 Javascript
JavaScript闭包和范围实例详解
2016/12/19 Javascript
利用Javascript实现简单的转盘抽奖
2017/02/13 Javascript
JS中使用 after 伪类清除浮动实例
2017/03/01 Javascript
基于JavaScript实现验证码功能
2017/04/01 Javascript
nodejs项目windows下开机自启动的方法
2017/11/22 NodeJs
Js Snowflake(雪花算法)生成随机ID的实现方法
2020/08/26 Javascript
[20:30]职业巡回赛回顾
2018/08/09 DOTA
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
python进阶教程之循环对象
2014/08/30 Python
Python的for和break循环结构中使用else语句的技巧
2016/05/24 Python
通过python+selenium3实现浏览器刷简书文章阅读量
2017/12/26 Python
python学生信息管理系统
2018/03/13 Python
Django缓存系统实现过程解析
2019/08/02 Python
python3应用windows api对后台程序窗口及桌面截图并保存的方法
2019/08/27 Python
Python爬取365好书中小说代码实例
2020/02/28 Python
Python面向对象程序设计之静态方法、类方法、属性方法原理与用法分析
2020/03/23 Python
使用Pycharm(Python工具)新建项目及创建Python文件的教程
2020/04/26 Python
Django Form常用功能及代码示例
2020/10/13 Python
CSS3实现淘宝留白的方法
2020/06/05 HTML / CSS
日本航空官方网站:JAL
2019/06/19 全球购物
写一个函数,要求输入一个字符串和一个字符长度,对该字符串进行分隔
2015/07/30 面试题
营销学习心得体会
2014/09/12 职场文书
庆祝国庆节演讲稿2014
2014/09/19 职场文书
医院领导班子四风对照检查材料
2014/09/27 职场文书
党员教师四风自我剖析材料
2014/09/30 职场文书
个人政风行风自查自纠报告
2014/10/21 职场文书
2015年感恩母亲节的演讲稿
2015/03/18 职场文书
python开发实时可视化仪表盘的示例
2021/05/07 Python