以下笔记由源码笔记助手自动生成

Ⅰ core

一、global-api

①assets.js

definition = this.options._base.extend(definition);

initAssetRegisters函数中,Vue.component,this.options._base值为Vue
我们每个组件的创建都是通过 Vue.extend 继承而来


②extend.js

Vue.extend = function (extendOptions: Object): Function {

总结:

  1. 定义了VueComponent,并且组件执行了和Vue定义一样的_init方法;
  2. Sub.options进行了选项合并;
  3. 初始化了props,进行了_props的代理。defineComputed
  4. 缓存了组件;

if (cachedCtors[SuperId]) {

这里的cachedCtors[SuperId]为Sub,即组件;第一次执行以后缓存到这里

Sub.prototype = Object.create(Super.prototype);

1
2
3
4
这里的参数options和上面的extendOptions不一样;
const Sub = function VueComponent(options) {
this._init(options);
};

VueComponent定义地方,继承Vue;
组件也会执行_init方法;

Sub.options = mergeOptions(

组件options合并到父组件上面,生成新的组件的options
注意这里的options不同于$options;

1
2
3
4
Sub.options = mergeOptions(
Super.options,
extendOptions
);

二、instance

①init.js

const opts = vm.$options = Object.create(vm.constructor.options);

组件的vm.$options继承其组件上面的options;


②lifecycle.js

let parent = options.parent

parent来源:core/vdom/create-component.js->createComponentInstanceForVnode
在vm.patch会创建子组件,在_update时,activeInstance总是指向当前实例。而调用createComponentInstanceForVnode时,会传入activeInstance

if (parent && !options.abstract) {

initLifecycle方法里,vm.$parent的值为最近的一个非抽象组件;

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {

这里const prevActiveInstance = activeInstance,patch之后,activeInstance = prevActiveInstance
要保存之前的,估计是为了递归考虑。


③proxy.js

const handlers = options.render && options.render._withStripped

_withStripped: 当手写render函数时,就没有with,那么就没有警告。所以可以给_withStripped设置以便得到警告;
在使用 webpack 配合 vue-loader 的环境中, vue-loader 会借助 vuejs@component-compiler-utils 将 template 编译为不使用 with 语句包裹的遵循严格模式的 JavaScript,并为编译后的 render 方法设置 render._withStripped = true

vm._renderProxy = new Proxy(vm, handlers);

has操作可以拦截
属性查询: foo in proxy
继承属性查询: foo in Object.create(proxy)
with 检查: with(proxy) { (foo); }
Reflect.has()


④render.js

defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true);

备注(todo:高阶组件用到


⑤state.js

toggleObserving(false);

initProps中:在定义 props 数据时,不将 prop 值转换为响应式数据,这里要注意的是:由于 props 本身是通过 defineReactive 定义的,所以 props 本身是响应式的,但没有对值进行深度定义

export function getData(data: Function, vm: Component): any {

为了防止使用 props 数据初始化 data 数据时收集冗余的依赖
pushTarget和popTarget,因为props的初始化在前。

watchers[key] = new Watcher(

计算属性,每个属性,对应一个watcher。和一个dep

export function defineComputed(

这里组件也会调用,在extent.js->initComputed

watch选项和method结合用法

createWatcher函数,如下使用场景

1
2
3
4
5
6
7
8
watch: {
name: 'handleNameChange'
},
methods: {
handleNameChange () {
console.log('name change')
}
}
1
2
3
if (typeof handler === 'string') {
handler = vm[handler];
}

三、observer

①array.js

if (inserted) ob.observeArray(inserted)

inserted表示增加的元素,对其进行观测

ob.dep.notify()

显然数组本身变化了,由于在get观测时,进行了收集,childOb就是此处的ob,那么理应触发notify;

1
2
3
4
5
6
7
8
9
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}

②dep.js

思考为什么不把Dep.target直接放到dep的subs里面?

每个观测的对象,都有一个ob属性。都有一个dep对应;
每个观测的属性,都有对应的一个闭包dep;
如果直接把Dep.target这个Watcher放到dep里面;
?无法做到去重依赖收集?

1
2
3
if (Dep.target) {
Dep.target.addDep(this);
}

③scheduler.js

export function queueWatcher (watcher: Watcher) {

入队,异步批量更新,也是值得学习的地方!

flush时,可能存在还有观察者入队情况

在这个flushSchedulerQueue执行的过程中,可能会执行到queueWatcher函数里面;
什么时候呢?
计算属性,比如队列执行更新时经常会执行渲染函数观察者的更新,渲染函数中很可能有计算属性的存在,由于计算属性在实现方式上与普通响应式属性有所不同,
所以当触发计算属性的 get 拦截器函数时会有观察者入队的行为,这个时候我们需要特殊处理。
也就是说,某个计算属性依赖一个非计算属性,当非计算属性都入队完毕后,开始flush了。
这个时候,计算属性可能入队。要插入在适当的位置。
?具体呢??

1
2
3
4
5
6
7
8
9
10
11
    if (!flushing) {
queue.push(watcher)
} else {
if already flushing, splice the watcher based on its id
if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}

④index.js

this.observeArray(value)

任何对数组的原生操作,实际上都通过原型链,访问了另外一个对象,另外一个对象
拥有和数组一样的方法,只是在里面进行了额外的操作,包括对新元素继续观测,触发当前数组的dep的notify

1
2
3
4
5
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)

if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {

避免重复观测一个数据对象

保证定义响应式数据行为的一致性

属性原本的 getter 由用户定义,用户可能在 getter 中做任何意想不到的事情.
但是如果只有getter,没有setter,那么,本身是不会深度观测的。如果重新给熟悉赋值,会对新的值进行观测。
与最开始的不深度观测不一致了。

1
2
3
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

if (Array.isArray(value)) {

defineReactive的get方法中,为了让数组的每个元素,增加属性时,也是响应的;

1
2
3
if (Array.isArray(value)) {
dependArray(value)
}

target.splice(key, 1, val)

Vue.set给数组增加元素实际上调用的是splice响应式方法;

defineReactive(ob.value, key, val)

Vue.set方法,在设置新属性后,本身的对象也改变了,需要触发通知.
本身的收集,是在get中完成。对应的都是同一个ob.

1
2
if (childOb) {
childOb.dep.depend()
1
2
defineReactive(ob.value, key, val)
ob.dep.notify()

function dependArray (value: Array<any>) {

如果数组的某个元素,是个对象或数组,那么一点会有对应的ob
那么对这个元素本身,需要进行收集。这样,自身增加元素的时候,可以调用自身ob这个观察者上面的dep属性,进行notify.

1
2
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()

⑤traverse.js

解决循环引用问题

它解决了循环引用导致死循环的问题

1
2
3
4
const obj1 = {}
const obj2 = {}
obj1.data = obj2
obj2.data = obj1
1
2
3
4
5
6
7

const depId = val.__ob__.dep.id;
if (seen.has(depId)) {
return;
}

seen.add(depId);

⑥watcher.js

如何避免依赖重复收集?

Watcher来管理dep。
newDeps,防止单次render,相同的值,虽然都执行了get,但不会都加入多次依赖。

一次render结束后:会执行cleanupDeps(),对无用的dep清理,无用的定义就是dep没在新的newDepIds里面;
然后将新的newDepIds复制给旧的depIds。而newDepIds清理。
这个depIds机制,可以防止多次执行render()时重复收集。
下次更新渲染时,某些熟悉已经进行了依赖收集了,就不需要再收集一次了。

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
 addDep(dep: Dep) {
const id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}

}
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();

tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}

this.dirty = false;

getAndInvoke方法内
由于计算属性是惰性求值,所以在实例化计算属性的时候 this.dirty 的值会被设置为 true,代表着还没有求值,后面当真正对计算属性求值时,也就是执行如上代码时才会将 this.dirty 设置为 false,代表着已经求过值了

计算属性惰性求值+依赖收集

此时执行get,那么计算属性方法内部变量,将会收集当前观察者,也就是计算属性的观察者。
当内部变量变化时,会自动调用当前计算属性的观察者
响应式属性收集计算属性的观察者对象,计算属性的观察者对象收集渲染函数的观察者对象

1
2
3
4
5
6
7
8
evaluate() {
if (this.dirty) {
this.value = this.get();
this.dirty = false;
}

return this.value;
}

if (!this.vm._isBeingDestroyed) {

remove self from vm’s watcher list
this is a somewhat expensive operation so we skip it
if the vm is being destroyed.


四、util

①perf.js

perf.measure(name, startTag, endTag)

window.performance
Vue性能追踪

1
2
mark = tag => perf.mark(tag)
measure = (name, startTag, endTag) => {