其他章节请看:

vue 快速入门 系列

实例方法(或 property)和静态方法

Vue(自身) 项目结构 一文中,我们研究了 vue 项目自身构建过程,也知晓了 import Vue from 'core/index' 就是引入 vue 的核心代码,该文件的前两行对应着 vue 的实例方法和静态方法(或称全局方法),本篇就和大家一起来看一下这两部分。

// vuev2.5.20/src/core/index.js

// 返回 Vue 构造函数,并准备好实例方法
import Vue from './instance/index'
// 静态方法(或称全局方法)
import { initGlobalAPI } from './global-api/index'
...

Tipvuev2.5.20 只是 vue 克隆到本地的项目名,下面将默认是这个项目。

实例方法(instance/index.js)

instance/index.js 的内容并不复杂,全部代码不到 30 行:

// 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)
} // 初始化相关。每次调用 new Vue() 就会被执行,里面包含很多初始化操作
initMixin(Vue)
// 状态相关。数据相关更确切
stateMixin(Vue)
// 事件相关
eventsMixin(Vue)
// 生命周期相关
lifecycleMixin(Vue)
// 渲染相关
renderMixin(Vue) export default Vue

首先定义 Vue 构造函数,接着调用initMixin()stateMixin()等 5 个方法,给 Vue 的原型添加方法(或 property)。以下是 initMixin()stateMixin() 的代码片段:

export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
}
}
export function stateMixin (Vue: Class<Component>) {
...
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set
Vue.prototype.$delete = del Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
...
}
}

剩余的三个方法也类似,也是给 Vue 的 prototype 增加方法。

以下是每个方法中在 Vue 的原型上定义的方法或 property:

  1. initMixin(Vue) - _init()
  2. stateMixin(Vue) - $data$props$set()$delete()$watch()
  3. eventsMixin(Vue) - $on()$once()$off()$emit()
  4. lifecycleMixin(Vue) - _update()$forceUpdate()$destroy()
  5. renderMixin(Vue) - $nextTick()_render()

接着,我们将逐一分析其中的实现。

initMixin(初始化相关)

此方法仅仅给 Vue 定义了一个原型方法,即 _init()。每次调用 new Vue() 就会被执行。

function Vue (options) {
...
this._init(options)
}

如果你看过 jQuery 的源码,里面也有一个类似的方法,是 init

var jQuery = function( selector, context ) {
// 返回jQuery对象
return new jQuery.fn.init( selector, context, rootjQuery );
} jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
// 很多逻辑的处理
...
}
...
}

每次执行 jQuery(jqOptions) 都会调用 new jQuery.fn.init(),里面有非常多的处理,所以我们可以给 jqOptions 传递多种不同类型的参数。

同样的,new Vue() 也支持多种参数,所以这个 _init() 里面也会做很多事情:

// 核心代码
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 合并参数
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// 初始化生命周期、初始化事件...
initLifecycle(vm)
initEvents(vm)
initRender(vm)
// 触发生命钩子:beforeCreate
callHook(vm, 'beforeCreate')
// resolve injections before data/props
// 我们可以使用的顺序:inject -> data/props -> provide
initInjections(vm)
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') if (vm.$options.el) {
// 挂载
vm.$mount(vm.$options.el)
}
}

_init() 方法依次会合并参数、接着对生命周期、事件等做初始化,其中也会依次触发生命周期钩子 beforeCreatecreated,最后根据条件进行挂载。

stateMixin(数据相关)

stateMixin(Vue) 中 给 Vue 的原型定义的方法(或 property)有:$data$props$set()$delete()$watch()

vm.$data、vm.$props

vm.$data ,返回 Vue 实例观察的数据对象。

vm.$props,返回当前组件接收到的 props 对象。

vm.$set()、vm.$delete() 和 vm.$watch()

$set、$delete 和 $watch 都是与数据相关。出现的背景和原理分析请看 侦测数据的变化 - [vue api 原理]

eventsMixin(事件相关)

eventsMixin(Vue) 中定义的的原型方法有:$on$once$off$emit。分别是注册事件,注册只触发一次的事件、解除事件、触发自定义事件。

vm.$on()

这四个方法,$on() 应该是核心,只有先注册事件,才能解绑事件,或触发事件。

用法如下:

vm.$on('test', function (msg) {
console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"

以下是 $on() 的源码,核心功能就是“收集事件和对应的回调”:

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 如果是数组,则遍历数组,依次注册
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 将回调存入 event 对应的数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
...
}
return vm
}
vm.$once()

