vue版本基于vue2.6.12版本
一、入口文件:vue/src/core/index.js
下面是入口文件的一张思维导图
vue/src/core/index.js源代码解析:
1、初始化全局API:initGlobalAPI(Vue)
2、定义实例属性$isServer
3、定义实例属性$ssrContext
4、定义实例属性FunctionalRenderContext
5、定义私有属性Vue.version:定义当前版本号
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' // 初始化全局属性和全局方法 initGlobalAPI(Vue) // 定义实例属性:Vue.prototype.$isServer // 用于判断当前 Vue 实例是否运行于服务器 Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) // 定义实例属性:Vue.prototype.$ssrContext // 服务端渲染内容 Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation // 定义实例属性:Vue.prototype.FunctionalRenderContext Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) // 定义私有属性,实例不能访问 Vue.version = '__VERSION__' export default Vue
二、引进Vue对象:import Vue from 'src/core/instance/index.js
下面看下该instanc/index.js 的内容
new Vue(options)的时候会调用this._init(option)
1、定义Vue工厂函数
2、initMixin(Vue) // 定义内部方法_init:Vue.prototype._init
3、stateMixin(Vue) // 定义实例属性:Vue.prototype.$data, $props;实例方法:$set,$delete,$watch
4、eventsMixin(Vue) // 定义关于事件的实例方法:Vue.prototype.$on Vue.prototype.$off Vue.prototype.$once Vue.prototype.$emit
5、lifecycleMixin(Vue) // 定义内部方法_update;定义实例方法:$forceUpdate $destroy
6、renderMixin(Vue) // 定义实例方法$nextTick 、内部方法_render
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' // 定义Vue工厂函数 function Vue (options) { // 判断当前环境是否用new 初始化一个Vue对象 if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } // 初始化选项、生活周期钩子、自定义事件等 // _init方法在initMixin(Vue)中定义 this._init(options) } initMixin(Vue) // 定义内部方法_init:Vue.prototype._init stateMixin(Vue) // 定义实例属性:Vue.prototype.$data, $props;实例方法:$set,$delete,$watch eventsMixin(Vue) // 定义关于事件的实例属性:Vue.prototype.$on Vue.prototype.$off Vue.prototype.$once Vue.prototype.$emit lifecycleMixin(Vue) // 定义内部方法_update;定义实例方法:$forceUpdate $destroy renderMixin(Vue) // 定义实例方法$nextTick 、内部方法_render export default Vue
下面看下这些方法的具体定义
1、initMixin(Vue)------定义内部方法_init:Vue.prototype._init
源码位置:src/core/instance/init
_init函数的作用:
1)给私有属性_uid自增1,每个组件每一次初始化时做的一个唯一的私有属性标识
2)合并options,并赋值给实例属性$options
3)定义实例私有属性vm._self = vm ,用于访问实例的数据和方法
4)调用initLifecycle(vm) :确认组件的父子关系;初始化实例属性vm.$parent、 vm.$root 、vm.$children、 vm.$refs;初识化内部相关属性
5)调用initEvents(vm) :将父组件的自定义事件传递给子组件;初始化实例内部属性_events(事件中心)、_hasHookEvent;
6)调用initRender(vm) :提供将render
函数转为vnode
的方法;初始化实例属性$slots、$scopedSlots;定义实例方法$createElement;定义响应式属性:$attrs、$listeners;
7)调用callHook(vm, 'beforeCreate') , 执行组件的beforeCreate钩子
8)调用initInjections(vm) ,resolve injections before data/props
9)调用initState(vm) ,对实例的选项props、data、computed、watch、methods初始化
10)调用initProvide(vm) , resolve provide after data/props
11)调用callHook(vm, 'created')
12)如果选项有提供挂载钩子,则执行挂载;$options.el:vm.$mount(vm.$options.el)
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid 给属性_uid加1 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 // 设置属性_isVue = true避免被作为响应式观察对象 vm._isVue = true // merge options // 如果是函数式组件则调用initInternalComponent // 否则合并选项到实例属性$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 { // SFC 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 // 保存自己到内部属性_self initLifecycle(vm) // 初始化实例属性vm.$parent、 vm.$root 、vm.$children、 vm.$refs;初识化内部相关属性 initEvents(vm) // 初始化实例内部属性_events、_hasHookEvent;如果有事件监听,则进行更新 initRender(vm) // 初始化实例属性$slots、$scopedSlots;定义实例方法$createElement;定义响应式属性:$attrs、$listeners callHook(vm, 'beforeCreate') // 调用生命周期beforeCreate initInjections(vm) // resolve injections before data/props initState(vm) // 对实例的选项props、data、computed、watch、methods初始化 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) } } }
initLifecycle:src/coreinstance/lifecircle.js
export function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent // 找到第一个非抽象的祖先组件 let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } // 将第一个非抽象的祖先组件,赋值给实例属性$parent vm.$parent = parent // 初始化实例属性$root vm.$root = parent ? parent.$root : vm // 初始化实例属性$children vm.$children = [] // 初始化实例属性$refs vm.$refs = {} // 初始化内部属性 vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
initEvents:src/coreinstance/events.js
export function initEvents (vm: Component) { // 定义实例内部属性_events vm._events = Object.create(null) // 事件中心 // 定义实例内部属性_hasHookEvent vm._hasHookEvent = false // init parent attached events // 如果有事件监听,则进行更新 const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } }
initRender:src/coreinstance/render.js
export function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees const options = vm.$options const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) }, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => { !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) }, true) } else { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) } }
callHook:src/coreinstance/init.js
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] const info = `${hook} hook` if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { invokeWithErrorHandling(handlers[i], vm, null, vm, info) } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
initInjections:src/coreinstance/inject.js
export function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { toggleObserving(false) Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) toggleObserving(true) } }
initState:src/coreinstance/state.js
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 选项有props,用defineReactive将props的属性定义成响应式的属性 if (opts.props) initProps(vm, opts.props) // 选项有methods,给method绑定作用域 if (opts.methods) initMethods(vm, opts.methods) // 选项有data,则初始化data,给data的每个key添加观察者对象 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // 选项有computed,则对每个key添加watcher if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
initProvide:src/coreinstance/inject.js
export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } }
2、stateMixin(Vue) ------定义实例属性:Vue.prototype.$data, $props;实例方法:$set,$delete,$watch
源码位置:src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) { // flow somehow has problems with directly declared definition object // when using Object.defineProperty, so we have to procedurally build up // the object here. // 定义data选项 const dataDef = {} dataDef.get = function () { return this._data } // 定义props选项 const propsDef = {} propsDef.get = function () { return this._props } // 做校验 if (process.env.NODE_ENV !== 'production') { dataDef.set = function () { warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } // props属性是只读的,不能直接对其做修改 propsDef.set = function () { warn(`$props is readonly.`, this) } } Object.defineProperty(Vue.prototype, '$data', dataDef) // 定义实例属性:$data Object.defineProperty(Vue.prototype, '$props', propsDef) // 定义实例属性:$props Vue.prototype.$set = set // 定义实例方法:$set Vue.prototype.$delete = del // 定义实例方法:$delete // 定义实例方法:$watch 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 = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } return function unwatchFn () { watcher.teardown() } } }
3、eventsMixin----定义关于事件的实例方法:$on 、$off 、$once 、$emit
源码位置:src/core/instance/events.js
export function eventsMixin (Vue: Class<Component>) { const hookRE = /^hook:/ Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm } Vue.prototype.$once = function (event: string, fn: Function): Component { const vm: Component = this function on () { vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) return vm } Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { const vm: Component = this // all if (!arguments.length) { vm._events = Object.create(null) return vm } // array of events if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } // specific event const cbs = vm._events[event] if (!cbs) { return vm } if (!fn) { vm._events[event] = null return vm } // specific handler let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } return vm } Vue.prototype.$emit = function (event: string): Component { const vm: Component = this if (process.env.NODE_ENV !== 'production') { const lowerCaseEvent = event.toLowerCase() if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${hyphenate(event)}" instead of "${event}".` ) } } let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) const info = `event handler for "${event}"` for (let i = 0, l = cbs.length; i < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info) } } return vm } }
4、lifecycleMixin--------定义内部方法_update;定义实例方法:$forceUpdate $destroy
源码位置:src/core/instance/lifecircle.js
export function lifecycleMixin (Vue: Class<Component>) { Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. } // 调用watcher的update()ff Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } // 调用钩子beforeDestroy destroyed Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (#6759) if (vm.$vnode) { vm.$vnode.parent = null } } }
5、renderMixin--------定义实例方法$nextTick 、内部方法_render
源码位置:src/core/instance/render.js
export function renderMixin (Vue: Class<Component>) { // install runtime convenience helpers installRenderHelpers(Vue.prototype) // 定义实例方法$nextTick Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } // 定义内部方法_render Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } finally { currentRenderingInstance = null } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode } }
三、引进initGlobalAPI方法:import { initGlobalAPI } from 'src/core/global-api/index'
下面看下initGlobalAPI的定义
1)定义全局属性Vue.config:Object.defineProperty(Vue, 'config', configDef)
2)抛出一些全局工具API,不推荐使用:Vue.util = {warn,extend,mergeOptions,defineReactive}
3)定义全局API----set delete nextTick:Vue.set = set;Vue.delete = del;Vue.nextTick = nextTick
4)定义全局API observable:Vue.observable
5)定义全局属性options,并初始化该options的字段有:Vue.options.components Vue.options.directives Vue.options.filters
6)定义私有属性:Vue.options._base = Vue
7)定义全局API:Ve.use----------- initUse(Vue)
8)定义全局API:Vue.mixin------ initMixin(Vue)
9)定义全局API:Vue.extend---- initExtend(Vue)
10)定义全局API:Vue.component 、Vue.filter 、Vue.directive------ initAssetRegisters(Vue)
/* @flow */ import config from '../config' import { initUse } from './use' import { initMixin } from './mixin' import { initExtend } from './extend' import { initAssetRegisters } from './assets' import { set, del } from '../observer/index' import { ASSET_TYPES } from 'shared/constants' import builtInComponents from '../components/index' import { observe } from 'core/observer/index' import { warn, extend, nextTick, mergeOptions, defineReactive } from '../util/index' export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // 定义全局属性config // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. // 抛出一些全局工具API,不推荐使用 Vue.util = { warn, extend, mergeOptions, defineReactive } // 定义全局API:set delet Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 定义全局属性options的字段:components,directives,filters Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue // 扩展全局属性options.components extend(Vue.options.components, builtInComponents) initUse(Vue) // 定义全局API:Ve.use initMixin(Vue) // 定义全局API:Vue.mixin initExtend(Vue) // 定义全局API:Vue.extend initAssetRegisters(Vue) // 定义全局API:Vue.component Vue.filter Vue.directive }
下面看下在initGlobal方法中用到的方法
1、定义全局属性Vue.config
源码:src/core/global-api/index.js
const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // 定义全局属性config
2、抛出一些全局工具API,不推荐使用:Vue.util = {warn,extend,mergeOptions,defineReactive}
Vue.util.warn------warn源码:src/core/util/debug.js
Vue.util.extend------extend源码:src/shared/util.js
Vue.util.mergeOptions------mergeOptions源码:src/core/util/options.js
Vue.util.defineReactive------defineReactive源码:src/core/observer/index.js
extend:src/shared/util.js
/** * Mix properties into target object. */ export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to }
mergeOptions:src/core/util/options.js
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */ 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 } normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. 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) } } } const options = {} let key for (key in parent) { mergeField(key) } 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 }
defineReactive:src/core/observer/index.js
/** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { // 如果设置的key已经存在obj上,并且不可配置,则直接返回 return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
下面看下dependArray的源码:src/core/observer/index.js
/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }
3、定义全局API:Vue.set、Vue.delete、Vue.nextTick------ Vue.set = set;Vue.delete = del;Vue.nextTick = nextTick
Vue.set,set源码----src/core/observer/index.js
给对象或者array添加属性,如果是响应式则定义成响应式,否则为非响应式数据
1)检查target类型不能为一般类型
2)target为array,并且key是有效索引,则通过splice添加元素
3)target为对象,key不在Object.prototype上,则直接赋值
4)避免在Vue的根实例添加属性
5)非响应式数据,直接赋值
6)利用defineReactive定义响应式数据,并通知watcher
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) // target不能为空,不能为number string boolean set ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { // 检查是否为数组,是否为有效的索引 target.length = Math.max(target.length, key) // 修改数组长度,避免索引值大于原数组长度 target.splice(key, 1, val) // 填充值 return val // 返回传入的值 } if (key in target && !(key in Object.prototype)) { // 对象:检查key已经是对象自身属性,则直接赋值,非响应式 target[key] = val return val // 返回传入的值 } const ob = (target: any).__ob__ // target的原型 if (target._isVue || (ob && ob.vmCount)) { // 避免在Vue实例或者跟数据里面添加响应式属性 process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { // target原型为null,则直接赋值,返回传入的值; target[key] = val // 即非响应式数据,不需要将其变成响应式的 return val } defineReactive(ob.value, key, val) // 定义响应式属性 ob.dep.notify() return val }
Vue.delete,del源码----src/core/observer/index.js
/** * Delete a property and trigger change if necessary. */ export function del (target: Array<any> | Object, key: any) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) // 减产target是否为Object Array ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { // 检查target是否为Array,key是否为有效索引 target.splice(key, 1) // 利用splice删除元素 return } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { // 有__ob__属性,表示是根实例root,则不能对他操作 process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } if (!hasOwn(target, key)) { // 不是自身属性,直接返回 return } delete target[key] // 利用delete删除对象自身属性 if (!ob) { return } ob.dep.notify() // 通知watcher更新 }
Vue.nextTick,nextTick源码----src/core/util/next-tick.js
1)将nextTick的参数cb,放到容器里面
2)利用微任务或者宏任务实现延迟执行cb功能:Promise 、MutationObserver、setImmediate、setTimeout
/* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' import { handleError } from './error' import { isIE, isIOS, isNative } from './env' export let isUsingMicroTask = false const callbacks = [] let pending = false function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // Fallback to setImmediate. // Technically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
4、定义全局API observable:Vue.observable
// 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj }
observe源码:src/core/observer/index.js
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ // 具有__ob__属性,表示已经定义成响应式数据了,避免重复定义 } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
Observer源码:src/core/observer/index.js
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */ export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { // value为数组 if (hasProto) { // export const hasProto = '__proto__' in {},可以访问__proto__属性 protoAugment(value, arrayMethods) // arrayMethods原型为Array.prototype的空{},value.__proto__ = arrayMethods } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { // 其他类型,实际针对的是Object this.walk(value) } } /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ // 响应式观察一个对象的property walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * Observe a list of Array items. */ // 响应式观察一个数组的属性 observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
这里用到的了arrayMethods,下面看下:import { arrayMethods } from './array'的代码
src/core/observer/array.js
1)定义一个原型为Array的原型的对象并导出:arrayMethods
2)用def方法重写Array里面改变原数组的方法,用于响应式处理---重写方法有:push pop shift unshift splice sort reverse
a)先调用数组方法
b)如果是新增元素,则调用observeArray方法
c)ob.dep.notify(),通知watcher更新
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from '../util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // 定义一个原型为Array的原型的对象 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ // 重写Array里面改变原数组的方法,用于响应式处理 methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted // 如果是插入元素,则调用observeArray switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change // 通知watcher更新 ob.dep.notify() return result }) })
下面看下def的源码:import { def } from '../util/index'-----src/core/util/lang.js
用Object.defineProperty给数组的方法添加属性
/** * Define a property. */ export function def (obj: Object, key: string, val: any, enumerable?: boolean) { // 从这里可以看出,数组的方法响应式的实现也是利用Object.defineProperty实现 Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) }
下面看下observeArray的源码:src/core/observer/index.js
用observe方法观察重写数组的方法
/** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }
下面看下Dep的源码:src/core/observer/dep.js
1)定义Dep类
2)定义静态属性:Dep.target--------储存最新的watcher
3)实例属性:id---每一个Dep实例对象的标识、subs---收集watcher
4)addSub(sub)-----添加新的watcher
5)removeSub(sub)-----删除watcher
6)depend()----添加新的依赖
7)notify()----通知订阅者更新:调用watcher的 update()
/* @flow */ import type Watcher from './watcher' import { remove } from '../util/index' import config from '../config' let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. */ export default class Dep { static target: ?Watcher; // 静态属性,可以被子类继承,不能对实例访问 id: number; // 实例属性 subs: Array<Watcher>; constructor () { this.id = uid++ // this.subs = [] // 收集watcher的容器 } addSub (sub: Watcher) { this.subs.push(sub) // 添加新的watcher } removeSub (sub: Watcher) { remove(this.subs, sub) // 删除watcher } depend () { // 添加新的依赖 if (Dep.target) { Dep.target.addDep(this) } } notify () { // 通知watcher更新 // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) // 升序排序 } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 通知更新 } } } // The current target watcher being evaluated. // This is globally unique because only one watcher // can be evaluated at a time. Dep.target = null // 静态属性,不会被实例继承,target的值永远是最新进栈的值 const targetStack = [] // 收集watcher export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
下面看下Watcher的源码:src/core/observer/watcher.js
/* @flow */ import { warn, remove, isObject, parsePath, _Set as Set, handleError, noop } from '../util/index' import { traverse } from './traverse' import { queueWatcher } from './scheduler' import Dep, { pushTarget, popTarget } from './dep' import type { SimpleSet } from '../util/index' let uid = 0 /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb // 回调,执行视图的更新 this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) // 设置Dep.target的值,依赖收集 let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ 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 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { // 更新 /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { // 调用diff,更新视图 if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
下面看下traverse方法的源码:import { traverse } from './traverse'-----src/core.observer/traverse.js
1)调用方法_traverse:循环
2)利用Set对象,添加属性
/* @flow */ import { _Set as Set, isObject } from '../util/index' import type { SimpleSet } from '../util/index' import VNode from '../vdom/vnode' const seenObjects = new Set() /** * Recursively traverse an object to evoke all converted * getters, so that every nested property inside the object * is collected as a "deep" dependency. */ export function traverse (val: any) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse (val: any, seen: SimpleSet) { let i, keys const isA = Array.isArray(val) // val如果是数组、Object、冻结属性、VNode的实例对象,则直接返回undefined,什么都不做 if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } // 如果有__ob__属性,表示是一个响应式对象 // 如果已经设置过观察,则直接返回;否则添加该idepId if (val.__ob__) { const depId = val.__ob__.dep.id if (seen.has(depId)) { return } seen.add(depId) } // 如果val是数组,则循环递归每一个元素都调用一边_traverse if (isA) { i = val.length while (i--) _traverse(val[i], seen) } else { // 如果是Object,则每一个属性都调用一遍_traverse keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
下面看下_set方法的原始代码:src/core/util/env.js
_set其实就是兼容Set数据类型,如果没有Set类型,则polyfill,这里的polyfill,只是简单的重写了:has add clear方法
let _Set /* istanbul ignore if */ // $flow-disable-line if (typeof Set !== 'undefined' && isNative(Set)) { // use native Set when available. _Set = Set } else { // a non-standard Set polyfill that only works with primitive keys. _Set = class Set implements SimpleSet { set: Object; constructor () { this.set = Object.create(null) } has (key: string | number) { return this.set[key] === true } add (key: string | number) { this.set[key] = true } clear () { this.set = Object.create(null) } } } export interface SimpleSet { has(key: string | number): boolean; add(key: string | number): mixed; clear(): void; } export { _Set }
下面看下queueActivatedComponent、queueWatcher的源码:
/* @flow */ import type Watcher from './watcher' import config from '../config' import { callHook, activateChildComponent } from '../instance/lifecycle' import { warn, nextTick, devtools, inBrowser, isIE } from '../util/index' export const MAX_UPDATE_COUNT = 100 const queue: Array<Watcher> = [] const activatedChildren: Array<Component> = [] let has: { [key: number]: ?true } = {} let circular: { [key: number]: number } = {} let waiting = false let flushing = false let index = 0 /** * Reset the scheduler's state. */ function resetSchedulerState () { index = queue.length = activatedChildren.length = 0 has = {} if (process.env.NODE_ENV !== 'production') { circular = {} } waiting = flushing = false } // Async edge case #6566 requires saving the timestamp when event listeners are // attached. However, calling performance.now() has a perf overhead especially // if the page has thousands of event listeners. Instead, we take a timestamp // every time the scheduler flushes and use that for all event listeners // attached during that flush. export let currentFlushTimestamp = 0 // Async edge case fix requires storing an event listener's attach timestamp. let getNow: () => number = Date.now // Determine what event timestamp the browser is using. Annoyingly, the // timestamp can either be hi-res (relative to page load) or low-res // (relative to UNIX epoch), so in order to compare time we have to use the // same timestamp type when saving the flush timestamp. // All IE versions use low-res event timestamps, and have problematic clock // implementations (#9632) if (inBrowser && !isIE) { const performance = window.performance if ( performance && typeof performance.now === 'function' && getNow() > document.createEvent('Event').timeStamp ) { // if the event timestamp, although evaluated AFTER the Date.now(), is // smaller than it, it means the event is using a hi-res timestamp, // and we need to use the hi-res version for event listener timestamps as // well. getNow = () => performance.now() } } /** * Flush both queues and run the watchers. */ function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } } function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) { callHook(vm, 'updated') } } } /** * Queue a kept-alive component that was activated during patch. * The queue will be processed after the entire tree has been patched. */ export function queueActivatedComponent (vm: Component) { // setting _inactive to false here so that a render function can // rely on checking whether it's in an inactive tree (e.g. router-view) vm._inactive = false activatedChildren.push(vm) } function callActivatedHooks (queue) { for (let i = 0; i < queue.length; i++) { queue[i]._inactive = true activateChildComponent(queue[i], true /* true */) } } /** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true 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) } // queue the flush if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) } } }
5、定义全局属性Vue.options,并初始化该options的字段有:Vue.options.components Vue.options.directives Vue.options.filters
// 定义全局属性options的字段:components,directives,filters Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // ASSET_TYPES export const ASSET_TYPES = [ 'component', 'directive', 'filter' ]
6、定义全局API:Ve.use----------- initUse(Vue)
initUse(Vue)源码:src/core/global-api/use.js
1)判断插件是否已经注册过,如果已经注册,则直接返回this
2)将第一个参数以后的参数归集,并且将Vue作为Vue作为数组的第一项
3)如果参数plugin是object,并有install方法,则将处理好的参数传给该install方法,并调用
4)如果参数plugin是functin,则将处理好的参数传给它,并直接调用
5)缓存注册的插件
export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { // 判断是否缓存过该插件,是的话直接返回,不再注册 return this } // additional parameters const args = toArray(arguments, 1) // 处理入参,将第一个参数以后的参数归集成数组。排除调use方法的第一个参数是因为:uese()方法的第一个参数是插件本身,后面的参数才是参数真正需要的参数 args.unshift(this) // this指向Vue,即将Vue作为数组参数的第一个值 if (typeof plugin.install === 'function') { // 对象有install方法,则调用install方法 plugin.install.apply(plugin, args) // 修改this指向为plugin自身,传入处理好的数组参数 } else if (typeof plugin === 'function') { // plugin为function,直接调用该function plugin.apply(null, args) // this指向为null,传入处理好的数组参数 } installedPlugins.push(plugin) // 缓存刚注册的插件,避免成功父注册 return this } }
7、定义全局API:Vue.mixin------ initMixin(Vue)
initMixin(Vue)源码:src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) { Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin) // 选项合并 return this } }
下面看下vue内部组件keep-alive是如何实现的
看下keep-alive对外暴露的对象:
1)在keep-alive组件中定义了3个生命周期函数:
a)created:初始化两个对象this..cache、this.keys,分别缓存VNode(虚拟DOM)和VNode对应的键集合
b)mounted:对include和exclude参数进行监听,然后实时地更新(删除)this.cache对象数据
c)destroyed:删除this.cache中缓存的VNode实例。这不是简单地将this.cache置为null,而是遍历调用pruneCacheEntry函数删除。删除缓存的VNode还要对应组件实例的destory钩子函数
2)name属性,abstract=true---表示不将虚拟DOM渲染成真是DOM,props:{include, exclude,max}
3)render函数
/* @flow */ import { isRegExp, remove } from 'shared/util' import { getFirstComponentChild } from 'core/vdom/helpers/index' type VNodeCache = { [key: string]: ?VNode }; function getComponentName (opts: ?VNodeComponentOptions): ?string { return opts && (opts.Ctor.options.name || opts.tag) } function matches (pattern: string | RegExp | Array<string>, name: string): boolean { if (Array.isArray(pattern)) { return pattern.indexOf(name) > -1 } else if (typeof pattern === 'string') { return pattern.split(',').indexOf(name) > -1 } else if (isRegExp(pattern)) { return pattern.test(name) } /* istanbul ignore next */ return false } function pruneCache (keepAliveInstance: any, filter: Function) { const { cache, keys, _vnode } = keepAliveInstance for (const key in cache) { const cachedNode: ?VNode = cache[key] if (cachedNode) { const name: ?string = getComponentName(cachedNode.componentOptions) if (name && !filter(name)) { pruneCacheEntry(cache, key, keys, _vnode) } } } } function pruneCacheEntry ( cache: VNodeCache, key: string, keys: Array<string>, current?: VNode ) { const cached = cache[key] if (cached && (!current || cached.tag !== current.tag)) { cached.componentInstance.$destroy() } cache[key] = null remove(keys, key) } const patternTypes: Array<Function> = [String, RegExp, Array] export default { name: 'keep-alive', abstract: true, // 是否需要将当前虚拟DOM渲染成真实DOM的关键 props: { include: patternTypes, // 缓存白名单,缓存命中的组件 exclude: patternTypes, // 缓存黑名单,命中组件不缓存 max: [String, Number] // 定义缓存组件上限,超出上限使用LRU的策略置换缓存数据 }, created () { this.cache = Object.create(null) // 缓存虚拟DOM this.keys = [] // 缓存的虚拟DOM的的键集合 }, destroyed () { // 删除所有的缓存 for (const key in this.cache) { pruneCacheEntry(this.cache, key, this.keys) } }, mounted () { // 实时监听黑白名单 this.$watch('include', val => { pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)) }) }, render () { const slot = this.$slots.default const vnode: VNode = getFirstComponentChild(slot) // 找到第一个子组件对象 const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // 存在组件参数 // check pattern const name: ?string = getComponentName(componentOptions) // 组件名 const { include, exclude } = this if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } const { cache, keys } = this const key: ?string = vnode.key == null // 定义组件的缓存key // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key if (cache[key]) { // 已经缓存过该组件 vnode.componentInstance = cache[key].componentInstance // make current key freshest remove(keys, key) // 调整key排序 keys.push(key) } else { cache[key] = vnode // 缓存组件对象 keys.push(key) // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除 pruneCacheEntry(cache, keys[0], keys, this._vnode) } } vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到 } return vnode || (slot && slot[0]) } }