双向数据绑定无非就是,视图 => 数据,数据 => 视图的更新过程
以下的方案中的实现思路:
- 定义一个Vue的构造函数并初始化这个函数(myVue.prototype._init)
- 实现数据层的更新:数据劫持,定义一个 obverse 函数重写data的set和get(myVue.prototype._obsever)
- 实现视图层的更新:订阅者模式,定义个 Watcher 函数实现对DOM的更新(Watcher)
- 将数据和视图层进行绑定,解析指令v-bind、v-model、v-click(myVue.prototype._compile)
- 创建Vue实例(new myVue)
1.object.defineproperty方式实现双向数据绑定
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
|
<!DOCTYPE html> <html> <head> <title>myVue</title> <style> #app{ text-align: center; } </style> </head> <body> <div id= "app" > <form> <input type= "text" v-model= "number" /> <button type= "button" v-click= "increment" >增加</button> </form> <h3 v-bind= "number" ></h3> </div> </body> <script> // 定义一个myVue构造函数 function myVue(option) { this ._init(option) } myVue.prototype._init = function (options) { // 传了一个配置对象 this .$options = options // options 为上面使用时传入的结构体,包括el,data,methods this .$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素 this .$data = options.data // this.$data = {number: 0} this .$methods = options.methods // this.$methods = {increment: function(){}} // _binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新 this ._binding = {} this ._obsever( this .$data) this ._compile( this .$el) } // 数据劫持:更新数据 myVue.prototype._obsever = function (obj) { let _this = this Object.keys(obj).forEach((key) => { // 遍历obj对象 if (obj.hasOwnProperty(key)) { // 判断 obj 对象是否包含 key属性 _this._binding[key] = [] // 按照前面的数据,_binding = {number: []} 存储 每一个 new Watcher } let value = obj[key] if ( typeof value === 'object' ) { //如果值还是对象,则遍历处理 _this._obsever(value) } Object.defineProperty(_this.$data, key, { enumerable: true , configurable: true , get: () => { // 获取 value 值 return value }, set: (newVal) => { // 更新 value 值 if (value !== newVal) { value = newVal _this._binding[key].forEach((item) => { // 当number改变时,触发_binding[number] 中的绑定的Watcher类的更新 item.update() // 调 Watcher 实例的 update 方法更新 DOM }) } } }) }) } // 订阅者模式: 绑定更新函数,实现对 DOM 元素的更新 function Watcher(el, data, key, attr) { this .el = el // 指令对应的DOM元素 this .data = data // this.$data 数据: {number: 0, count: 0} this .key = key // 指令绑定的值,本例如"number" this .attr = attr // 绑定的属性值,本例为"innerHTML","value" this .update() } // 比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新 Watcher.prototype.update = function () { this .el[ this .attr] = this .data[ this .key] } // 将view与model进行绑定,解析指令(v-bind,v-model,v-clickde)等 myVue.prototype._compile = function (el) { // root 为id为app的Element元素,也就是我们的根元素 let _this = this let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组 nodes.map(node => { if (node.children.length && node.children.length > 0) { // 对所有元素进行遍历,并进行处理 _this._compile(node) } if (node.hasAttribute( 'v-click' )) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++ let attrVal = node.getAttribute( 'v-click' ) node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致 } // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件 if (node.hasAttribute( 'v-model' ) && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' )) { let attrVal = node.getAttribute( 'v-model' ) _this._binding[attrVal].push( new Watcher( node, // 对应的 DOM 节点 _this.$data, attrVal, // v-model 绑定的值 'value' )) node.addEventListener( 'input' , () => { _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定 }) } if (node.hasAttribute( 'v-bind' )) { let attrVal = node.getAttribute( 'v-bind' ) _this._binding[attrVal].push( new Watcher( node, _this.$data, attrVal, // v-bind 绑定的值 'innerHTML' )) } }) } window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况 new myVue({ el: '#app' , data: { number: 0, count: 0 }, methods: { increment() { this .number++ }, incre() { this .count++ } } }) } </script> </html> |
2.Proxy 实现双向数据绑定
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
|
<!DOCTYPE html> <html> <head> <title>myVue</title> <style> #app{ text-align: center; } </style> </head> <body> <div id= "app" > <form> <input type= "text" v-model= "number" /> <button type= "button" v-click= "increment" >增加</button> </form> <h3 v-bind= "number" ></h3> </div> </body> <script> // 定义一个myVue构造函数 function myVue(option) { this ._init(option) } myVue.prototype._init = function (options) { // 传了一个配置对象 this .$options = options // options 为上面使用时传入的结构体,包括el,data,methods this .$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素 this .$data = options.data // this.$data = {number: 0} this .$methods = options.methods // this.$methods = {increment: function(){}} this ._binding = {} this ._obsever( this .$data) this ._complie( this .$el) } // 数据劫持:更新数据 myVue.prototype._obsever = function (data) { let _this = this let handler = { get(target, key) { return target[key]; // 获取该对象上key的值 }, set(target, key, newValue) { let res = Reflect.set(target, key, newValue); // 将新值分配给属性的函数 _this._binding[key].map(item => { item.update(); }); return res; } }; // 把代理器返回的对象代理到this.$data,即this.$data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法 this .$data = new Proxy(data, handler); } // 将view与model进行绑定,解析指令(v-bind,v-model,v-clickde)等 myVue.prototype._complie = function (el) { // el 为id为app的Element元素,也就是我们的根元素 let _this = this let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组 nodes.map(node => { if (node.children.length && node.children.length > 0) this ._complie(node) if (node.hasAttribute( 'v-click' )) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++ let attrVal = node.getAttribute( 'v-click' ) node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致 } // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件 if (node.hasAttribute( 'v-model' ) && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' )) { let attrVal = node.getAttribute( 'v-model' ) console.log(_this._binding) if (!_this._binding[attrVal]) _this._binding[attrVal] = [] _this._binding[attrVal].push( new Watcher( node, // 对应的 DOM 节点 _this.$data, attrVal, // v-model 绑定的值 'value' , )) node.addEventListener( 'input' , () => { _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定 }) } if (node.hasAttribute( 'v-bind' )) { let attrVal = node.getAttribute( 'v-bind' ) if (!_this._binding[attrVal]) _this._binding[attrVal] = [] _this._binding[attrVal].push( new Watcher( node, _this.$data, attrVal, // v-bind 绑定的值 'innerHTML' , )) } }) } // 绑定更新函数,实现对 DOM 元素的更新 function Watcher(el, data, key, attr) { this .el = el // 指令对应的DOM元素 this .data = data // 代理的对象 this.$data 数据: {number: 0, count: 0} this .key = key // 指令绑定的值,本例如"num" this .attr = attr // 绑定的属性值,本例为"innerHTML","value" this .update() } // 比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新 Watcher.prototype.update = function () { this .el[ this .attr] = this .data[ this .key] } window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况 new myVue({ el: '#app' , data: { number: 0, count: 0 }, methods: { increment() { this .number++ }, incre() { this .count++ } } }) } </script> </html> |
3.将上面代码改成class的写法
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
|
<!DOCTYPE html> <html> <head> <title>myVue</title> <style> #app{ text-align: center; } </style> </head> <body> <div id= "app" > <form> <input type= "text" v-model= "number" /> <button type= "button" v-click= "increment" >增加</button> </form> <h3 v-bind= "number" ></h3> </div> </body> <script> class MyVue { constructor(options) { // 接收了一个配置对象 this .$options = options // options 为上面使用时传入的结构体,包括el,data,methods this .$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素 this .$data = options.data // this.$data = {number: 0} this .$methods = options.methods // this.$methods = {increment: function(){}} this ._binding = {} this ._obsever( this .$data) this ._complie( this .$el) } _obsever (data) { // 数据劫持:更新数据 let _this = this let handler = { get(target, key) { return target[key]; // 获取该对象上key的值 }, set(target, key, newValue) { let res = Reflect.set(target, key, newValue); // 将新值分配给属性的函数 _this._binding[key].map(item => { item.update(); }); return res; } }; // 把代理器返回的对象代理到this.$data,即this.$data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法 this .$data = new Proxy(data, handler); } _complie(el) { // el 为id为app的Element元素,也就是我们的根元素 let _this = this let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组 nodes.map(node => { if (node.children.length && node.children.length > 0) this ._complie(node) if (node.hasAttribute( 'v-click' )) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++ let attrVal = node.getAttribute( 'v-click' ) node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致 } // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件 if (node.hasAttribute( 'v-model' ) && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' )) { let attrVal = node.getAttribute( 'v-model' ) if (!_this._binding[attrVal]) _this._binding[attrVal] = [] _this._binding[attrVal].push( new Watcher( node, // 对应的 DOM 节点 _this.$data, attrVal, // v-model 绑定的值 'value' , )) node.addEventListener( 'input' , () => { _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定 }) } if (node.hasAttribute( 'v-bind' )) { let attrVal = node.getAttribute( 'v-bind' ) if (!_this._binding[attrVal]) _this._binding[attrVal] = [] _this._binding[attrVal].push( new Watcher( node, _this.$data, attrVal, // v-bind 绑定的值 'innerHTML' , )) } }) } } class Watcher { constructor (el, data, key, attr) { this .el = el // 指令对应的DOM元素 this .data = data // 代理的对象 this.$data 数据: {number: 0, count: 0} this .key = key // 指令绑定的值,本例如"num" this .attr = attr // 绑定的属性值,本例为"innerHTML","value" this .update() } update () { this .el[ this .attr] = this .data[ this .key] } } window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况 new MyVue({ el: '#app' , data: { number: 0, count: 0 }, methods: { increment() { this .number++ }, incre() { this .count++ } } }) } </script> </html> |
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.cnblogs.com/vickylinj/p/13378034.html