$once(),注册只触发一次的事件。

如果你看过 jQuery 源码(事件系统),也能猜到 $once 应该是基于 $on 来实现:

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
// 在 $on 的基础上实现。给回调设置一个代理
vm.$on(event, on)
return vm
}
vm.$off()

$off(),解除事件绑定。

逻辑比较简单:支持没有传参的情况、传参是数组、解绑指定事件、解绑指定事件的回调。请看源码:

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// 如果没有提供参数,则移除所有的事件监听器
if (!arguments.length) {
// Object.create(null) 创建一个原型为null的空对象,以此方式来清空事件
vm._events = Object.create(null)
return vm
}
// 数组,则依次解除事件绑定
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
// 解绑指定的事件
const cbs = vm._events[event]
// 没有回调,直接返回
if (!cbs) {
return vm
}
// 未指定要解绑的事件的回调,则直接清空该事件的所有回调
if (!fn) {
vm._events[event] = null
return vm
}
// 解绑事件指定的回调
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.$emit()

$emit(),触发当前实例上的事件。

取得回调数组,依次触发回调。请看源码:

Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
...
// 取得回调数组
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
}

lifecycleMixin(生命周期相关)

lifecycleMixin(Vue) 中给 Vue 定义的原型方法有:_update()$forceUpdate()$destroy()

vm.$forceUpdate()

迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

代码出奇的少:

// $forceUpdate() 的全部代码

Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}

关键应该就是 vm._watcher.update()

通过全局搜索,发现 vm._watcher 赋值是在 watcher.js 的构造函数中(行{1}):

// src/core/observer/watcher.js
export default class Watcher {
vm: Component;
... constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this // {1}
}

于是我们知道 vm 的 _watcher 是一个 Watcher。而根据 侦测数据的变化 - [基本实现] 中对 Watcher 的研究,我们写过这样一段代码:

class Watcher{
...
// 数据改变时收到通知,然后再通知到外界
update(newVal, oldVal){
this.callback(newVal, oldVal)
}
}

调用 update(),会通知到外界。这里的外界或许就是 vm,然后 vm 会做一些事情,其中或许就包含重新渲染。

vm.$destroy()

$destroy(),完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。请看源码:

Vue.prototype.$destroy = function () {
const vm: Component = this
// 防止重复调用
if (vm._isBeingDestroyed) {
return
}
// 触发钩子:beforeDestroy
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) {
// teardown,取消观察。也就是从所有依赖(Dep)中把自己删除
vm._watcher.teardown()
}
// 解绑用户通过 vm.$watch 添加的监听。
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 已被销毁
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
// 在当前渲染的树上调用销毁钩子
vm.__patch__(vm._vnode, null)
// 调用钩子:destroyed
callHook(vm, 'destroyed')
// 解绑所有事件
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
// 释放循环引用 (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}

Tipvm._watchers 是在 Watcher 的构造函数中添加元素的,而在 vm.$watch 方法中就有 new Watcher(),所以推测 vm._watchers 中的内容来自 vm.$watch

export default class Watcher {
vm: Component;
... constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
...
}
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
...
const watcher = new Watcher(vm, expOrFn, cb, options)
...
}

renderMixin(渲染相关)

renderMixin(Vue) 中给 Vue 定义的原型方法有:$nextTick()_render()

vm.$nextTick()

$nextTick(),将回调延迟到下次 DOM 更新循环之后执行

例如:

new Vue({
// ...
methods: {
// ...
example: function () {
// 修改数据
this.message = 'changed'
// DOM 还没有更新
this.$nextTick(function () {
// DOM 现在更新了
// `this` 绑定到当前实例
this.doSomethingElse()
})
}
}
})

根据用法我们可以猜测 $nextTick() 会将我们我们的回调先保存起来,然后再合适的时间再触发。请看源码:

Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}

代码应该在 nextTick() 中:

// next-tick.js
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(ctx)
}
})
//感觉这里有秘密 {1}
if (!pending) {
pending = true
timerFunc()
}
...
}

nextTick() 中,首先将我们的回调保存起来,将回调方法封装到一个匿名函数,然后再存入 callbacks 数组中。

什么时候触发回调?感觉行{1}处有秘密。事实证明我猜对了,请看源码:

// next-tick.js 中相关代码如下:

