vue源码版本vue2.5.2
new Vue()
做了啥?new Vue()
会执行_init
方法,而_init
方法在initMixin
函数中定义。
src/core/instance/index.js
文件中定义了Vue
function Vue (options) {this._init(options)
}initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)export default Vue
initMixin
函数 初始化定义的Vue.prototype._init
。
export function initMixin (Vue: Class) {Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uid,自增IDvm._uid = uid++vm._isVue = true// merge options 合并optionsif (options && options._isComponent) {initInternalComponent(vm, options)} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}vm._renderProxy = vm// expose real selfvm._self = vm// 初始化生命周期相关实例属性initLifecycle(vm)//初始化事件initEvents(vm)//定义$createElement() createElement的执行过程initRender(vm)// 执行beforeCreate生命周期钩子callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/props//将data代理至Vue._data data的代理initState(vm)// 初始化provideinitProvide(vm) // resolve provide after data/props// 执行created生命周期钩子callHook(vm, 'created')// 当options中存在el属性,则执行挂载if (vm.$options.el) {//挂载#app $mount执行过程vm.$mount(vm.$options.el)}}
}
initLifecycle
函数initLifecycle
初始化生命周期相关变量即在vue实例上挂载一些属性并设置默认值,如$parent,$root,$children,$ref,vm._watcher,vm.__inactive,vm._directInactive,vm._isMounted,vm._isDestroyed,vm._isBeingDestroyed
export function initLifecycle (vm: Component) {const options = vm.$options// locate first non-abstract parentlet parent = options.parent//当前组件存在父级并且当前组件不是抽象组件if (parent && !options.abstract) {//通过while循环来向上循环,如果当前组件的父级是抽象组件并且也存在父级,那就继续向上查找当前组件父级的父级while (parent.$options.abstract && parent.$parent) {//更新parentparent = parent.$parent}//把该实例自身添加进找到的父级的$children属性中parent.$children.push(vm)}//直到找到第一个不是抽象类型的父级时,将其赋值vm.$parentvm.$parent = parent//设置实例根元素。判断如果当前实例存在父级,那么当前实例的根实例$root属性就是其父级的根实例$root属性,如果不存在,那么根实例$root属性就是它自己vm.$root = parent ? parent.$root : vmvm.$children = []vm.$refs = {}vm._watcher = nullvm._inactive = nullvm._directInactive = false//实例是否已挂载vm._isMounted = false//实例是否已销毁vm._isDestroyed = false//实例是否正准备销毁vm._isBeingDestroyed = false
}
initEvents
初始化事件initEvents 初始化事件。 初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。
export function initEvents (vm: Component) {
//在vm上新增_events属性并将其赋值为空对象,用来存储事件。vm._events = Object.create(null)vm._hasHookEvent = false// init parent attached events//获取父组件注册的事件赋给listeners,const listeners = vm.$options._parentListeners//如果listeners不为空,则调用updateComponentListeners函数,将父组件向子组件注册的事件注册到子组件的实例中if (listeners) {updateComponentListeners(vm, listeners)}
}
initRender
initRender函数 初始化渲染.。
export function initRender (vm: Component) {vm._vnode = null // the root of the child treevm._staticTrees = null // v-once cached treesconst options = vm.$optionsconst parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent treeconst renderContext = parentVnode && parentVnode.context//实例上插槽vm.$slots = resolveSlots(options._renderChildren, renderContext)vm.$scopedSlots = emptyObject//在实例上定义_c函数和$_createElement函数vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)const parentData = parentVnode && parentVnode.datadefineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)}
inject
选项inject 选项中的每一个数据key都是由其上游父级组件提供的,所以我们应该把每一个数据key从当前组件起,不断的向上游父级组件中查找该数据key对应的值,直到找到为止。如果在上游所有父级组件中没找到,那么就看在inject 选项是否为该数据key设置了默认值,如果设置了就使用默认值,如果没有设置,则抛出异常。
export function initInjections (vm: Component) {
//调用resolveInject把inject选项中的数据转化成键值对的形式赋给resultconst result = resolveInject(vm.$options.inject, vm)if (result) {toggleObserving(false)//遍历result中的每一对键值Object.keys(result).forEach(key => {//调用defineReactive函数将其添加当前实例上defineReactive(vm, key, result[key])})toggleObserving(true)}
}
注意,在把result中的键值添加到当前实例上之前,会先调用toggleObserving(false),而这个函数内部是把shouldObserve = false,这是为了告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式
resolveInject函数内部是如何把inject 选项中数据转换成键值对的。
export function resolveInject (inject: any, vm: Component): ?Object {if (inject) {// inject is :any because flow is not smart enough to figure out cached//创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。const result = Object.create(null)const keys = hasSymbol? Reflect.ownKeys(inject).filter(key => {/* istanbul ignore next */return Object.getOwnPropertyDescriptor(inject, key).enumerable}): Object.keys(inject)for (let i = 0; i < keys.length; i++) {const key = keys[i]//provideKey就是上游父级组件提供的源属性const provideKey = inject[key].fromlet source = vm//while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中while (source) {if (source._provided && hasOwn(source._provided, provideKey)) {result[key] = source._provided[provideKey]break}source = source.$parent}if (!source) {//是否有default属性,如果有的话,则拿到这个默认值,官方文档示例中说了,默认值可以为一个工厂函数,所以当默认值是函数的时候,就去该函数的返回值,否则就取默认值本身。如果没有设置默认值,则抛出异常。if ('default' in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} else if (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}}}return result}
}
官方文档中说inject 选项可以是一个字符串数组,也可以是一个对象,在上面的代码中只看见了处理当为对象的情况,那如果是字符串数组呢?怎么没有处理呢?
其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject
从Vue 2.0版本起,Vue不再对所有数据都进行侦测,而是将侦测粒度提高到了组件层面,对每个组件进行侦测,所以在每个组件上新增了vm._watchers属性,用来存放这个组件内用到的所有状态的依赖,当其中一个状态发生变化时,就会通知到组件,然后由组件内部使用虚拟DOM进行数据比对,从而降低内存开销,提高性能。
export function initState (vm: Component) {
//实例上新增了一个属性_watchers,用来存储当前实例中所有的watcher实例,无论是使用vm.$watch注册的watcher实例还是使用watch选项注册的watcher实例,都会被保存到该属性中。vm._watchers = []const opts = vm.$options//先判断实例中是否有props选项,如果有,就调用props选项初始化函数initProps去初始化props选项;if (opts.props) initProps(vm, opts.props)//先判断实例中是否有props选项,如果有,就调用props选项初始化函数initProps去初始化props选项;if (opts.methods) initMethods(vm, opts.methods)//接着再判断实例中是否有data选项,如果有,就调用data选项初始化函数initData去初始化data选项;如果没有,就把data当作空对象并将其转换成响应式if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}//接着再判断实例中是否有computed选项,如果有,就调用computed选项初始化函数initComputed去初始化computed选项if (opts.computed) initComputed(vm, opts.computed)//最后判断实例中是否有watch选项,如果有,就调用watch选项初始化函数initWatch去初始化watch选项if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}
开发中有注意到我们在data中可以使用props,在watch中可以观察data和props,之所以可以这样做,就是因为在初始化的时候遵循了这种顺序,先初始化props,接着初始化data,最后初始化watch。
在此之前,在合并options时已将props规范化
function initProps (vm: Component, propsOptions: Object) {
//propsData,父组件传入的真实props数据const propsData = vm.$options.propsData || {}//props,指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中const props = vm._props = {}// cache prop keys so that future props updates can iterate using Array// instead of dynamic object key enumeration.//指向vm.$options._propKeys的指针,缓存props对象中的key,将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的keyconst keys = vm.$options._propKeys = []//当前组件是否为根组件const isRoot = !vm.$parent// root instance props should be converted//如果不是,那么不需要将props数组转换为响应式的,toggleObserving(false)用来控制是否将数据转换成响应式if (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)//调用validateProp函数校验父组件传入的props数据类型是否匹配并获取到传入的值valueconst value = validateProp(key, propsOptions, propsData, vm)//将键和值通过defineReactive函数添加到props(即vm._props)中defineReactive(props, key, value)// static props are already proxied on the component's prototype// during Vue.extend(). We only need to proxy props defined at// instantiation here.//判断这个key在当前实例vm中是否存在,如果不存在,则调用proxy函数在vm上设置一个以key为属性的代码,当使用vm[key]访问数据时,其实访问的是vm._props[key]if (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)
}
function initMethods (vm: Component, methods: Object) {const props = vm.$options.propsfor (const key in methods) {if (process.env.NODE_ENV !== 'production') {//如果methods中某个方法只有key而没有方法体时,抛出异常:提示用户方法未定义if (typeof methods[key] !== 'function') {warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +`Did you reference the function correctly?`,vm)}//如果methods中某个方法名与props中某个属性名重复了,就抛出异常:提示用户方法名重复了if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`,vm)}//判断如果methods中某个方法名如果在实例vm中已经存在并且方法名是以_或$开头的,就抛出异常:提示用户方法名命名不规范if ((key in vm) && isReserved(key)) {warn(`Method "${key}" conflicts with an existing Vue instance method. ` +`Avoid defining component methods that start with _ or $.`)}}//将method绑定到实例vm上,然后通过this.xxx来访问methods选项中的xxx方法了vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}
function initData (vm: Component) {//获取到用户传入的data选项,赋给变量data,同时将变量data作为指针指向vm._data,然后判断data是不是一个函数,如果是就调用getData函数获取其返回值,将其保存到vm._data中。如果不是,就将其本身保存到vm._data中let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}//如果不是对象的话,就抛出警告:提示用户data应该是一个对象。if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {//判断data对象中是否存在某一项的key与methods中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复。if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}//判断是否存在某一项的key与prop中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)//调用proxy函数将data对象中key不以_或$开头的属性代理到实例vm上,这样,我们就可以通过this.xxx来访问data选项中的xxx数据了} else if (!isReserved(key)) {proxy(vm, `_data`, key)}}// observe data//调用observe函数将data中的数据转化成响应式observe(data, true /* asRootData */)
}
function initComputed (vm: Component, computed: Object) {// $flow-disable-line//义了一个变量watchers并将其赋值为空对象,同时将其作为指针指向vm._computedWatchersconst watchers = vm._computedWatchers = Object.create(null)// computed properties are just getters during SSRconst isSSR = isServerRendering()for (const key in computed) {//遍历computed选项中的每一项属性,首先获取到每一项的属性值,记作userDefconst userDef = computed[key]
//判断userDef是不是一个函数,如果是函数,则该函数默认为取值器getter,将其赋值给变量getter;如果不是函数,则说明是一个对象,则取对象中的get属性作为取值器赋给变量getterconst getter = typeof userDef === 'function' ? userDef : userDef.get//如果上面两种情况取到的取值器不存在,则抛出警告:提示用户计算属性必须有取值器if (process.env.NODE_ENV !== 'production' && getter == null) {warn(`Getter is missing for computed property "${key}".`,vm)}if (!isSSR) {// create internal watcher for the computed property.//判断如果不是在服务端渲染环境下,则创建一个watcher实例,并将当前循环到的的属性名作为键,创建的watcher实例作为值存入watchers对象中watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}// component-defined computed properties are already defined on the// component prototype. We only need to define computed properties defined// at instantiation here.//判断当前循环到的的属性名是否存在于当前实例vm上if (!(key in vm)) {//不存在,调用defineComputed函数为实例vm上设置计算属性defineComputed(vm, key, userDef)} else if (process.env.NODE_ENV !== 'production') {//存在,则在非生产环境下抛出警告if (key in vm.$data) {warn(`The computed property "${key}" is already defined in data.`, vm)} else if (vm.$options.props && key in vm.$options.props) {warn(`The computed property "${key}" is already defined as a prop.`, vm)}}}
}
defineComputed函数 该函数接受3个参数,分别是:target、key和userDef。其作用是为target上定义一个属性key,并且属性key的getter和setter根据userDef的值来设置
export function defineComputed (target: any,key: string,userDef: Object | Function
) {
//义了变量sharedPropertyDefinition,它是一个默认的属性描述符。
//定义了变量shouldCache,用于标识计算属性是否应该有缓存。该变量的值是当前环境是否为非服务端渲染环境,如果是非服务端渲染环境则该变量为true。也就是说,只有在非服务端渲染环境下计算属性才应该有缓存const shouldCache = !isServerRendering()//判断如果userDef是一个函数,则该函数默认为取值器getterif (typeof userDef === 'function') {//判断如果userDef是一个函数,则该函数默认为取值器getter,此处在非服务端渲染环境下并没有直接使用userDef作为getter,而是调用createComputedGetter函数创建了一个getter,这是因为userDef只是一个普通的getter,它并没有缓存功能,所以我们需要额外创建一个具有缓存功能的getter,而在服务端渲染环境下可以直接使用userDef作为getter,因为在服务端渲染环境下计算属性不需要缓存。由于用户没有设置setter函数,所以将sharedPropertyDefinition.set设置为noopsharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {//如果userDef不是一个函数,那么就将它当作对象处理。在设置sharedPropertyDefinition.get的时候先判断userDef.get是否存在,如果不存在,则将其设置为noop,如果存在,则同上面一样,在非服务端渲染环境下并且用户没有明确的将userDef.cache设置为false时调用createComputedGetter函数创建一个getter赋给sharedPropertyDefinition.get。然后设置sharedPropertyDefinition.set为userDef.set函数sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}if (process.env.NODE_ENV !== 'production' &&sharedPropertyDefinition.set === noop) {//如果用户没有设置setter的话,那么就给setter一个默认函数,这是为了防止用户在没有设置setter的情况下修改计算属性,从而为其抛出警告sharedPropertyDefinition.set = function () {warn(`Computed property "${key}" was assigned to but it has no setter.`,this)}}//调用Object.defineProperty方法将属性key绑定到target上,其中的属性描述符就是上面设置的sharedPropertyDefinitionObject.defineProperty(target, key, sharedPropertyDefinition)
}
createComputedGetter
该函数是一个高阶函数,其内部返回了一个computedGetter函数,所以其实是将computedGetter函数赋给了sharedPropertyDefinition.get。当获取计算属性的值时会执行属性的getter,而属性的getter就是 sharedPropertyDefinition.get,也就是说最终执行的 computedGetter函数
function createComputedGetter (key) {return function computedGetter () {//储在当前实例上_computedWatchers属性中key所对应的watcher实例const watcher = this._computedWatchers && this._computedWatchers[key]//如果watcher存在,则调用watcher实例上的depend方法和evaluate方法,并且将evaluate方法的返回值作为计算属性的计算结果返回if (watcher) {if (watcher.dirty) {watcher.evaluate()}if (Dep.target) {watcher.depend()}return watcher.value}}
}function createGetterInvoker(fn) {return function computedGetter () {return fn.call(this, this)}
}
watch的几种写法
watch: {a: function (val, oldVal) {console.log('new: %s, old: %s', val, oldVal)},// methods选项中的方法名b: 'someMethod',// 深度侦听,该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深c: {handler: function (val, oldVal) { /* ... */ },deep: true},// 该回调将会在侦听开始之后被立即调用d: {handler: 'someMethod',immediate: true},// 调用多个回调e: ['handle1',function handle2 (val, oldVal) { /* ... */ },{handler: function handle3 (val, oldVal) { /* ... */ },}],// 侦听表达式'e.f': function (val, oldVal) { /* ... */ }}
function initWatch (vm: Component, watch: Object) {
//在函数内部会遍历watch选项for (const key in watch) {//拿到每一项的key和对应的值handlerconst handler = watch[key]//判断handler是否为数组,如果是数组则循环该数组并将数组中的每一项依次调用createWatcher函数来创建watcherif (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])}//不是数组,则直接调用createWatcher函数来创建watcher} else {createWatcher(vm, key, handler)}}
}
function createWatcher (vm: Component,//当前实例keyOrFn: string | Function,//被侦听的属性表达式handler: any,//watch选项中每一项的值options?: Object//用于传递给vm.$watch的选项对象
) {
//判断handle是一个对象时,handler = handler.handlerif (isPlainObject(handler)) {options = handlerhandler = handler.handler}
//如果handle是一个字符串,则直接在实例上找if (typeof handler === 'string') {handler = vm[handler]}return vm.$watch(keyOrFn, handler, options)
}
分开解析-如果是一个对象
if (isPlainObject(handler)) {//将handler对象整体记作optionsoptions = handler//把handler对象中的handler属性作为真正的回调函数记作handlerhandler = handler.handler}
则为以下情况
watch:{c: {handler: function (val, oldVal) { /* ... */ },deep: true},// 该回调将会在侦听开始之后被立即调用d: {handler: 'someMethod',immediate: true},}
分开解析-如果handler为一个字符串
if (typeof handler === 'string') {handler = vm[handler]
}
则为以下情况
watch:{// methods选项中的方法名b: 'someMethod',}
Vue.prototype.$watch
//src\core\instance\state.js
Vue.prototype.$watch = function (expOrFn: string | Function,cb: any,options?: Object
): Function {const vm: Component = this
//判断回调函数是否是一个对象if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {cb.call(vm, watcher.value)}return function unwatchFn () {watcher.teardown()}
}
provide
var Parent = {provide: {foo: 'bar'},// ...
}const Provider = {provide () {return {[s]: 'foo'}}
}
export function initProvide (vm: Component) {const provide = vm.$options.provideif (provide) {vm._provided = typeof provide === 'function'? provide.call(vm): provide}
}
上一篇:linux console快捷键
下一篇:内核中的高精度定时器