引用:滴滴商业FED
Vue源码解读-数据的双向绑定简陋版(仅用于原理说明)

可运行 + 可调试

滴滴源码中
import & export 没法在浏览器运行啊,有没有!!!
用vue-cli编译,Chrome没法打断点啊,有没有!!!

鉴于这种情况,为了让代码 可运行 + 可调试 ,只好 手动 把代码改成了浏览器可用代码,666666!!!

说明:

1
2
3
4
5
demo.$watch('c', () => console.log('c is changed'))
为了打断点,改为:
demo.$watch('c', () => {
console.log('c is changed')
})

html => line 25
app.js => line 208 & line 261 & line 272
打断点,可以愉快而且直观地看到代码的运行了。666666!!!

index.html

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="app.js"></script>
<script>
var Vue = require('Vue');
let demo = new Vue({
data: {
'a': {
'ab': {
'c': 'C'
}
},
'b': {
'bb': 'BB'
},
'c': 'C'
}
});
demo.$watch('c', () => {
console.log('c is changed')
})
// get value
demo.c = 'CCC'
// new value seted
// get value
// c is changed
demo.c = 'DDD'
// new value seted
// get value
// c is changed
demo.a
// get value
demo.a.ab = {
'd': 'D'
}
// get value
// get value
// new value seted
console.log(demo.a.ab)
// get value
// get value
// {get d: (), set d: ()}
demo.a.ab.d = 'DD'
// get value
// get value
// new value seted
console.log(demo.a.ab);
// get value
// get value
// {get d: (), set d: ()}
</script>
</body>
</html>

app.js

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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/**
* Require the given path.
*
* @param {String} path
* @return {Object} exports
* @api public
*/

function require(path, parent, orig) {
var resolved = require.resolve(path);

// lookup failed
if (null == resolved) {
orig = orig || path;
parent = parent || 'root';
var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
err.path = orig;
err.parent = parent;
err.require = true;
throw err;
}

var module = require.modules[resolved];

// perform real require()
// by invoking the module's
// registered function
if (!module._resolving && !module.exports) {
var mod = {};
mod.exports = {};
mod.client = mod.component = true;
module._resolving = true;
module.call(this, mod.exports, require.relative(resolved), mod);
delete module._resolving;
module.exports = mod.exports;
}

return module.exports;
}

/**
* Registered modules.
*/

require.modules = {};

/**
* Registered aliases.
*/

require.aliases = {};

/**
* Resolve `path`.
*
* Lookup:
*
* - PATH/index.js
* - PATH.js
* - PATH
*
* @param {String} path
* @return {String} path or null
* @api private
*/

require.resolve = function (path) {
if (path.charAt(0) === '/') path = path.slice(1);

var paths = [
path,
path + '.js',
path + '.json',
path + '/index.js',
path + '/index.json'
];

for (var i = 0; i < paths.length; i++) {
var path = paths[i];
if (require.modules.hasOwnProperty(path)) return path;
if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
}
};

/**
* Normalize `path` relative to the current path.
*
* @param {String} curr
* @param {String} path
* @return {String}
* @api private
*/

require.normalize = function (curr, path) {
var segs = [];

if ('.' != path.charAt(0)) return path;

curr = curr.split('/');
path = path.split('/');

for (var i = 0; i < path.length; ++i) {
if ('..' == path[i]) {
curr.pop();
} else if ('.' != path[i] && '' != path[i]) {
segs.push(path[i]);
}
}

return curr.concat(segs).join('/');
};

/**
* Register module at `path` with callback `definition`.
*
* @param {String} path
* @param {Function} definition
* @api private
*/

require.register = function (path, definition) {
require.modules[path] = definition;
};

/**
* Alias a module definition.
*
* @param {String} from
* @param {String} to
* @api private
*/

require.alias = function (from, to) {
if (!require.modules.hasOwnProperty(from)) {
throw new Error('Failed to alias "' + from + '", it does not exist');
}
require.aliases[to] = from;
};

/**
* Return a require function relative to the `parent` path.
*
* @param {String} parent
* @return {Function}
* @api private
*/

