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 interf以上是关于vue2.x源码学习的主要内容,如果未能解决你的问题,请参考以下文章