原生Js实现Vue双向绑定

Vue数据双向数据绑定是通过发布者-订阅者模式来实现的,在正常使用Vue的时候,调用data里的属性是这样的

1
2
3
4
5
6
7
8
9
10
var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});

1596277495272

里面有一个set和get,这是使用了Object里面的defineProperty,我们新建一个空对象,使用一下这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
var Person = {};
var name = '';
Object.defineProperty(Person,'name',{
set: function(value){
console.log('我被设置了一个值叫:'+value);
name = value;
},
get: function(){
return '你的名字是'+name
}
})
Person.name = 'LJM';
console.log(Person.name)

1596277597282

1596277666179

可以看出,我们对defineProperty函数的set和get方法进行了重写,而且结构也和Vue实现的结构差不多,所以Vue是通过defineProperty进行数据劫持的。

实现过程

  1. 实现一个监听者observe,用来劫持并监听所有的属性,如果有数据变动,就通知所有的订阅者
  2. 实现一个订阅者Watcher,收到属性变化通知后执行相应的函数,从而实现更新目的

实现一个observe

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
function defineReactive(data,key,value){
observe(value);//遍历所有字属性
var dep = new Dep(); //添加订阅者
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
set(newVal){
//新值旧值一样直接返回
if (value === newVal) {
return;
}
value = newVal;

console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
//所有订阅者更新
dep.notify();
},
get(){

if (Dep.target) {
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return value;
}
})

}
Dep.target = null;
function observe(data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
})
}

实现订阅者

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
function Dep () {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
//实现Watcher
function Watcher(vm, exp, cb) {
this.vm = vm;
//需要监听的字段名字
this.exp = exp;
this.cb = cb;
this.value = this.get() // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update(){
this.run();
},
run(){
let value = this.vm.data[this.exp]
let oldValue = this.value;
if(value !== oldValue){
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
},
get(){
Dep.target = this;//缓存
let value = this.vm.data[this.exp];
Dep.target = null;//释放
return value;
}
}

把监听者和订阅者相关联

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
function SelfVue (data, el, exp) {
var self = this;
this.data = data;

Object.keys(data).forEach(function(key) {
self.proxyKeys(key); // 绑定代理属性
});

observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}

SelfVue.prototype = {
proxyKeys: function (key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function proxyGetter() {
return self.data[key];
},
set: function proxySetter(newVal) {
self.data[key] = newVal;
}
});
}
}

实现变换

1
2
3
4
5
6
7
8
9
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');

window.setTimeout(function () {
console.log('name值改变了');
selfVue.name = 'canfoo';
}, 2000);

整个的代码

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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向绑定</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>

<h1 id="name">{ { name } }</h1>

<script>

var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});
var person = {
name: 'LJM'
}
console.log(person.name)
var Person = {};
var name = '';
Object.defineProperty(Person,'name',{
set: function(value){
console.log('我被设置了一个值叫:'+value);
name = value;
},
get: function(){
return '你的名字是'+name
}
})
Person.name = 'LJM';
console.log(Person.name)
//JS实现Vue
function defineReactive(data,key,value){
observe(value);//遍历所有字属性
var dep = new Dep(); //添加订阅者
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
set(newVal){
//新值旧值一样直接返回
if (value === newVal) {
return;
}
value = newVal;

console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
//所有订阅者更新
dep.notify();
},
get(){

if (Dep.target) {
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return value;
}
})

}
Dep.target = null;
function observe(data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
})
}
var library = {
book1: {
name: ''
},
book2: ''
};
// observe(library);
// library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
// library.book2 = '没有此书籍'; // 属性book2已经被监听了,现在值为:“没有此书籍”
//植入订阅器
function Dep () {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
//实现Watcher
function Watcher(vm, exp, cb) {
this.vm = vm;
//需要监听的字段名字
this.exp = exp;
this.cb = cb;
this.value = this.get() // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update(){
this.run();
},
run(){
let value = this.vm.data[this.exp]
let oldValue = this.value;
if(value !== oldValue){
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
},
get(){
Dep.target = this;//缓存
let value = this.vm.data[this.exp];
Dep.target = null;//释放
return value;
}
}
//把observe和watcher关联
function SelfVue (data, el, exp) {
var self = this;
this.data = data;

Object.keys(data).forEach(function(key) {
self.proxyKeys(key); // 绑定代理属性
});

observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}

SelfVue.prototype = {
proxyKeys: function (key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function proxyGetter() {
return self.data[key];
},
set: function proxySetter(newVal) {
self.data[key] = newVal;
}
});
}
}
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');

