My Little World

vue 源码学习四【数据双向绑定】

vue双向绑定原理,依赖收集是
在created声明周期之前,
render生成虚拟dom的时候

1
2
3
4
5
6
7
8
9
Vue.prototype._init = function (options) {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
callHook(vm, 'beforeCreate');//运行订阅了beforecreate钩子的相关方法
initState(vm);//处理数据
callHook(vm, 'created');

转换

转换成内置函数mergedInstanceDataFn

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
function mergeOptions (
parent,
child,
vm
) {
{
checkComponents(child);
}

if (typeof child === 'function') {
child = child.options;
}

// ... normalizeProps, normalizeInject, normalizeDirectives

if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}

var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
//在上面的循环中会遍历到配置的options参数里的data属性,则会调用到strats.data
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}
//
strats.data = function (
parentVal,
childVal,
vm
) {
if (!vm) {
//...vm就是VUE实例,所以不会走这里
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
};

function mergeDataOrFn (
parentVal,
childVal,
vm
) {
if (!vm) {
...
} else {
return function mergedInstanceDataFn () {
// instance merge
var instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
var defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}

这一步结束后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vm.$options.data = function mergedInstanceDataFn () {
//声明时传递进来的data属性值,是函数则执行拿到对象
var instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
//内部属性没有data属性,所以 defaultData 为undefined
var defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
function mergeData (to, from) {
if (!from) { return to }
...//省略若干逻辑
}

数据劫持

开始进行真正初始化—数据劫持

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

function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
}

function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
//得到的data不是object对象
if (!isPlainObject(data)) {
data = {};
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{if (methods && hasOwn(methods, key)) {...与method同名警告}}
if (props && hasOwn(props, key)) {
...与prop同名警告
} else if (!isReserved(key)) { //不是以$或者_开头的key
proxy(vm, "_data", key); //将key值存一份到vm的'_data'属性上
}
}
// observe data
observe(data, true /* asRootData */);
}

function getData (data, vm) {
// #7573 disable dep collection when invoking(调用) data getters
pushTarget();
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}

给data创建观察者实例,挂载‘_ob_’属性,指向一个Observer对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}

Observer对象有三个属性,‘ob’属性相同
{
value:data,
dep : new Dep();
vmCount :0
}

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
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
//遍历所有属性并将它们转换为getter / setter。
//仅当值类型为Object时才应调用此方法。
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};

真正进行劫持的方法defineReactive$$1

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
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();

var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}

// cater(迎合) for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}

var childOb = !shallow && observe(val);//对值进行劫持处理 开始递归处理
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) { //new watch的时候,Dep.target指向Watcher对象,再运行render函数,访问具体变量就会调用这里
dep.depend();//对key的依赖进行依赖收集
if (childOb) {//如果值是一个对象的情况下
childOb.dep.depend();//对对象值的依赖进行依赖收集 实现深度监听
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
//相同值,不更新
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
//有setter方法直接用setter方法更新,没有直接赋值
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
//对新值进行劫持处理 更新childOb
//如果新值是对象对新赋的值在更新时进行依赖收集
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}

每个被劫持过的数据的标识

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
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};

Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this); //调用Watcher.addDep方法,this指dep
}
};

Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update(); //执行watcher的update函数
}
};
Dep.target = null;
var targetStack = [];

订阅发布

mountComponent函数会new Watcher一个对象,执行get方法【详见源码分析二】

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
var _Set;
/* istanbul ignore if */ // $flow-disable-line
if (typeof Set !== 'undefined' && isNative(Set)) {
// use native Set when available.
_Set = Set;
} else {
// a non-standard Set polyfill that only works with primitive keys.
_Set = /*@__PURE__*/(function () {
function Set () {
this.set = Object.create(null);
}
Set.prototype.has = function has (key) {
return this.set[key] === true
};
Set.prototype.add = function add (key) {
this.set[key] = true;
};
Set.prototype.clear = function clear () {
this.set = Object.create(null);
};
return Set;
}());
}
var Watcher = function Watcher (...args){
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
}
Watcher.prototype.get = function get () {
pushTarget(this); //将当前Watcher挂到第三方Dep.target上
var value;
var vm = this.vm;
try {
//this.getter会执行render,触发数据的get方法,进行相互订阅
value = this.getter.call(vm, vm);
} catch (e) {
...
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
//watcher和dep互相存id
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this); //将watcher加入到dep的sub属性中
}
}
};
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get(); //重新调用render函数更新数据
if (
value !== this.value ||
//对于需要深度监听的数据类型,值没有变,也许值内容发生了变化
isObject(value) ||
this.deep
) {
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};

vue3.0数据响应式原理

vue3.0的改进

1.对源码进行优化
使用monorepo和TS管理和开发源码,提升自身代码的可维护性
2.性能优化
减少源码体积
移除冷门的feature(如filter,inline-template)
引入tree-shaking,减小打包体积

数据劫持优化
原来缺点
无法判断对象属性删除新增
深度层次结点要递归遍历,也不知道会不会用到,都要劫持,不友好

解决
使用proxy API 可以检测到删除还是新增
在用到时才劫持,避免无脑递归

3.编译优化
判断更新的颗粒度变小,从原来的组件级,利用block tree的区块细化到动态节点级
引入 compositionAPI 优化逻辑关注点相关的代码,可以避免mixin的时候的命名冲突