「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
一、背景
在上一篇中,我们通过对 Vue 项目中 rollup 的打包分析了一下 vuapplee.js 的入口问题,从而解释了 initGlobalAPI 和 new Vue 的先后关系。这个先后关系决定了,new Vue 时一些属性html文件怎么打开,比如 Vue.options 的来源。理解这些对象的来源,有利于理解这appear个 Vue 的执行过程。
本篇小作文立意说一说 new Vue 都发生了什elementary么,这个问题也是一个常见的面试题。
二、代码目录结构
看下 vue 的项目结构,这个是被简化过的文件结构,因为我们只讨论源码,很element翻译多子目录都被省略掉了,比面试自我介绍3分钟通用如 benchmarks,等。别紧张elementui,只有 src 目录下是源码,其他很多内容暂时不用管。
.
├── benchmarks
├── dist
├── examples
├── flow
├── packages
├── scripts
├── src // 源码目录
│ ├── compiler
│ │ ├── codegen
│ │ ├── directives
│ │ └── parser
│ ├── core 这个是个重中之重
│ │ ├── components
│ │ ├── global-api
│ │ ├── instance
│ │ │ └── render-helpers
│ │ ├── observer
│ │ ├── util
│ │ └── vdom
│ │ ├── helpers
│ │ └── modules
│ ├── platforms
│ │ ├── web
│ │ │ ├── compiler
│ │ │ │ ├── directives
│ │ │ │ └── modules
│ │ │ ├── runtime
│ │ │ │ ├── components
│ │ │ │ ├── directives
│ │ │ │ └── modules
│ │ │ ├── server
│ │ │ │ ├── directives
│ │ │ │ └── modules
│ │ │ └── util
│ │ └── weex weex 相关,省略
│ ├── server 省略
│ ├── sfc
│ └── shared
├── test
└── types
二、断点进入 Vue 源码
2elementui.1 test.html 的断点
我们在 test.html
中 new Vue
之前写了一个 dappointmentebugger
,打开控制台,刷新,就能看到代码停在了这里:
2.2 进入到源码的入口文件
因为之前我们在第一篇的 浅羲Vue源码-1-准备工作 通过配置 rollup
的 --sourcemap
参数生成了 sourcemap
文件,此时就派上用场了。通过进一步点击断点的按步骤执行,进入到 Vue
构造函数的所在文件:src/core/instance/index.js
。
三、Vue 构造函数
3.1 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) // Vue 构造函数就这一行代码
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
从上面的代码,可以清晰发现,Vuehtml标签属性大全
构造函数只有一行关键代码。但是有个神奇的事情是,并没有见到 _init
方法声明,element滑板那么这个方法是在哪里声明的呢?答案是 initMixin
3.2 src/core/instance/init.js 中的 inhtml是什么意思itMixin
前面提到 initMixn
会给 Vue
的构造函数上添加 _init
方Element法,其具体做法是导出一个函数,该函数接收 Vue
构造函数,然后扩展 _init
方法:
// some import
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// .... detail of _init
}
}
四、_init
的逻辑
接下我们分步骤看下 _init
都做了什么事情。
4.1 声明 vappreciatem 变量
在 Vue
中 vm
是一个非常重要的变量,时刻注意,vm
就是 Vue
的实例,即这里的 vm = this
:
// some import
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this // vm 是 Vue 的实例
// a uid
vm._uid = uid++ // 每个 vue 实例都有一个 _uid,是个自增的数字
vm._isVue = true // 一个标识符,用于防止被数据观察处理
}
}
4.2 处理组件或者根实例的选项合并
4.2.1 组件选项
组件选项就是我们创建组件时传递的对象,例如在 test.html
中声明初始化sdk什么意思的子组件,这就是个组件选项,可以理解为创建 Vue 组件所必须的配置项;
const sub = {
template: `
<div>{{ someKey + foo }}</div>`,
props: {
someKey: {
type: String,
default: 'hhhhhhh'
}
},
inject: ['foo']
};
4.2.2 根实例选项
即 new Vue()elementary是什么意思
时传给构面试问题造函数的选项就是根实例选项,例如 test.html
中创建 Vue
实例传入的对象:
new Vue({ // 这对象就是根实例选项
el: '#app',
data: {
msg: 'hello vue'
},
hahaha: 'hahahahahha',
provide: {
foo: 'bar'
},
components: {
someCom: sub
}
})
4.2.3 合并选项
选项合并时,是将合并过后的选项赋值到 vm.$options
这个属初始化失败是怎么解决性上,这个属性在贯穿了整个源码阅读,当遇到这个属性时,请谨记html标签本次用到elements中文翻译这application个属性添加了哪些属性,更新了哪些属性,又或者删除了哪些属性
Vue.prototype._init = function (options?: Object) {
// ...
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// ....
}
这里用到了几个方法,mergerOpapprovetions
和 resolveConstructorOptions
,其中 resolveConstructorOptions
就是从构面试技巧造函数自身上获取 opt面试自我介绍一分钟ions
等信息给到实例复用,里暂时不表。
4.3 代理实例属性 _renderProxy
到 vm
自身
当访问到 _re面试常见问题及回答技巧nderProxy
时,就是在访问 vm
自身的属性,这个属性在后面处理渲染时有用到。
Vue.prototype._init = function (options?: Object) {
// ......
if (process.env.NODE_ENV !== 'production') {
initProxy(vm); // 开发环境用 ES6 的 Proxy 实现的
} else {
vm._renderProxy = vm
}
// ....
}
4.4 执行一系列初Element始化
Vue.prototype._init = function (options?: Object) {
// ....
// 初始化如 Vue 实例上的一些关键属性,如 _inactive/_isMounted/_isDestroyed,
// 另外还有组件间的关系:$parent/$children/$refs 等
initLifecycle(vm)
// 初始化组件的自定义事件,比如说在自定义组件上 @some-event
initEvents(vm)
// 初始化渲染最重要的方法:vm._c 和 vm.$createElement,
// 解析组件中的 slot,挂载到 vm.$slots 属性
initRender(vm)
// 调用 beforeCreate 生命周期钩子
callHook(vm, 'beforeCreate')
// 初始化组件上的 inject 选项,把这个东西处理成 result[key] = val 的标准形式,
// 然后对这个 result 做数据响应式处理,代理每个 key 到 vm
initInjections(vm) // resolve injections before data/props
// 这里是处理数据响应式的重点,后面会单独说,大致是处理 props/methods/data/computed/watch
initState(vm)
// 解析组件上的 provide,这个 provide 有点像 react 的 Provider,是跨组件深层传递数据的
// 同样,把解析结果代理到 vm._provide 上;
// 这里多说一点,为啥先初始化 initInjections 后初始化 initProvide 呢?
// 他之所以敢这么干是因为 inject 在子组件上,而 provide 在父组件上,而父组件先于子组件被
// 处理所以当 inject 后初始化没问题,因为他取用的是父组件上的 provide,
// 此时父组件的 provide 早已经初始化完成了
initProvide(vm) // resolve provide after data/props
// 调用 created 生命周期钩子
callHook(vm, 'created')
// .......
}
4.5 el 和 $mount
这里如果 $options.el
属性存在,即挂载点,就执行 $mount
去挂载;
Vue.prototype._init = function (options?: Object) {
// 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,
// 就不需要再手动调用 $mount,没有 el 就需要手动 $mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}