在vue源码中/src/core/instance/index.js
会有实例的初始化代码
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'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
其中定义了vue构造函数,然后分别调用了initMixin, stateMixin, eventsMixin,lifecycleMixin,renderMixin 这五个函数,然后将vue构造函数作为参数传递给这个五个函数。
这个五个函数的作用 就是向Vue 的原型中挂载方法。
初始化方法 initMixin
/* @flow */
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
...
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
...
可以看到当 initMixin被调用是,Vue构造函数的prototype属性会被添加_init方法。 在new Vue()会调用this._init()方法,实现一系列初始化操作。
数据相关的实例方法
在stateMixin被调用是,会向 vue构造函数 的prototype属性挂载与数据相关的实例方法。 vm.$watch
, vm.$set
, vm.$delete
. 并且会挂载上 $data
和 $props
state.js
...
import {
set,
del,
observe,
defineReactive,
toggleObserving
} from '../observer/index'
...
export function stateMixin (Vue: Class) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
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
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
...
事件相关的实例方法
在 eventsMixn中 会实现 vm.$on
vm.$off
vm.$once
vm.$emit
这个四个方法。
export function eventsMixin (Vue: Class) {
Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
Vue.prototype.$once = function (event: string, fn: Function): Component {
Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
Vue.prototype.$emit = function (event: string): Component {
...
}
vm.$on的实现
使用: vm.$on(event, callback)
参数:
- {String | Array
}event - {Function} callback
用户: 监听实例上的自定义事件,事件由$emit触发。回调函数会接受所有传入事件 所触发的函数的 额外参数
const hookRE = /^hook:/
// vm.$on(event, callback)
// 监听实例上的自定义事件,事件由$emit触发
Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
const vm: Component = this
// 如果是参数是数组 遍历数组 ,每一项都递归调用vm.$on,使回调 可以被 数组中 每项事件名对应的 事件列表 监听
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 不是数组 判断 vm._events中是否存在,不存在 设置为空数组 然后传入回调
// _events是一个对象,对象上每一个 key(事件名)对应的都是 一个数组 ,数组中是监听的事件
(vm._events[event] || (vm._events[event] = [])).push(fn)
// ???todo
// 而不是哈希查找hook:event cost 使用在注册时标记的布尔标志
// 而不是散列查找
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
vm.$off的实现
使用 vm.$off([event, callback])
参数:
- {String | Array
} event - {Function} callback
用法: 移除自定义事件监听器
- 如果没有传递参数, 则移除所有的事件监听器
- 如果只传了事件名,则移除该事件所有的 事件监听器
- 如果都传了,移除对应这个回调的监听器监听器
其中 移除对应回调的事件监听器, 用的是倒序循环移除,好处是不会改变未被移除事件的位置
Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
const vm: Component = this
// 如果没有传参数 ,移除所有的事件监听器
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// 数组,遍历每一项 递归调用 vm.$off 移除对应的监听器
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// 不是数组 获取 当前对应的 需要移除监听器的数组
// vm._evnets是一个对象
const cbs = vm._events[event]
// 如果 不存在,说明没有被 监听过,直接返回实例
if (!cbs) {
return vm
}
// 如果 没有传入需要移除的 监听器,那么移除该事件所有的监听器
if (!fn) {
vm._events[event] = null
return vm
}
// 存在需要移除的监听事件
// 倒序循环 移除对应的事件
// 倒序的好处是 splice之后 ,不会影响未被遍历到的监听器位置
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
vm.$once
使用 vm.$once(event, callbakc)
参数:
- {String | Arrray
} event - {Function} callback
用法: 监听一个自定义事件,但是只触发一次,触发之后移除监听器
具体实现其实就是 在vm.on 实现监听自定义事件,当自定义事件触发后,执行拦截器,将监听器移除
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
// 包装 fn事件, 调用的时候 ,移除自身。并执行 fn函数
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
// 把fn函数挂载到 on事件上
// vm.$off取消监听时 会判断 cb===fn || cb.fn === fn
on.fn = fn
// 监听 包装后的on 事件
vm.$on(event, on)
return vm
}
vm.$emit
使用: vm.$emit(event, [...args])
参数:
- {String} event
- [...args]
用法: 触发当前实例上的事件,附加参数会传给监听器回调
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) {
// toArray 把类似于数组的数据转换为真正的数组
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// toArray 把类似于数组的数据转换为真正的数组, 它的第二个参数是起始位置
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
// 遍历调用 invokeWithErrorHandling 处理监听器
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
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)`))
// 避免在嵌套调用时多次触发catch
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
生命周期相关的实例方法
与生命周期相关的实例方法有4个, 分别是 vm.$mount
, vm.$forceUpdate
, vm.$nextTick
, vm.$destroy
其中lifecycleMixin中 挂载到 vue 实例的有vm.$forceUpdate
,vm.$destroy
vm.$forceUpdate
作用是 是vue实例重新渲染,仅仅影响实例本身以及插入插槽内容的子组件,而不是 所有子组件。
vue的每一个实例都有一个watcher, 当状态发生变化是,会通知组件级别,然后组件内使用虚拟DOM进行更详细的渲染操作。
原理就是手动通知vue实例重新渲染
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
// 如果实例有 watcher 则 手动 触发 watcher的update方法 重新渲染
if (vm._watcher) {
vm._watcher.update()
}
}
vm.$destory
// 销毁实例
Vue.prototype.$destroy = function () {
const vm: Component = this
// 防止 被重复调用 如果_isBeingDestroyed为true,则正在销毁中或者已经被销毁了
if (vm._isBeingDestroyed) {
return
}
// 触发 beforeDestroy 的钩子函数
callHook(vm, 'beforeDestroy')
// 设置_isBeingDestroyed 为ture 标识正在销毁中
vm._isBeingDestroyed = true
// 移除当前组件与 父组件的连接 (父组件中删除 自己)
const parent = vm.$parent
// 父组件存在 && 父组件没有被销毁 && 不是抽象组件
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// vm._watcher 是vue实例上的,监听组件中的所有状态(状态的依赖列表)
// 从所有依赖项的Dep 列表中 将自己移除 (从其他dep中移除当前实例的watcher)
// 移除之后,当状态发生变化时,watcher实例就不会再得到通知了
if (vm._watcher) {
vm._watcher.teardown()
}
// vm._watchers 是用户调用vm.$watche创建的 的依赖列表
let i = vm._watchers.length
while (i--) {
// 也跳跃watcher实例的teardown 移除自身
vm._watchers[i].teardown()
}
// ???todo
// 从数据对象中删除引用
// 冻结对象可能没有观察者。
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// 添加_isDestroyed 属性,标识vue实例已经被销毁
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
// 不会将已经渲染到页面的DOM节点移除,但是会把模板中的所有指令解绑
vm.__patch__(vm._vnode, null)
// 触发destroyed钩子
callHook(vm, 'destroyed')
// 移除所有的事件监听器
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// todo??? 发布循环引用
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
vm.$nexttick
这里需要知道宏任务和微任务的区别。
然后
// 标志 使用微任务
export let isUsingMicroTask = false
// 需要 执行的 函数
const callbacks = []
// 是否 nexttick执行中 只能有一个pending
let pending = false
...
// nextTick 多次使用 ,callbacks中会有多个回调放到 微任务或宏任务中 等待执行,
// pending 控制 nextTick只会执行一次,执行完之后callbacks 置空,pending 重置
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// callbacks中推入 自定义 函数 ()=>{}
callbacks.push(() => {
// 如果有 回调
if (cb) {
try {
// 执行自定义函数时 ,执行 回调
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
// 如果没有传回调 ,执行自定义函数的时候, 执行 _resolve (即下面 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then)
_resolve(ctx)
}
})
// 如果pending 为false, 可以执行
// timerFunc 会返回一个 promise.then微任务 ,挂起不会执行 ,当调用nextTick的那一层 宏任务执行完,才 会执行timerFunc 里面的微任务 。 而这个微任务 执行 一个函数 flushCallbacks ,flushCallbacks把callback里面的回调全部执行并清空
if (!pending) {
// pending 为true , 执行中。(只能有一个nextTicke)
pending = true
// 执行 nextTick处理的微任务或者宏任务
timerFunc()
}
// 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
看一下timerFunc
// 需要执行的 任务
let timerFunc
// 如果有 支持Promise , timerFunc 返回 为微任务
// 执行到微任务会挂起 ,不会立即执行 ,等待当前宏任务或微任务执行完之后,才会执行 挂起的微任务
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// ios 中 回调被推送到微任务队列中,但队列没有被刷新,直到浏览器需要执行其他一些工作时
// 添加一个空计时器来“强制”刷新微任务队列。
if (isIOS) setTimeout(noop)
}
// 微任务标识 为true
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
// 非 ie 且 支持 MutationObserver(提供了监视对DOM树所做更改的能力)
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 微任务 MutationObserver
let counter = 1
// 当观察到变动时执行的回调函数 flushCallbacks
const observer = new MutationObserver(flushCallbacks)
// 选择需要观察变动的节点
const textNode = document.createTextNode(String(counter))
// observe 配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
// {characterData: true} 观察器的配置(需要观察什么变动)
observer.observe(textNode, {
characterData: true
})
// 执行 timerFunc 时, 修改节点, 节点的变通会 通知回调函数 flushCallbacks执行
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
// 微任务标识 为true
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 上面不支持 返回宏任务 setImmediate(比setTimeout更好)
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
// 都不支持 返回宏任务 用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
微任务真正执行的列表
// 执行 nexttick传入的函数
function flushCallbacks () {
// 重置pending
pending = false
// 拷贝 callbacks 后置空
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
next-tick.js源码
/* @flow */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 标志 使用微任务
export let isUsingMicroTask = false
// 需要 执行的 函数
const callbacks = []
// 是否 nexttick执行中 只能有一个pending
let pending = false
// 执行 nexttick传入的函数
function flushCallbacks () {
// 重置pending
pending = false
// 拷贝 callbacks 后置空
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 需要执行的 任务
let timerFunc
// 如果有 支持Promise , timerFunc 返回 为微任务
// 执行到微任务会挂起 ,不会立即执行 ,等待当前宏任务或微任务执行完之后,才会执行 挂起的微任务
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// ios 中 回调被推送到微任务队列中,但队列没有被刷新,直到浏览器需要执行其他一些工作时
// 添加一个空计时器来“强制”刷新微任务队列。
if (isIOS) setTimeout(noop)
}
// 微任务标识 为true
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
// 非 ie 且 支持 MutationObserver(提供了监视对DOM树所做更改的能力)
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 微任务 MutationObserver
let counter = 1
// 当观察到变动时执行的回调函数 flushCallbacks
const observer = new MutationObserver(flushCallbacks)
// 选择需要观察变动的节点
const textNode = document.createTextNode(String(counter))
// observe 配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
// {characterData: true} 观察器的配置(需要观察什么变动)
observer.observe(textNode, {
characterData: true
})
// 执行 timerFunc 时, 修改节点, 节点的变通会 通知回调函数 flushCallbacks执行
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
// 微任务标识 为true
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 上面不支持 返回宏任务 setImmediate(比setTimeout更好)
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
// 都不支持 返回宏任务 用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// nextTick 多次使用 ,callbacks中会有多个回调放到 微任务或宏任务中 等待执行,
// pending 控制 nextTick只会执行一次,执行完之后callbacks 置空,pending 重置
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// callbacks中推入 自定义 函数 ()=>{}
callbacks.push(() => {
// 如果有 回调
if (cb) {
try {
// 执行自定义函数时 ,执行 回调
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
// 如果没有传回调 ,执行自定义函数的时候, 执行 _resolve (即下面 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then)
_resolve(ctx)
}
})
// 如果pending 为false, 可以执行
// timerFunc 会返回一个 promise.then微任务 ,挂起不会执行 ,当调用nextTick的那一层 宏任务执行完,才 会执行timerFunc 里面的微任务 。 而这个微任务 执行 一个函数 flushCallbacks ,flushCallbacks把callback里面的回调全部执行并清空
if (!pending) {
// pending 为true , 执行中。(只能有一个nextTicke)
pending = true
// 执行 nextTick处理的微任务或者宏任务
timerFunc()
}
// 没有传入回调,但是支持promise,返回new promise, nextTick可以使用then
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}