window.setTimeout(function () {
console.log('name值改变了');
selfVue.name = 'canfoo';
}, 2000);
</script>
</body>
</html>

1596278186597

1596278197253

确实在两秒后变换了数据,但是现在还没有解析器去解析{ { } }是直接利用innerHtml来替换里面的所有数据

所以,还要加一个Compile模板解析器

实现Compile
  1. 解析模板指令,替换数据,初始化视图

  2. 对应的节点绑定对应的更新函数,初始化相应的订阅器

    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
    class Compile  {
    constructor(el, vm){
    this.vm = vm;
    this.el = document.querySelector(el.el);
    this.fragment = null;
    this.init();
    }
    init() {
    if (this.el) {
    this.fragment = this.nodeToFragment(this.el);
    this.compileElement(this.fragment);
    this.el.appendChild(this.fragment);
    } else {
    console.log('Dom元素不存在');
    }
    }
    nodeToFragment (el) {
    var fragment = document.createDocumentFragment();
    var child = el.firstChild;
    while (child) {
    // 将Dom元素移入fragment中
    fragment.appendChild(child);
    child = el.firstChild
    }
    return fragment;
    }
    compileElement(el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
    var reg = /\{\{(.*)\}\}/;
    var text = node.textContent;

    if (self.isElementNode(node)) {
    self.compile(node);
    } else if (self.isTextNode(node) && reg.test(text)) {
    self.compileText(node, reg.exec(text)[1]);
    }

    if (node.childNodes && node.childNodes.length) {
    self.compileElement(node);
    }
    });
    }
    compile(node) {
    var nodeAttrs = node.attributes;
    var self = this;
    Array.prototype.forEach.call(nodeAttrs, function(attr) {
    var attrName = attr.name;
    if (self.isDirective(attrName)) {
    var exp = attr.value;
    var dir = attrName.substring(2);
    if (self.isEventDirective(dir)) { // 事件指令
    self.compileEvent(node, self.vm, exp, dir);
    } else { // v-model 指令
    self.compileModel(node, self.vm, exp, dir);
    }
    node.removeAttribute(attrName);
    }
    });
    }
    compileText(node, exp) {
    var self = this;
    var initText = this.vm[exp];
    this.updateText(node, initText);
    new Watcher(this.vm, exp, function (value) {
    self.updateText(node, value);
    });
    }
    compileEvent(node, vm, exp, dir) {
    var eventType = dir.split(':')[1];
    var cb = vm.methods && vm.methods[exp];

    if (eventType && cb) {
    node.addEventListener(eventType, cb.bind(vm), false);
    }
    }
    compileModel(node, vm, exp, dir) {
    var self = this;
    var val = this.vm[exp];
    this.modelUpdater(node, val);
    new Watcher(this.vm, exp, function (value) {
    self.modelUpdater(node, value);
    });

    node.addEventListener('input', function(e) {
    var newValue = e.target.value;
    if (val === newValue) {
    return;
    }
    self.vm[exp] = newValue;
    val = newValue;
    });
    }
    updateText (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
    }
    modelUpdater(node, value, oldValue) {
    node.value = typeof value == 'undefined' ? '' : value;
    }
    isDirective(attr) {
    return attr.indexOf('v-') == 0;
    }
    isEventDirective(dir) {
    return dir.indexOf('on:') === 0;
    }
    isElementNode (node) {
    return node.nodeType == 1;
    }
    isTextNode(node) {
    return node.nodeType == 3;
    }
    }
