开始
接上文,上面留了一个问题,本文会解答,就是this._init()方法在哪里定义的。 我们先看initMixin,就是上文执行完Vue声明函数之后,执行的第一个mixin。 这里直接贴重要的代码
let uid = 0
export function initMixin (Vue: Class<Component>) {
//这里options?:object是flow的语法,类似typescript.不用太管先
//_init就是这里挂在原型上的
Vue.prototype._init = function (options?: Object) {
//把实例对象给vm变量
const vm: Component = this
// vm 添加_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)
}
// 避免vm被observer的标志
vm._isVue = true
//options我们通过new Vue传过来的是{render: h => h(App),},所以执行else
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 上绑定一个$option属性,并且合并option,去看看 (mergeOptions),
// 合并之后,可以用$options.data访问Vue实例化传入的data
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
//这里执行一个代理的过程 看(initProxy)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
//这里的就直接用vm当自己的代理了
vm._renderProxy = vm
}
// 挂载自己
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)
}
}
}
复制代码
mergeOptions 合并options
我们从上面的代码抽出来讲,先是将vm=this方便以后用vm代替this,接着关键点就是合并options,并把options挂在vm上。怎么合并的呢? 调用了mergeOptions, 看我写主要的部分。这里就是把options给合并了
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
//就直接返回了,没干什么
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
//挂载props
normalizeProps(child, vm)
//注入
normalizeInject(child, vm)
//标准化指令
normalizeDirectives(child)
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
// 主要部分,将父与子的field字段进行合并,最后还是返回了options
const options = {}
let key
//合并父级属性到options
for (key in parent) {
mergeField(key)
}
//合并子级属性到options
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
//合并函数
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
复制代码
合并中有一个参数是 resolveConstructorOptions(vm.constructor),拿到函数作为参数,我们进去看看怎么搞的
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
}
复制代码
上面我们知道Ctor就是Vue构造函数,他是没有super属性的,所以这里就先返回了options.但是这里你调用debugger可以看到Ctors.options有属性的,什么时候加上的呢?这个要等我们调用initGlobalAPI 才知道。
( 插一句,这里用到源码调试手段,debugger.看源码利器。)
initProxy 代理
再看最_init()里面有个 initProxy(vm)函数,这里其实是一个代理的过程。 我们先讲什么是代理,最基础的用法(es6的一个语法), 下面本来调用a.name是"mik",但是代理proxy重写了get方法,直接返回了"张三",所以读取proxy.name就是张三。
var a={"name":"mik"};
let proxy=new Proxy(a,{
get:function (target,property) {
return '张三';
}
})
console.log(proxy.name) //张三
复制代码
看了基础代理,再看Vue里面的代理
initProxy = function initProxy (vm) {
// hasProxy true
if (hasProxy) {
// 用哪个handler处理代理
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
//用hasHandler,看Proxy ,百度下js的proxy代理,就是 vm._renderProxy的改变会被映射到vm
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
//最主要的是这里,vm 的代理处理函数handlers,在下面了
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
const allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require'
)
const hasHandler = {
has (target, key) {
//是否有这个属性
const has = key in target
//属性是否在全局里面,全局的在上面
const isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
if (!has && !isAllowed) {
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return has || !isAllowed
}
}
复制代码
总结
这篇就先讲这么多,主要是options的合并和initproxy的代理。