// 存放回调函数
const callbacks = []
let pending = false // 依次执行回调函数
function flushCallbacks () {
pending = false
// 备份数组 callbacks。
// 防止遍历回调时,对 callbacks 修改
const copies = callbacks.slice(0)
// 清空数组 callbacks
callbacks.length = 0
// 依次执行回调
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 这里我们有使用微任务的异步延迟包装器。
let timerFunc if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// ios 兼容处理 Promise
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// MutationObserver 也是微任务
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
// window.setImmediate,该特性是非标准的,请尽量不要在生产环境中使用它!
// ie支持, 作用类似于 setTimeout(fn, 0)
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 回退到 setTimeout。
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}

timerFunc 是一个函数,会根据条件选择不同的异步方法封装。这里有4种异步方法,Promise.thenMutationObserver 是微任务,后两种(setImmediatesetTimeout)是宏任务。

已第一种为例:

timerFunc = () => {
p.then(flushCallbacks)
// ios 兼容处理 Promise
if (isIOS) setTimeout(noop)
}

当我们执行 timerFunc() 时,就会立刻执行 p.then(flushCallbacks)flushCallbacks() 方法会依次执行回调函数,但 flushCallbacks() 方法只会在合适的时间被触发(即事件循环中)。

最后我们总结下:执行 this.$nextTick(fn) ,将进入 nextTick(),首先将我们的回调函数用匿名函数封装起来,在将这个匿名函数扔在 callbacks 中,因为没有代办事项(let pending = false),于是执行 timerFunc()

Tip: 有关事件循环、微任务和宏任务的介绍可以看这里

静态方法(global-api/index.js)

global-api/index.js 文件代码总共约 70 行,除去通过 import 导入的 20 行,剩余的都在 initGlobalAPI() 方法中:

export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
...
Object.defineProperty(Vue, 'config', configDef) // 暴露的 util 方法。注意:这些不被视为公共 API 的一部分 - 避免依赖
Vue.util = {
...
} // vm.$set 是 Vue.set 的别名。
// delete 和 nextTick 类似
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick // 2.6 explicit observable API
// 让一个对象可响应
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
} Vue.options = Object.create(null)
// ASSET_TYPES = [ 'component', 'directive', 'filter' ]
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 extend(Vue.options.components, builtInComponents) // 定义了一个方法:Vue.use
initUse(Vue)
// 定义了一个方法:Vue.mixin()
initMixin(Vue)
// 初始化继承。定义了一个方法:Vue.extned()
initExtend(Vue)
// 初始化资产注册。定义了三个方法:Vue.component()、Vue.directive()和Vue.filter()
initAssetRegisters(Vue)
}

接着我们就源码的顺序依次分析。

Vue.set()、Vue.delete()和Vue.nextTick()

  • Vue.set() 是 vm.$set() 的别名

  • Vue.delete() 是 vm.$delete 的别名

  • Vue.nextTick() 是 vm.$nextTick() 的别名

Vue.observable()

代码很少:

Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}

直接看官网介绍:

用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景:

const state = Vue.observable({ count: 0 })

const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}

initUse

// src/core/global-api/use.js

export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 按照了的插件
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果已经安装了此插件,就直接返回 Vue
if (installedPlugins.indexOf(plugin) > -1) {
return this
} // additional parameters
// 附加参数
const args = toArray(arguments, 1)
// this 插入第一位
args.unshift(this)
// 插件的 install 属性 如果是函数,则执行 install
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
// 如果 plugin 本身是函数,就执行 plugin
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 将该插件放入已安装的插件列表中
installedPlugins.push(plugin)
return this
}
}
Vue.use()

initUse(Vue) 中只定义了 Vue.use() 这个全局方法。根据前面的源码分析,此方法的作用应该是安装插件。我们查阅 api 确认一下。

用法:安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。

Tip:到底什么是插件?官网:插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  1. 添加全局方法或者 property。如:vue-custom-element
  2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  3. 通过全局混入来添加一些组件选项。如 vue-router
  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

这样说来,插件的用处挺大的。

Tip:与 jQuery 有些类似,jQuery 也提供了方法(如 jQuery.extend = jQuery.fn.extend = function() {})来扩展核心功能。

initMixin

export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
Vue.mixin()

initMixin(Vue) 中只定义了一个全局方法 Vue.mixin

mixin 有点类似 Object.assign 的味道。我们看一下官网的介绍:

用法:全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。不推荐在应用代码中使用。

我们在看一下它的用法(来自官网):

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
}) new Vue({
myOption: 'hello!'
})
// => "hello!"