整合
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
class SelfVue{
constructor(options){
var self = this;
this.data = options.data;
this.methods = options.methods;
this.vm = this;
Object.keys(this.data).forEach(function(key) {
self.proxyKeys(key); // 绑定代理属性
});

new observe(this.data);
new Compile(options, this.vm);
options.mounted.call(this); // 所有事情处理好后执行mounted函数
}
proxyKeys(key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function getter () {
return self.data[key];
},
set: function setter (newVal) {
self.data[key] = newVal;
}
});
}
}


class observe{
constructor(data){
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
})
}
defineReactive(data,key,value){
new observe(value);//遍历所有字属性
var dep = new Dep(); //添加订阅者
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
set(newVal){
//新值旧值一样直接返回
if (value === newVal) {
return;
}
value = newVal;

console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
//所有订阅者更新
dep.notify();
},
get(){

if (Dep.target) {
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return value;
}
})
}
}
class Dep{
constructor(){
this.subs = [];
this.target = null;
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(function(sub) {
sub.update();
});
}
}
class Watcher{
constructor(vm, exp, cb){
this.vm = vm;
//需要监听的字段名字
this.exp = exp;
this.cb = cb;
this.value = this.get() // 将自己添加到订阅器的操作
}
update(){
this.run();
}
run(){
let value = this.vm.data[this.exp]
let oldValue = this.value;
if(value !== oldValue){
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
}
get(){
Dep.target = this;//缓存
let value = this.vm.data[this.exp];
Dep.target = null;//释放
return value;
}
}
class Compile {
constructor(el, vm){
this.vm = vm;
this.el = document.querySelector(el.el);
this.fragment = null;
this.init();
}
init() {
if (this.el) {
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
} else {
console.log('Dom元素不存在');
}
}
nodeToFragment (el) {
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while (child) {
// 将Dom元素移入fragment中
fragment.appendChild(child);
child = el.firstChild
}
return fragment;
}
compileElement(el) {
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;

if (self.isElementNode(node)) {
self.compile(node);
} else if (self.isTextNode(node) && reg.test(text)) {
self.compileText(node, reg.exec(text)[1]);
}

if (node.childNodes && node.childNodes.length) {
self.compileElement(node);
}
});
}
compile(node) {
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
var attrName = attr.name;
if (self.isDirective(attrName)) {
var exp = attr.value;
var dir = attrName.substring(2);
if (self.isEventDirective(dir)) { // 事件指令
self.compileEvent(node, self.vm, exp, dir);
} else { // v-model 指令
self.compileModel(node, self.vm, exp, dir);
}
node.removeAttribute(attrName);
}
});
}
compileText(node, exp) {
var self = this;
var initText = this.vm[exp];
this.updateText(node, initText);
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
}
compileEvent(node, vm, exp, dir) {
var eventType = dir.split(':')[1];
var cb = vm.methods && vm.methods[exp];

if (eventType && cb) {
node.addEventListener(eventType, cb.bind(vm), false);
}
}
compileModel(node, vm, exp, dir) {
var self = this;
var val = this.vm[exp];
this.modelUpdater(node, val);
new Watcher(this.vm, exp, function (value) {
self.modelUpdater(node, value);
});

node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
self.vm[exp] = newValue;
val = newValue;
});
}
updateText (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
modelUpdater(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
isDirective(attr) {
return attr.indexOf('v-') == 0;
}
isEventDirective(dir) {
return dir.indexOf('on:') === 0;
}
isElementNode (node) {
return node.nodeType == 1;
}
isTextNode(node) {
return node.nodeType == 3;
}
}
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
<div id="app">
<h2>{ { title } }</h2>
<input v-model="name">
<h1>{ { name } }</h1>
<button v-on:click="clickMe">click me!</button>
</div>
<script src="js/vueBind.js"></script>
<script>
var selfVue = new SelfVue({
el: '#app',
data: {
title: '您好',
name: 'canfoo'
},
methods: {
clickMe: function () {
this.title = 'hello world';
}
},
mounted: function () {
window.setTimeout(() => {
this.title = '你好';
}, 1000);
}
});
</script>

1596288579053