require.relative = function (parent) {
var p = require.normalize(parent, '..');

/**
* lastIndexOf helper.
*/

function lastIndexOf(arr, obj) {
var i = arr.length;
while (i--) {
if (arr[i] === obj) return i;
}
return -1;
}

/**
* The relative require() itself.
*/

function localRequire(path) {
var resolved = localRequire.resolve(path);
return require(resolved, parent, path);
}

/**
* Resolve relative to the parent.
*/

localRequire.resolve = function (path) {
var c = path.charAt(0);
if ('/' == c) return path.slice(1);
if ('.' == c) return require.normalize(p, path);

// resolve deps by returning
// the dep in the nearest "deps"
// directory
var segs = parent.split('/');
var i = lastIndexOf(segs, 'deps') + 1;
if (!i) i = 0;
path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
return path;
};

/**
* Check if module is defined at `path`.
*/

localRequire.exists = function (path) {
return require.modules.hasOwnProperty(localRequire.resolve(path));
};

return localRequire;
};
require.register("Vue/src/main.js", function (exports, require, module) {
var observe = require('./Observer');
var Watcher = require('./Watcher');

function Vue(options = {}) {

// 简化了$options的处理
this.$options = options
// 简化了对data的处理
let data = this._data = this.$options.data
// 将所有data最外层属性代理到Vue实例上
Object.keys(data).forEach(key => {
this._proxy(key)
})
// 监听数据
observe(data)
}

// 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
Vue.prototype.$watch = function (expOrFn, cb) {
new Watcher(this, expOrFn, cb)
}
Vue.prototype._proxy = function (key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => this._data[key],
set: (val) => {
this._data[key] = val
}
})
}

module.exports = Vue
});
require.register("Vue/src/Observer.js", function (exports, require, module) {
// Dep用于订阅者的存储和收集,将在下面实现
var Dep = require('./Dep');

// Observer类用于给data属性添加set&get方法
class Observer{
constructor(value){
this.value = value
this.walk(value)
}
walk(value){
Object.keys(value).forEach(key => this.convert(key, value[key]))
}
convert(key, val){
defineReactive(this.value, key, val)
}
}
function defineReactive(obj, key, val){
var dep = new Dep()
// 给当前属性的值添加监听
var chlidOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=> {
console.log('get value')
// 如果Dep类存在target属性,将其添加到dep实例的subs数组中
// target指向一个Watcher实例,每个Watcher都是一个订阅者
// Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
// 此处的问题是:并不是每次Dep.target有值时都需要添加到订阅者管理员中去管理,需要对订阅者去重,不影响整体思路,不去管它
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set: (newVal) => {
console.log('new value seted')
if(val === newVal) return
val = newVal
// 对新值进行监听
chlidOb = observe(newVal)
// 通知所有订阅者,数值被改变了
dep.notify()
}
})
}
function observe(value){
// 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
if(!value || typeof value !== 'object'){
return
}
return new Observer(value)
}

module.exports = observe
});
require.register("Vue/src/Watcher.js", function (exports, require, module) {
var Dep = require('./Dep');
class Watcher{
constructor(vm, expOrFn, cb){
this.vm = vm // 被订阅的数据一定来自于当前Vue实例
this.cb = cb // 当数据更新时想要做的事情
this.expOrFn = expOrFn // 被订阅的数据
this.val = this.get() // 维护更新之前的数据
}
// 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
update(){
this.run()
}
run(){
const val = this.get()
if(val !== this.val){
this.val = val;
this.cb.call(this.vm)
}
}
get(){
// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = this
const val = this.vm._data[this.expOrFn]
// 置空,用于下一个Watcher使用
Dep.target = null
return val;
}
}
module.exports = Watcher
});
require.register("Vue/src/Dep.js", function (exports, require, module) {
class Dep{
constructor(){
this.subs = []
}
addSub(sub){
this.subs.push(sub)
}
notify(){
// 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
this.subs.forEach((sub) => sub.update())
}
}
module.exports = Dep
});

require.alias("Vue/src/main.js", "Vue/index.js");