# 阅读vue源码经验
- 为啥beforeCreate之前拿不到data
因为callHook('beforeCreate')在initState之前执行
- $on,$off,$emit的实现
class Event {
constructor() {
this._events = {}
}
$on(event, fn) {
if(Array.isArray(event)) {
for(let i = 0,l = event.length; i < l ; i++) {
this.$on(event[i],fn);
}
} else {
(this._events[event] || (this._events[event] = [])).push(fn)
}
return this
}
$once(event, fn) {
const self = this
function on() {
this.$off(event, on)
fn.apply(self, arguments)
}
on.fn = fn;
this.$on(event,on)
return this
}
$emit(event) {
const cbs = this._events[event]
const args = Array.prototype.slice.call(arguments,1)
if(cbs) {
for(var i = 0, l = cbs.length; i < l ;i++ ){
cbs[i](args)
}
}
return this
}
$off(event,fn){
// 没有参数,取消所有事件
if(!arguments.length) {
this._events = {}
return this
}
// 多个事件名,同一个事件
// $on(['say','he'], function(){ console.log(1)})
if(Array.isArray(event) ){
for(var i$1 = 0, l = event.length; i$1 < l; i$1 ++) {
this.$off(event[i$1], fn)
}
return this
}
// 没有找到事件
var cbs = this._events[event];
if (!cbs) {
return this
}
//
if (!fn) {
this._events[event] = null;
return this
}
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return this
}
}
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
- diff算法
- 父子组件挂载顺序
父---beforeCreate
父---created
父---beforeMount
子---beforeCreate
子---created
子---beforeMount
孙---beforeCreate
孙---created
孙---beforeMount
孙---mounted
子---mounted
父---mounted
父---beforeDestroy
子---beforeDestroy
孙---beforeDestroy
孙---destroyed
子---destroyed
父---destroyed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- render函数到底是啥
<div id="app">
<span ref="span" @click="setName"> {{name}}</span>
</div>
<script>
(function anonymous() {
with(this){
return _c(
'div',
{
attrs:{
"id":"app"
}
},
[_c(
'span',
{
ref:"span",
on:{
"click":setName
}
},
[_v(" " + _s(name))])
]
)
}
})
</script>
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
- 一个组件一个Watcher实例
function mountComponent() {
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
}
2
3
4
5
6
7
8
9
10
- 依赖收集是何时进行的 执行render函数,触发依赖收集
(function anonymous() {
with(this){return _c('div',[_v(_s(name)+" "),_c('router-view')],1)}
})
2
3
- v-model的实现
这个HTML代码<div id="app"><input type="text" v-model="myname"></div>
经过下面编译后
addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
genDefaultModel (vue2.6.12.js:7571)
model (vue2.6.12.js:7445)
genDirectives (vue2.6.12.js:11298)
genData$2 (vue2.6.12.js:11206)
genElement (vue2.6.12.js:11075)
genNode (vue2.6.12.js:11484)
(anonymous) (vue2.6.12.js:11447)
genChildren (vue2.6.12.js:11447)
genElement (vue2.6.12.js:11078)
generate (vue2.6.12.js:11043)
baseCompile (vue2.6.12.js:11943)
compile (vue2.6.12.js:11914)
compileToFunctions (vue2.6.12.js:11797)
Vue.$mount (vue2.6.12.js:12027)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最终生成的代码如下
(function anonymous() {
with(this) {
return _c(
'div', {
attrs: {
"id": "app"
}
}, [_c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (myname),
expression: "myname"
}],
attrs: {
"type": "text"
},
domProps: {
"value": (myname)
},
on: {
"input": function ($event) {
if ($event.target.composing) return;
myname = $event.target.value
}
}
})])
}
})
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
所有说 v-model
是<input :value="myname" type="text" @input="myname=$event">
- 数组的哪几个方法为啥能触发视图更新
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
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
- 事件修饰符once的实现
<div id="app"><input :value="myname" type="text" @input.once="change"> </div>
经过下面编译过程
add$1 (vue2.6.12.js:7653)
createOnceHandler(event.name, cur, event.capture); (vue2.6.12.js:7603)
updateListeners (vue2.6.12.js:2282)
updateDOMListeners (vue2.6.12.js:7683)
invokeCreateHooks (vue2.6.12.js:6153)
createElm (vue2.6.12.js:6040)
createChildren (vue2.6.12.js:6137)
createElm (vue2.6.12.js:6038)
patch (vue2.6.12.js:6610)
Vue._update (vue2.6.12.js:4011)
updateComponent (vue2.6.12.js:4134)
get (vue2.6.12.js:4557)
Watcher (vue2.6.12.js:4545)
mountComponent (vue2.6.12.js:4141)
Vue.$mount (vue2.6.12.js:9146)
Vue.$mount (vue2.6.12.js:12046)
(anonymous) (v-model的实现.html:29)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
将input.once事件编译为
handler = original._wrapper = function (e) {
if (
// no bubbling, should always fire.
// this is just a safety net in case event.timeStamp is unreliable in
// certain weird environments...
e.target === e.currentTarget ||
// event is fired after handler attachment
e.timeStamp >= attachedTimestamp ||
// bail for environments that have buggy event.timeStamp implementations
// #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
// #9681 QtWebEngine event.timeStamp is negative value
e.timeStamp <= 0 ||
// #9448 bail if event is fired in another document in a multi-page
// electron/nw.js app, since event.timeStamp will be using a different
// starting reference
e.target.ownerDocument !== document
) {
return original.apply(this, arguments)
}
};
}
target$1.addEventListener(
name,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
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
- 执行input事件 触发handler事件
- handler触发 original.apply(this, arguments)
- 触发
onceHandler
事件
function onceHandler () {
var res = handler.apply(null, arguments);
if (res !== null) {
remove$2(event, onceHandler, capture, _target);
}
}
2
3
4
5
6
- 该onceHandler事件执行后,把当前DOM绑定的事件进行移除,从而实现执行一次的效果
- 事件修饰符.sync的实现
会被编译为
v-bind="title"
this.$emit('update:tilte','xxx')
@update="title=$event"
2
3
4
5
6
7
- nextTick的实现
1. <span ref="span" @click="setName"> {{name}}</span> 点击触发setName
2. 调用对应事件,对应事件
2
3
4
5
setName() { this.name=Math.random() console.log('. :>> ', this.$el.textContent);
this.$nextTick(() => {
console.log('thinextTicks. :>> ', this.$el.textContent);
})
}
3. 触发 this.$nextTick(() => { console.log('thinextTicks. :>> ', this.$el.textContent);})
4. 触发 Vue.prototype.$nextTick = function (fn) { return nextTick(fn, this) };
5. 触发 function nextTick (cb, ctx) {}
2
3
4
5
6
- Vue.set 和 this.$set
this.$set(this,'name',100) 通过对新值进行defineReactive()后再触发 ob.dep.notify() 触发视图更新
- 计算属性的依赖收集
代码实例
<div id="app">{{pName}}</div>
<script>
var vm = new Vue({
el:'#app',
data() {
return {
name:'2'
}
},
computed:{
pName() {
return this.name + 'ppppp'
}
},
created() {
setTimeout(() => {
this.name = 4
}, 2000)
},
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
实例化过程
initComputed (vm, {
pName() {
return this.name + 'ppppp'
}
})
var watchers = vm._computedWatchers = Object.create(null);
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
defineComputed(vm, key, userDef);
Object.defineProperty(target, key, sharedPropertyDefinition);
// userDef = pName() {
// return this.name + 'ppppp'
// }
sharedPropertyDefinition.get = createGetterInvoker(userDef)
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
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
依赖收集调用栈
Object.defineProperty(vm, 'pName', {
configurable: true,
enumerable: true,
get:computedGetter
});
// reactiveGetter (vue.js:1050) //
// proxyGetter (vue.js:4627) // 触发对name 的读取
// pName (computed.html:17) // 执行 pName() {return this.name + 'ppppp'} 函数
// get (vue.js:4478) // this.getter.call(vm, vm);
// evaluate (vue.js:4583) // 获取pName的值
// computedGetter (vue.js:4832) // 触发computedGetter
// (anonymous) (VM452:3) // 调用render函数, 执行 this.pName
// Vue._render (vue.js:3551) //
// updateComponent (vue.js:4067) //
// get (vue.js:4478)
// Watcher (vue.js:4467) // 实例化组件实例的watcher
// mountComponent (vue.js:4074) // 挂载组件
// Vue.$mount (vue.js:9061) // 开始挂载组件
// Vue.$mount (vue.js:11961) // 生成render函数
// Vue._init (vue.js:5012) // 初始化实例
// Vue (vue.js:5078)
// (anonymous) (computed.html:8) // 实例化Vue
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Vuex经验
Vue.use(Vuex)
Vuex.install(Vue)
applyMixin(Vue)
Vue.mixin({ beforeCreate: vuexInit })
function vuexInit () {
var options = this.$options;
// store injection
// 根组件注入
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
获取父组件的store实例
this.$store = options.parent.$store;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
this.$store = options.store || options.parent.$store;
new Vue({ el: '#app', store })
触发 beforeCreate, 如果没有store就执行挂载,否则从父组件继承store
渲染使用了Store里的数据的组件
触发组件的计算属性
Vue.component('a-a' ,{
template: '<div><h1>我是A{{count}}</h1> <button @click="add()"> 加</button></div>',
computed: {
count () {
return this.$store.state.count
}
},
methods: {
add() {
this.$store.commit('add', 1000)
}
},
})
2
3
4
5
6
7
8
9
10
11
12
13
- 触发Store的
this.$store.state
- 触发
prototypeAccessors$1.state.get = function () {
return this._vm._data.$$state
};
2
3
- 触发
Object.defineProperty(obj, '$$state', {get() {}})
- 进一步读取
this.$store.state.count
触发childOb.dep.depend();
- 进行依赖收集
- 通过commit 触发state变更,触发computed
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
2
3
4
5
6
# VueRouter笔记
Vue.use(VueRouter) - 全局混入beforeCreate钩子
Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var router = new VueRouter()
执行根
new Vue
的时候,执行混入的beforeCreate
钩子,并做四件事- 把当前应用的根组件的
_routerRoot
设置为根组件的实例 - 把当前应用的根组件的
_router
设置为用户传入的router实例 - 调用
router.init
初始化router - 设置当前应用的根组件的
_route
为响应式
- 把当前应用的根组件的
new Vue({
el:'#app',
router
})
2
3
4
this._router.init(this)
执行
Vue.util.defineReactive(this, '_route', this._router.history.current)
调用render函数
(function anonymous() {
with(this){return _c('div',[_v(_s(name)+" "),_c('router-view')],1)}
})
2
3
渲染
router-view
组件调用
RouterView
组件的render方法获取
var route = parent.$route;
,触发上面第5步defineReactive$$1
定义的_route的get函数调用get方法做依赖收集,此时的Dep.target是根Vue的实例
当调用
this.$router.push
会触发transitionTo
事件,从而触发第5步响应式_route
的改变,从而触发dep.notify();
触发
subs[i].update()
触发
queueWatcher(this);
# debug记录
// render (router.js:324)
// createFunctionalComponent (vue2.6.10.js:3065)
// createComponent (vue2.6.10.js:3238)
// _createElement (vue2.6.10.js:3422)
// createElement (vue2.6.10.js:3360)
// vm._c (vue2.6.10.js:3491)
// (anonymous) (VM963:3)
// Vue._render (vue2.6.10.js:3545)
// updateComponent (vue2.6.10.js:4061)
// get (vue2.6.10.js:4472)
// Watcher (vue2.6.10.js:4461)
// mountComponent (vue2.6.10.js:4068)
// Vue.$mount (vue2.6.10.js:9038)
// Vue.$mount (vue2.6.10.js:11923)
// Vue._init (vue2.6.10.js:5006)
// Vue (vue2.6.10.js:5072)
// (anonymous) (15路由debug.html:29)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
← Vue过渡 & 动画原理 Vuex →