「这是我参与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) } }
评论(0)