本文实例讲述了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,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.defineProperty():方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13var 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实现双向绑定的核心
完整代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153<!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.uoften.com/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
最后
以上就是沉静秀发最近收集整理的关于Vue双向绑定实现原理与方法详解的全部内容,更多相关Vue双向绑定实现原理与方法详解内容请搜索靠谱客的其他文章。
发表评论 取消回复