vue2.x源码学习
Posted 赏花赏景赏时光
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2.x源码学习相关的知识,希望对你有一定的参考价值。
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/core/instance/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)
对实例的computed的实例化,computed的源码:src/core/instance/state.js
const computedWatcherOptions = lazy: true
function initComputed (vm: Component, computed: Object)
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed)
const userDef = computed[key]
const 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.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // lazy:true,默认不执行用户定义的方法
)
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) // 实例上还没有该属性,将属性定义到实例上
defineComputed(vm, key, userDef)
else if (process.env.NODE_ENV !== 'production') // 即computed里面的属性不能和data props中的属性重复
if (key in vm.$data) // 属性已经在data选项中定义过,则警告
warn(`The computed property "$key" is already defined in data.`, vm)
else if (vm.$options.props && key in vm.$options.props) // 在props上定义过,则警告
warn(`The computed property "$key" is already defined as a prop.`, vm)
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
)
const shouldCache = !isServerRendering()
if (typeof userDef === 'function')
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
else
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop)
sharedPropertyDefinition.set = function ()
warn(
`Computed property "$key" was assigned to but it has no setter.`,
this
)
Object.defineProperty(target, key, sharedPropertyDefinition)
function createComputedGetter (key) // 创建getter,当取值时会执行该方法
return function computedGetter ()
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher)
if (watcher.dirty)
watcher.evaluate() // 计算结果,计算时会进行依赖收集,dirty更改为false
if (Dep.target)
watcher.depend()
return watcher.value
function createGetterInvoker(fn)
return function computedGetter ()
return fn.call(this, this)
function initMethods (vm: Component, methods: Object)
const props = vm.$options.props
for (const key in methods)
if (process.env.NODE_ENV !== 'production')
if (typeof methods[key] !== 'function')
warn(
`Method "$key" has type "$typeof methods[key]" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
if (props && hasOwn(props, key))
warn(
`Method "$key" has already been defined as a prop.`,
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 $.`
)
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
initProvide:src/core/instance/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()
set、delete源码:src/core/observer/index.js
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) // 填充值,利用数组的splice变异方法触发响应式
return val // 返回传入的值
if (key in target && !(key in Object.prototype)) // 对象:检查key已经是对象自身属性,则直接赋值,响应式
target[key] = val
return val // 返回传入的值
const ob = (target: any).__ob__ // target有__ob__表示已经被添加过响应式处理
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[key] = val // target即非响应式数据,不需要将其变成响应式的,则直接赋值,返回传入的值;
return val
defineReactive(ob.value, key, val) // 定义响应式属性
ob.dep.notify() // 通知
return val
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更新
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)) // event为数组,则循环遍历调用$on
for (let i = 0, l = event.length; i < l; i++)
vm.$on(event[i], fn)
else // event为string,则将监听事件和回调函数添加到事件处理中心_events对象中
(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) // 通过$on方法注册事件
return vm
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component
const vm: Component = this
// all,调用this.$off()没有传参数,则清空事件处理中心缓存的事件及其回调
if (!arguments.length)
vm._events = Object.create(null)
return vm
// array of events,events为array,则循环遍历调用$off
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) // 如果调用$off时,没有传回调函数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) // 如果入参fn === 缓存的回调函数,或者入参fn === 缓存的cb.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
invokeWithErrorHandling源码位置:src/core/util/error.js
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
)
let res
try
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled)
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
catch (e)
handleError(e, vm, info)
return res
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()
以上是关于vue2.x源码学习的主要内容,如果未能解决你的问题,请参考以下文章