根据用法和源码,我们能推测出,Vue.mixin 就是将我们的参数合并到 Vue(this.options) 中,后续创建 Vue 实例时这些参数就会有体现。

initExtend

Vue.extend()

initExtend(Vue) 中只定义了 Vue.extend() 这个全局方法。

extend 这个方法名,又让我想起 Object.assignjQuery.extend。让我们先看一下 api 如何介绍:

用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

好像猜错了。根据用法介绍,Vue.extend 的作用更像 es6 中 Class 的 extends。直接分析其源码再次验证我们的猜测:

Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
// Super 指向 Vue
const Super = this
const SuperId = Super.cid
// 缓存相关 {1}
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
} const name = extendOptions.name || Super.options.name
... // 定义一个子类 Sub
const Sub = function VueComponent (options) {
this._init(options)
}
// 以 Vue 的原型作为新建对象的原型,并将新建对象设置为 Sub 的原型
// 用 new Sub() 创建的对象也能访问到 Vue.prototype 中的方法或属性
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
// 合并参数
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// 新增 super 属性,指向 Vue
Sub['super'] = Super if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
} // 允许进一步的扩展/混合/插件使用
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use ... // 在扩展时保留对超级选项的引用。
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options) // 缓存构造函数
cachedCtors[SuperId] = Sub
// 返回子类 Sub
return Sub
}

执行 Vue.extend() 将返回一个子类 Sub,该子类继承了父级(Vue)。为了提高性能,里面增加了缓存机制(行{1})。

initAssetRegisters

initAssetRegisters(Vue) 中定义了三个全局方法。

export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
* 创建资产注册方法。
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
...
}
})
}

ASSET_TYPES 是一个数组:

export const ASSET_TYPES = [
'component',
'directive',
'filter'
]

Tip:如果你看过 jQuery,你也会发现有类似的写法,也就是当多个方法的逻辑相似,就可以写在一起,显得精简。

Vue.component()、Vue.directive()和Vue.filter()

直接看源码有点难懂(注释可稍后再看):

ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// 没有传第二个参数,表示获取。比如获取指令
if (!definition) {
return this.options[type + 's'][id]
} else {
// 组件的特别处理
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
// 指令的特殊处理
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 与获取对应,这里应该就是注册。例如注册指令、组件或过滤器
this.options[type + 's'][id] = definition
return definition
}
}
})

我们翻阅一下对应 api:

  • Vue.filter( id, [definition] ) - 注册或获取全局过滤器。
// 注册
Vue.filter('my-filter', function (value) {
// 返回处理后的值
}) // getter,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')
  • Vue.directive() - 注册或获取全局指令。

  • Vue.component() - 注册或获取全局组件

核心就是注册获取。感觉作用比较简单,有点类似注册事件,取得事件的回调。现在我们在来看一下源码和相应的注释,会发现容易理解多了。

Tip:组件、指令、过滤器当然不是回调这么简单,所以我们可以猜测:这里只是负责注册和获取,至于如何生效,却不在此处。

分布它处的方法

vm.$mount()

如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

项目中搜索 $mount ,发现有三个文件对其有定义 Vue.prototype.$mount =

  1. platforms\web\runtime\index.js
  2. platforms\weex\runtime\index.js
  3. platforms\web\entry-runtime-with-compiler.js

前两个文件是根据不同的平台(web或weex)对 $mount 方法的不同的定义。

而最后一个文件有些意思,请看源码:

// entry-runtime-with-compiler.js

// 函数劫持。保存原始 $mount
const mount = Vue.prototype.$mount // {1}
// 对原始 $mount 进行封装。主要做的工作是将模板编译成渲染函数
Vue.prototype.$mount = function ( // {2}
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
...
const options = this.$options
// resolve template/el and convert to render function
// 解析模板/el并转换为渲染函数
if (!options.render) {
...
}
return mount.call(this, el, hydrating)
}

Vue(自身) 项目结构 一文中,我们知道 entry-runtime-with-compiler.js 生成的文件是完整版本(dist/vue.js),完整版本是包含编译器的,而我们在 模板 一文中也知晓了编译器的主要作用,将模板编译成渲染函数。

所以我们能推测出这个 $mount(行{2}),就是在原始 $mount(行{1})中增加编译器的功能。

其他章节请看:

vue 快速入门 系列

