《一天读懂一个Vue源码文件》---init.js ,
天下风云出我辈,一入江湖岁月催。 黄图霸业谈笑中,不胜人生一场醉。-- 《笑傲江湖》
为什么会问这个问题
上一篇文章大致了解了一下vnode
,Component
,vnodeData
的数据结构,刚才大致数了一下vue源码src文件下所有的Js文件,大致一共有70个,按照一天弄明白一个文件的话,基本上两个月就可以把vue的源码理解个差不多了。
今天先看src/core/instance/index.js
及src/core/instance/init.js
这两个文件。
从代码的角度思考Vue是什么
项目中通常入口文件index.js中通常会有以下代码:
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#pay");
从这个代码中可以看到,使用new
关键字,创建了一个对象,所以可以得出以下结论,Vue是个构造函数,这是毫无疑问的。既然是构造函数,那么一定有初始的参数。我们看下接口中定义的Vue都包含哪些参数。
vue.d.ts中定义的Vue接口
export interface Vue {
// 只读属性
readonly $el: HTMLElement;
readonly $options: ComponentOptions<this>;
readonly $parent: Vue;
readonly $root: Vue;
readonly $children: Vue[];
readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
readonly $slots: { [key: string]: VNode[] };
readonly $scopedSlots: { [key: string]: ScopedSlot };
readonly $isServer: boolean;
readonly $data: Record<string, any>;
readonly $props: Record<string, any>;
readonly $ssrContext: any;
readonly $vnode: VNode;
readonly $attrs: Record<string, string>;
readonly $listeners: Record<string, Function | Function[]>;
// 其他属性
$mount(elementOrSelector?: Element | String, hydrating?: boolean): this;
$forceUpdate(): void;
$destroy(): void;
$set: typeof Vue.set;
$delete: typeof Vue.delete;
$watch(
expOrFn: string,
callback: (this: this, n: any, o: any) => void,
options?: WatchOptions
): (() => void);
$watch<T>(
expOrFn: (this: this) => T,
callback: (this: this, n: T, o: T) => void,
options?: WatchOptions
): (() => void);
$on(event: string | string[], callback: Function): this;
$once(event: string, callback: Function): this;
$off(event?: string | string[], callback?: Function): this;
$emit(event: string, ...args: any[]): this;
$nextTick(callback: (this: this) => void): void;
$nextTick(): Promise<void>;
$createElement: CreateElement;
}
可以看到,接口中定义的属性,都是我们日常开发中常用的属性。
然后看src/core/instance/index.js
文件
这个文件中内容比较少,最容易理解
// 重点关注 initMixin
import { initMixin } from './init'
// 暂时不考虑这些
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// 重点关注 initMixin
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
可以看出Vue
构造函数接受options
作为参数,并且调用_init()
方法进行初始化。随后又执行了initMixin(Vue)方法进行初始化。
这里有一个非常巧妙的问题,就是构造函数中的_init
方法初始化的问题。举个例子。
function Vue(options){
this._init(options)
}
let b = new Vue({data:'test'});
// 会报错
// Uncaught TypeError: this._init is not a function
但是,initMixin(Vue)
方法接受Vue,并在其原型上添加了_init
方法,进行了初始化。这个非常巧妙。
接下来看src/core/instance/init.js
抛开其中以下几个方法,这个文件其实只做了一件事---合并options,处理参入的参数
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
init.js源代码
该文件的大体逻辑如下: initMixn()
方法接受options
作为参数,options
如果是组件,则通过initInternalComponent(vm,options)
方法将options
参数合并到vm.options
上。否则,将从vm的构造函数中解析出options
进行合并。解析的过程中使用 dedupe()
方法消除重复数据。
接来来,调用initProxy(vm)
对属性进行拦截。
最后,调用vm.$mount(vm.$options.el)
进行挂载。
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
opts.parent = options.parent
opts.propsData = options.propsData
opts._parentVnode = options._parentVnode
opts._parentListeners = options._parentListeners
opts._renderChildren = options._renderChildren
opts._componentTag = options._componentTag
opts._parentElm = options._parentElm
opts._refElm = options._refElm
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const extended = Ctor.extendOptions
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
}
}
return modified
}
// 消除重复
function dedupe (latest, extended, sealed) {
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
// between merges
if (Array.isArray(latest)) {
const res = []
sealed = Array.isArray(sealed) ? sealed : [sealed]
extended = Array.isArray(extended) ? extended : [extended]
for (let i = 0; i < latest.length; i++) {
// push original options and not sealed options to exclude duplicated options
if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
return res
} else {
return latest
}
}
明日复明日,明日何其多
明天看initLifeCycle
最后说两句
- 动一动您的小手,
「点个赞吧」
- 都看到这里了,不妨
「加个关注」
javascript基础知识总结