vue源码笔记
文章目录
以下笔记由源码笔记助手自动生成
Ⅰ 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 {
总结:
- 定义了VueComponent,并且组件执行了和Vue定义一样的_init方法;
- Sub.options进行了选项合并;
- 初始化了props,进行了_props的代理。defineComputed
- 缓存了组件;
if (cachedCtors[SuperId]) {
这里的cachedCtors[SuperId]
为Sub,即组件;第一次执行以后缓存到这里
Sub.prototype = Object.create(Super.prototype);
1 | 这里的参数options和上面的extendOptions不一样; |
VueComponent定义地方,继承Vue;
组件也会执行_init方法;
Sub.options = mergeOptions(
组件options合并到父组件上面,生成新的组件的options
注意这里的options不同于$options;
1 | Sub.options = mergeOptions( |
二、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 | watch: { |
1 | if (typeof handler === 'string') { |
三、observer
①array.js
if (inserted) ob.observeArray(inserted)
inserted表示增加的元素,对其进行观测
ob.dep.notify()
显然数组本身变化了,由于在get观测时,进行了收集,childOb就是此处的ob,那么理应触发notify;
1 | if (Dep.target) { |
②dep.js
思考为什么不把Dep.target直接放到dep的subs里面?
每个观测的对象,都有一个ob属性。都有一个dep对应;
每个观测的属性,都有对应的一个闭包dep;
如果直接把Dep.target这个Watcher放到dep里面;?
无法做到去重依赖收集?
1 | if (Dep.target) { |
③scheduler.js
export function queueWatcher (watcher: Watcher) {
入队,异步批量更新,也是值得学习的地方!
flush时,可能存在还有观察者入队情况
在这个flushSchedulerQueue执行的过程中,可能会执行到queueWatcher函数里面;
什么时候呢?
计算属性,比如队列执行更新时经常会执行渲染函数观察者的更新,渲染函数中很可能有计算属性的存在,由于计算属性在实现方式上与普通响应式属性有所不同,
所以当触发计算属性的 get 拦截器函数时会有观察者入队的行为,这个时候我们需要特殊处理。
也就是说,某个计算属性依赖一个非计算属性,当非计算属性都入队完毕后,开始flush了。
这个时候,计算属性可能入队。要插入在适当的位置。?
具体呢??
1 | if (!flushing) { |
④index.js
this.observeArray(value)
任何对数组的原生操作,实际上都通过原型链,访问了另外一个对象,另外一个对象
拥有和数组一样的方法,只是在里面进行了额外的操作,包括对新元素继续观测,触发当前数组的dep的notify
1 | if (Array.isArray(value)) { |
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
避免重复观测一个数据对象
保证定义响应式数据行为的一致性
属性原本的 getter 由用户定义,用户可能在 getter 中做任何意想不到的事情.
但是如果只有getter,没有setter,那么,本身是不会深度观测的。如果重新给熟悉赋值,会对新的值进行观测。
与最开始的不深度观测不一致了。
1 | if ((!getter || setter) && arguments.length === 2) { |
if (Array.isArray(value)) {
defineReactive的get方法中,为了让数组的每个元素,增加属性时,也是响应的;
1 | if (Array.isArray(value)) { |
target.splice(key, 1, val)
Vue.set
给数组增加元素实际上调用的是splice响应式方法;
defineReactive(ob.value, key, val)
Vue.set方法,在设置新属性后,本身的对象也改变了,需要触发通知.
本身的收集,是在get中完成。对应的都是同一个ob.
1 | if (childOb) { |
1 | defineReactive(ob.value, key, val) |
function dependArray (value: Array<any>) {
如果数组的某个元素,是个对象或数组,那么一点会有对应的ob。
那么对这个元素本身,需要进行收集。这样,自身增加元素的时候,可以调用自身ob这个观察者上面的dep属性,进行notify.
1 | e = value[i] |
⑤traverse.js
解决循环引用问题
它解决了循环引用导致死循环的问题
1 | const obj1 = {} |
1 |
|
⑥watcher.js
如何避免依赖重复收集?
Watcher来管理dep。newDeps
,防止单次render,相同的值,虽然都执行了get,但不会都加入多次依赖。
一次render结束后:会执行cleanupDeps(),对无用的dep清理,无用的定义就是dep没在新的newDepIds里面;
然后将新的newDepIds复制给旧的depIds。而newDepIds清理。
这个depIds机制,可以防止多次执行render()时重复收集。
下次更新渲染时,某些熟悉已经进行了依赖收集了,就不需要再收集一次了。
1 | addDep(dep: Dep) { |
this.dirty = false;
getAndInvoke
方法内
由于计算属性是惰性求值,所以在实例化计算属性的时候 this.dirty 的值会被设置为 true,代表着还没有求值,后面当真正对计算属性求值时,也就是执行如上代码时才会将 this.dirty 设置为 false,代表着已经求过值了
计算属性惰性求值+依赖收集
此时执行get,那么计算属性方法内部变量,将会收集当前观察者,也就是计算属性的观察者。
当内部变量变化时,会自动调用当前计算属性的观察者
响应式属性收集计算属性的观察者对象,计算属性的观察者对象收集渲染函数的观察者对象
1 | evaluate() { |
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 | mark = tag => perf.mark(tag) |