vue 快速入门 系列 —— 实例方法(或 property)和静态方法的更多相关文章

  1. vue 快速入门 系列 —— Vue 实例的初始化过程

    其他章节请看: vue 快速入门 系列 Vue 实例的初始化过程 书接上文,每次调用 new Vue() 都会执行 Vue.prototype._init() 方法.倘若你看过 jQuery 的源码, ...

  2. vue 快速入门 系列 —— 侦测数据的变化 - [基本实现]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [基本实现] 在 初步认识 vue 这篇文章的 hello-world 示例中,我们通过修改数据(app.seen = false),页面中 ...

  3. vue 快速入门 系列 —— 侦测数据的变化 - [vue 源码分析]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue 源码分析] 本文将 vue 中与数据侦测相关的源码摘了出来,配合上文(侦测数据的变化 - [基本实现]) 一起来分析一下 vue ...

  4. vue 快速入门 系列 —— vue 的基础应用(上)

    其他章节请看: vue 快速入门 系列 vue 的基础应用(上) Tip: vue 的基础应用分上下两篇,上篇是基础,下篇是应用. 在初步认识 vue一文中,我们已经写了一个 vue 的 hello- ...

  5. vue 快速入门 系列 —— vue loader 扩展

    其他章节请看: vue 快速入门 系列 vue loader 扩展 在vue loader一文中,我们学会了从零搭建一个简单的,用于单文件组件开发的脚手架.本篇将在此基础上继续引入一些常用的库:vue ...

  6. vue 快速入门 系列 —— vue-router

    其他章节请看: vue 快速入门 系列 Vue Router Vue Router 是 Vue.js 官方的路由管理器.它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌. 什么是路由 ...

  7. vue 快速入门 系列 —— Vue(自身) 项目结构

    其他章节请看: vue 快速入门 系列 Vue(自身) 项目结构 前面我们已经陆续研究了 vue 的核心原理:数据侦测.模板和虚拟 DOM,都是偏底层的.本篇将和大家一起来看一下 vue 自身这个项目 ...

  8. vue 快速入门 系列 —— 侦测数据的变化 - [vue api 原理]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue api 原理] 前面(侦测数据的变化 - [基本实现])我们已经介绍了新增属性无法被侦测到,以及通过 delete 删除数据也不会 ...

  9. vue 快速入门 系列 —— 虚拟 DOM

    其他章节请看: vue 快速入门 系列 虚拟 DOM 什么是虚拟 dom dom 是文档对象模型,以节点树的形式来表现文档. 虚拟 dom 不是真正意义上的 dom.而是一个 javascript 对 ...

随机推荐

  1. 【LeetCode】225. Implement Stack using Queues 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  2. 【LeetCode】746. Min Cost Climbing Stairs 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetc ...

  3. Codeforces 931C:Laboratory Work(构造)

    C. Laboratory Work time limit per test : 1 second memory limit per test : 256 megabytes input : stan ...

  4. 防止 jar 包被反编译

    1.隔离Java程序 最简单的方法就是让用户不能够访问到Java Class程序,这种方法是最根本的方法,具体实现有多种方式.例如,开发人员可以将关键的Java Class放在服务器端,客户端通过访问 ...

  5. Chapter 2 Randomized Experiments

    目录 概 2.1 Randomization 2.2 Conditional randomization 2.3 Standardization 2.4 Inverse probability wei ...

  6. [opencv]拟合vector<Mat>集合区域接近的元素

    vector<Rect> PublicCardFrameDetection::fitrect(vector<Rect> rects){ int size = rects.siz ...

  7. 《python-美藏篇》1.可迭代、迭代器与生成器

    首先区分可迭代对象(Iterable).迭代器(Iterator) 可迭代对象:包含__iter__方法的对象为可迭代对象,如List.Tuple.Dictionary 迭代器:包含__iter__. ...

  8. JavaScript交互式网页设计 • 【第7章 jQuery操作 DOM】

    全部章节   >>>> 本章目录 7.1 DOM 对象和 jQuery 对象 7.1.1 DOM 对象 7.1.2 jQuery 对象 7.1.3 jQuery 对象和 DOM ...

  9. MySQL数据库基础(4)SELECT 数据查询

    目录 一.SELECT 选择列表 二.MySQL 运算符 三.定制显示查询结果 四.模糊查询 一.SELECT 选择列表 1.语法 SELECT <COLUMN1, COLUMN2, COLUM ...

  10. rsync配置文件讲解

    1.安装rysnc 一般在安装系统时rsync是安装上(yum安装) 2.     vim /etc/xinetd.d/rsync 在这个路径下有配置文件 service rsync { disabl ...