Vue2.6是vue的js
版的最后一个版本。 Vue 2.7以后都用ts重写了。
Flow
Flow是是facebook 出品的 JavaScript 静态类型检查工具。在Vue2.7以前使用Flow进行静态代码检查。 具有如下功能:
- 不需要写任何代码,获取类型推导的能力
- 通过类型注释来约束类型
Vue 2.0 为什么选用 Flow 进行静态代码检查而不是直接使用 TypeScript?
https://www.zhihu.com/question/46397274/answer/101193678
这个选择最根本的还是在于工程上成本和收益的考量。Vue 2.0 本身在初期的快速迭代阶段是用 ES2015 写的,整个构建工具链也沿用了 Vue 1.x 的基于 ES 生态的一套(Babel, ESLint, Webpack, Rollup...),全部换 TS 成本过高,短期内并不现实。
相比之下 Flow 对于已有的 ES2015 代码的迁入/迁出成本都非常低:
- 可以一个文件一个文件地迁移,不需要一竿子全弄了。
- Babel 和 ESLint 都有对应的 Flow 插件以支持语法,可以完全沿用现有的构建配置;
- 更贴近 ES 规范。除了 Flow 的类型声明之外,其他都是标准的 ES。万一哪天不想用 Flow 了,用 babel-plugin-transform-flow-strip-types 转一下,就得到符合规范的 ES。
- 在需要的地方保留 ES 的灵活性,并且对于生成的代码尺寸有更好的控制力 (rollup / 自定义 babel 插件)
2018 年,ts真香
在阅读源码的时候,为了简单,将其视为typescript处理。
Vue目录结构
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 跨平台支持
├── server # 服务端渲染 (nodejs环境)
├── sfc # .vue 文件解析
├── shared # 共享代码.工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 跨平台支持
├── server # 服务端渲染 (nodejs环境)
├── sfc # .vue 文件解析
├── shared # 共享代码.工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
额外的引用网络上的一张图片:
Vue的构建过程
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
通过package.json文件可以看到,运行 scripts/build.js文件:
在此文件中,读取config.js
文件的相关配置,并解析入参解析。默认build不构建weex。
随后将使用rollup构建工具,结合config.js文件的配置内容,构建产出物:
function build(builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
function buildEntry(config) {
const output = config.output
const { file, banner } = output
// 文件名包含 `min.js` 或 `prod.js` 则认为是生产环境
const isProd = /(min|prod)\.js$/.test(file)
return rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ output: [{ code }] }) => {
if (isProd) {
/**
* https://github.com/terser/terser
* Terser 是一个用于 JavaScript 压缩和混淆的工具,旨在减小 JavaScript 文件的大小,并提高网页加载性能。
* 它是 UglifyJS 的替代品,并针对 ES6+ 代码进行了优化。
* Terser 可以移除未使用的代码、压缩代码、混淆变量名等,以减小 JavaScript 文件的大
*/
const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
toplevel: true, output: {
// 这个选项指定了输出的内容是否仅包含 ASCII 字符。
// 将所有非 ASCII 字符转化为 Unicode 转义序列,这有助于确保代码在不同环境中能够正确解析。
ascii_only: true
}, // 这个选项指定了在压缩过程中识别并移除无副作用的函数。`pure_funcs` 字段中列出的函数名称,表示这些函数被认为是“纯函数”,即调用这些函数不会产生任何副作用。
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
}
function build(builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
function buildEntry(config) {
const output = config.output
const { file, banner } = output
// 文件名包含 `min.js` 或 `prod.js` 则认为是生产环境
const isProd = /(min|prod)\.js$/.test(file)
return rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ output: [{ code }] }) => {
if (isProd) {
/**
* https://github.com/terser/terser
* Terser 是一个用于 JavaScript 压缩和混淆的工具,旨在减小 JavaScript 文件的大小,并提高网页加载性能。
* 它是 UglifyJS 的替代品,并针对 ES6+ 代码进行了优化。
* Terser 可以移除未使用的代码、压缩代码、混淆变量名等,以减小 JavaScript 文件的大
*/
const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
toplevel: true, output: {
// 这个选项指定了输出的内容是否仅包含 ASCII 字符。
// 将所有非 ASCII 字符转化为 Unicode 转义序列,这有助于确保代码在不同环境中能够正确解析。
ascii_only: true
}, // 这个选项指定了在压缩过程中识别并移除无副作用的函数。`pure_funcs` 字段中列出的函数名称,表示这些函数被认为是“纯函数”,即调用这些函数不会产生任何副作用。
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
}
Runtime Only VS Runtime + Compiler
通常我们利用 vue-cli 去初始化我们的 Vue.js 项目的时候会询问我们用 Runtime Only 版本的还是 Runtime + Compiler 版本。下面我们来对比这两个版本。
- Runtime Only
我们在使用 Runtime Only 版本的 Vue.js 的时候,通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量。
- Runtime + Compiler
- 我们如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板,如下所示:
// 需要编译器的版本
new Vue({
template: '<div>{{ hi }}</div>'
})
// 这种情况不需要
new Vue({
render (h) {
return h('div', this.hi)
}
})
// 需要编译器的版本
new Vue({
template: '<div>{{ hi }}</div>'
})
// 这种情况不需要
new Vue({
render (h) {
return h('div', this.hi)
}
})
因为在 Vue.js 2.0 中,最终渲染都是通过 render
函数,如果写 template
属性,则需要编译成 render
函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。
很显然,这个编译过程对性能会有一定损耗,所以通常我们更推荐使用 Runtime-Only 的 Vue.js
Vue如何被定义和初始化
追踪多个文件,我们可以找到:
可以看到Vue实际上是使用 Function
来实现的类,好处是使用prototype
进行扩展很方便。
为 Vue 的 prototype 扩展一些方法:
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
initMixin
代码见src/core/instance/init.js
文件:
// 这里精简了一些后的代码,忽略了 Performance API 调用
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 防止this被observed的flag
vm._isVue = true
// merge options
if (options && options._isComponent /*是组件的时候*/) {
// Vue注释说:优化内部组件实例,因为动态选项合并非常慢并且没有任何内部组件选项需要特殊处理
initInternalComponent(vm, options)
} else {
// 将传入的options最终都merge到$options上。 `new Vue(options)`
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
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')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
// 这里精简了一些后的代码,忽略了 Performance API 调用
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 防止this被observed的flag
vm._isVue = true
// merge options
if (options && options._isComponent /*是组件的时候*/) {
// Vue注释说:优化内部组件实例,因为动态选项合并非常慢并且没有任何内部组件选项需要特殊处理
initInternalComponent(vm, options)
} else {
// 将传入的options最终都merge到$options上。 `new Vue(options)`
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
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')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
Class<Component>
类型声明为:
type Class<T> = { new(): T };
type Class<T> = { new(): T };
定义了一个类型叫做Class。 表示可以使用new来进行初始化T。如:
interface MyConstructor {
new(): MyClass;
}
let myConstructor: MyConstructor = MyClass;
let instance: MyClass = new myConstructor();
interface MyConstructor {
new(): MyClass;
}
let myConstructor: MyConstructor = MyClass;
let instance: MyClass = new myConstructor();
vue中Component这个类型声明为:
export type Component<
Data = DefaultData<never>,
Methods = DefaultMethods<never>,
Computed = DefaultComputed,
Props = DefaultProps> =
| typeof Vue
| FunctionalComponentOptions<Props>
| ComponentOptions<never, Data, Methods, Computed, Props>
export type Component<
Data = DefaultData<never>,
Methods = DefaultMethods<never>,
Computed = DefaultComputed,
Props = DefaultProps> =
| typeof Vue
| FunctionalComponentOptions<Props>
| ComponentOptions<never, Data, Methods, Computed, Props>
由于有类型约束,为了能调用 initMixin
、stateMixin(Vue)
等方法。文件core/instance/index.js
文件中没有添加/*@flow*/
注释,进而忽略了类型检查。
this指向
Vue.prototype._init
函数中this指向需要结合上下文来看。
function Vue(options) {
// 通过 `this instanceof Vue` 来判断有没有用 new 关键词调用。
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)
}
function Vue(options) {
// 通过 `this instanceof Vue` 来判断有没有用 new 关键词调用。
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)
}
- 由于
Vue
要求用new来初始化,故function Vue() {}
里的this是新创建的Vue
的实例 - 这里又调用了
this._init(...)
,因此Vue.prototype._init
中this
也是Vue
实例
.constructor
上文中,我们知道了this的指向。因此vm.constructor
就是Vue
。撸一个简单的例子:
function VueZ(options) {
// 因为是通过new函数调用的,这里的this是新创建的VueZ的实例
this._init(options)
}
function initMixin(Vue) {
Vue.prototype._init = function () {
var vm = this;
console.log(vm.constructor === VueZ); // true
};
}
initMixin(VueZ);
new VueZ({'a': 'b'})
function VueZ(options) {
// 因为是通过new函数调用的,这里的this是新创建的VueZ的实例
this._init(options)
}
function initMixin(Vue) {
Vue.prototype._init = function () {
var vm = this;
console.log(vm.constructor === VueZ); // true
};
}
initMixin(VueZ);
new VueZ({'a': 'b'})
vm._isVue = true
vm._isVue = true
,防止被observed的flag,可以追溯到observe代码。
mergeOptions(resolveConstructorOptions...)
这个东西实际上是把vue默认的配置和用户传入的options做一次合并。
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor) /*相当于获取Vue.options*/,
options || {},
vm
)
// ===============================================================
// resolveConstructorOptions
export function resolveConstructorOptions (Ctor: Class<Component>) {
....
}
// mergeOptions 是一个工具类,处理合并
export function mergeOptions (parent: Object,child: Object,vm?: Component): Object {
//...
}
// check if there are any late-modified/attached options (#4976)
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
...
}
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor) /*相当于获取Vue.options*/,
options || {},
vm
)
// ===============================================================
// resolveConstructorOptions
export function resolveConstructorOptions (Ctor: Class<Component>) {
....
}
// mergeOptions 是一个工具类,处理合并
export function mergeOptions (parent: Object,child: Object,vm?: Component): Object {
//...
}
// check if there are any late-modified/attached options (#4976)
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
...
}
resolveConstructorOptions
export function resolveConstructorOptions (Ctor: Class<Component>) {
// ①
let options = Ctor.options
// 有super属性,说明Ctor是Vue.extend构建的子类
// ②
if (Ctor.super) {
// 递归, 最底一层必然是原始Vue上的options
const superOptions = resolveConstructorOptions(Ctor.super)
// ③ superOptions 被extend的vue的options(原来Vue的options)
const cachedSuperOptions = Ctor.superOptions
// ④
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options. Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
// ⑤
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
// 这里还未仔细研究
options.components[options.name] = Ctor
}
}
}
return options
}
export function resolveConstructorOptions (Ctor: Class<Component>) {
// ①
let options = Ctor.options
// 有super属性,说明Ctor是Vue.extend构建的子类
// ②
if (Ctor.super) {
// 递归, 最底一层必然是原始Vue上的options
const superOptions = resolveConstructorOptions(Ctor.super)
// ③ superOptions 被extend的vue的options(原来Vue的options)
const cachedSuperOptions = Ctor.superOptions
// ④
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options. Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
// ⑤
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
// 这里还未仔细研究
options.components[options.name] = Ctor
}
}
}
return options
}
① 上文我们知道了vm.constructor
就是Vue
, 故Ctor.options
为Vue.options
。通过全局搜索。 initGlobalAPI(Vue)
,可以找到默认全局定义了一个空的、干净的options。
② Ctor.super
判断有super属性,这里的super
和javaScript中的super关键字不一样。这里的super是Vue.extend
的时候添加的属性。
关于Vue.extend
实际用的比较少。使用方法见下图,文档:API — Vue.js
③ Ctor.superOptions
Vue.extend
的时候会将被extend的vue的options赋给superOptions
属性。
④ 由于涉及到递归,如果经过层层extend,可以展示如图的逻辑
⑤ 这里修复了BUG 4976 , 调用 resolveModifiedOptions
找到了差异变更的options,再merge进去。
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// ....
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// ....
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
initInternalComponent
主要是组件$options
的初始化。和组件初始化过程见:[0x035-Vue源码解读-Vue-component]
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 创建一个新对象,并将现有对象的原型链设置为该新对象的原型
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent // parent vue 实例
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
// :name="'abc'"
opts.propsData = vnodeComponentOptions.propsData
// @click="click"
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 创建一个新对象,并将现有对象的原型链设置为该新对象的原型
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent // parent vue 实例
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
// :name="'abc'"
opts.propsData = vnodeComponentOptions.propsData
// @click="click"
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Note
Object.create() 方法创建一个新的对象,并将现有的对象作为新对象的原型。在这种情况下,新创建的对象将以 Super.prototype 作为其原型。
这是一种在不使用 new 关键字和构造函数的情况下创建继承自另一个对象的对象的方法。
一些关键点:
• Super 被假定为一个定义原型对象的构造函数。
• 新创建的对象将把 Super.prototype 作为其 proto 属性,从而继承 Super.prototype 上定义的所有属性和方法。
• 这是在 JavaScript 中实现原型继承的常用方式。
initLifecycle
初始化了生命周期状态标识等操作。
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
}
// 将Vue实例添加到找到的父组件的$children中,这样父组件也能访问子组件
parent.$children.push(vm)
}
// 也将$parent添加到子组件
vm.$parent = parent
// 先初始化的必然是父组件,因此获取根组件不需要递归查询,子组件只要依次父组件的$root即可
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
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
}
// 将Vue实例添加到找到的父组件的$children中,这样父组件也能访问子组件
parent.$children.push(vm)
}
// 也将$parent添加到子组件
vm.$parent = parent
// 先初始化的必然是父组件,因此获取根组件不需要递归查询,子组件只要依次父组件的$root即可
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// _parentListeners 是在 initInternalComponent 函数中赋值的。
// vue组件(_isComponent)才有此次属性
// 这里的 listeners 可以理解为获取到的父组件的自定义事件
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// _parentListeners 是在 initInternalComponent 函数中赋值的。
// vue组件(_isComponent)才有此次属性
// 这里的 listeners 可以理解为获取到的父组件的自定义事件
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
这里需要了解下Vue中,解析子组件的过程(解析AST -> VNode),如图。
可以知道自定义的事件解析到了 options.listeners
具体过程可以参考此文档,写的很细。
updateComponentListeners
作用是将父组件的事件绑定到子组件上,便于子组件后期调用。
let target: any
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
// 这样没看懂?可能需要结合后文
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
// 声明临时变量,add/remove/createOnceHandler调用时使用
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
// 临时变量
target = undefined
}
let target: any
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
// 这样没看懂?可能需要结合后文
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
// 声明临时变量,add/remove/createOnceHandler调用时使用
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
// 临时变量
target = undefined
}
这里还用到 .$on
、 .$off
等。
.$on
和 .$off
是在 eventsMixin(Vue)
方法中引入,代码比较清晰,将事件加进来
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// 无参数,删除全部
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// event是数组类型
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) {
// fn为空,从_events中删除
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs /*vm._events[event] */.length
// 从 vm._events[event] 数组中删除传入=fn的值
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// 无参数,删除全部
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// event是数组类型
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) {
// fn为空,从_events中删除
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs /*vm._events[event] */.length
// 从 vm._events[event] 数组中删除传入=fn的值
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
updateListeners
将父vm上的自定义事件增加到子vm上。
export function updateListeners (
on: Object, /*父事件 vm._parentListeners*/
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
// 获取一个父事件
def = cur = on[name]
// 当前场景old是空
old = oldOn[name]
// 处理事件修饰符
event = normalizeEvent(name)
if (isUndef(cur)) {
// 事件未定义,给于警告
process.env. NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
// old中不存在,就新增一下
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
// 满足 .once 修饰符
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
// old中存在,并且 cur != old
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
export function updateListeners (
on: Object, /*父事件 vm._parentListeners*/
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
// 获取一个父事件
def = cur = on[name]
// 当前场景old是空
old = oldOn[name]
// 处理事件修饰符
event = normalizeEvent(name)
if (isUndef(cur)) {
// 事件未定义,给于警告
process.env. NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
// old中不存在,就新增一下
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur, vm)
}
// 满足 .once 修饰符
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
// old中存在,并且 cur != old
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
initRender
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
// renderContext: 父的vm
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 将 createElement fn 绑定到此实例 以便我们在其中获得正确的this
vm._c = (a,b,c,d) => createElement(vm, a /*tag*/, b/*data*/, c/*children*/, d/*normalizationType*/, false /*alwaysNormalize*/)
// 暴漏给用户编写渲染函数: https://v2.cn.vuejs.org/v2/guide/render-function.html
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 公开 $attrs 和 $listeners 以便更轻松地创建 HOC
// 它们需要具有响应性,以便使用它们的 HOC 始终保持更新 const parentData = parentVnode && parentVnode.data
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
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
// renderContext: 父的vm
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 将 createElement fn 绑定到此实例 以便我们在其中获得正确的this
vm._c = (a,b,c,d) => createElement(vm, a /*tag*/, b/*data*/, c/*children*/, d/*normalizationType*/, false /*alwaysNormalize*/)
// 暴漏给用户编写渲染函数: https://v2.cn.vuejs.org/v2/guide/render-function.html
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 公开 $attrs 和 $listeners 以便更轻松地创建 HOC
// 它们需要具有响应性,以便使用它们的 HOC 始终保持更新 const parentData = parentVnode && parentVnode.data
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
initRender
主要是一些属性的暴露和初始化。 这里提到的HOC,略。
关于 HOC
Higher order components in Vue
高阶组件 (HOC) 是一种架构模式,在 React 中极为常见,但也可以在 Vue 中使用。它可以被描述为一种用于在组件之间共享通用功能而无需重复代码的方法。 HOC 的目的是增强组件的功能。它允许项目中的可重用性和可维护性。
关于defineReactive
,见文档[0x036-Vue源码解读-defineReactive]
initProvide
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm) // provide可以是fuction
: provide
}
}
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm) // provide可以是fuction
: provide
}
}
provide实际上可以是function也可以是object:
// 1
new Vue({
template: `<child/>`,
provide: {
foo: 1,
bar: null
}
}).$mount()
// 2
new Vue({
template: `<child/>`,
data: {
a: 1,
b: false
},
provide () {
return {
foo: this.a,
bar: this.b
}
}
}).$mount()
// 1
new Vue({
template: `<child/>`,
provide: {
foo: 1,
bar: null
}
}).$mount()
// 2
new Vue({
template: `<child/>`,
data: {
a: 1,
b: false
},
provide () {
return {
foo: this.a,
bar: this.b
}
}
}).$mount()
initInjections
这部分代码比较简单:
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 防止初始化完的时候被observe
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)
}
}
//
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
// ① Reflect.ownKeys 和 Object.keys的区别
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
// 由上方的代码得知: _provided 是在 provide的方法里赋值的
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env. NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 防止初始化完的时候被observe
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)
}
}
//
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
// ① Reflect.ownKeys 和 Object.keys的区别
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
// 由上方的代码得知: _provided 是在 provide的方法里赋值的
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env. NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}