大家好,今天的分享由团队的 uncle13 老师提供。
先来一张比较流行的图:
在 Vue.js 中,当使用 new Vue
创建一个新的 Vue 实例时,会触发一系列的初始化过程,用于设置和管理组件的状态、响应式数据、渲染等。以下是使用 new Vue
创建实例时发生的主要步骤:
实例的创建:
Vue
构造函数创建一个 Vue 实例,同时进行初始化配置的合并和属性初始化。初始化生命周期钩子:
beforeCreate
、created
等,用于在组件的不同生命周期阶段执行自定义逻辑。初始化事件系统:
$on
、$emit
、$off
等方法,用于实现组件之间的通信。初始化注入和状态:
初始化渲染函数:
初始化渲染相关的属性:
$attrs
和 $listeners
,用于在组件中处理继承的属性和监听器。初始化计算属性和方法:
初始化侦听属性:
初始化组件的 props:
调用 beforeCreate
钩子:
beforeCreate
钩子。初始化注入:
beforeCreate
钩子中,会处理注入的数据,将注入的数据挂载到实例上。初始化响应式数据:
beforeCreate
钩子中,会将响应式数据进行初始化,使其具有响应式特性。调用 created
钩子:
created
钩子。编译模板(如果有):
挂载实例:
$mount
方法来挂载 Vue 实例到页面上的 DOM 元素上,触发组件的渲染。调用 beforeMount
钩子:
beforeMount
钩子。执行渲染函数:
调用 mounted
钩子:
mounted
钩子。创建一个 Vue 实例时,涉及到许多初始化过程,包括响应式数据、渲染函数的编译、生命周期钩子的调用等。以下是相关源码的简要解析,针对 Vue 2.x 版本:
创建 Vue 实例:
function Vue(options) {
if (!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
在这个步骤中,Vue
构造函数被调用,同时通过 _init
方法进行初始化。
初始化 Vue 实例:
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
// 合并配置
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
// 初始化生命周期钩子
initLifecycle(vm);
// 初始化事件
initEvents(vm);
// 初始化渲染函数
initRender(vm);
// 调用 beforeCreate 钩子
callHook(vm, 'beforeCreate');
// 初始化注入
initInjections(vm);
// 初始化响应式数据
initState(vm);
// 初始化侦听属性
initProvide(vm);
// 调用 created 钩子
callHook(vm, 'created');
// 挂载实例
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
在初始化阶段,以下几个重要步骤值得关注:
mergeOptions
:合并 Vue 实例构造函数的选项和用户传入的选项。initLifecycle
:初始化生命周期钩子,如 $parent
、$children
等。initEvents
:初始化事件系统,如 $on
、$emit
等。initRender
:初始化渲染函数,设置 $slots
和 $createElement
等。initInjections
和 initState
:初始化注入和响应式数据。initProvide
:处理 provide
和 inject
。$mount
:如果指定了 el
,则挂载实例到 DOM 元素上。初始化响应式数据:
export function initState(vm) {
vm._watchers = [];
const opts = vm.$options;
if (opts.props) initProps(vm, opts.props);
if (opts.methods) initMethods(vm, opts.methods);
if (opts.data) {
initData(vm);
} else {
observe((vm._data = {}), true);
}
if (opts.computed) initComputed(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
这一步主要负责初始化组件的响应式数据。它会根据 data
选项初始化数据,并通过 Object.defineProperty
实现数据的响应式。同时,也会处理 props
、methods
、computed
、watch
等选项。
初始化渲染函数:
export function initRender(vm) {
vm._vnode = null;
vm._staticTrees = null;
const options = vm.$options;
const parentVnode = (vm.$vnode = options._parentVnode);
const renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(options._renderChildren, renderContext);
vm.$scopedSlots = emptyObject;
// 创建 createElement 和 $createElement 方法
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
// 定义 $attrs 和 $listeners
const parentData = parentVnode && parentVnode.data;
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true);
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true);
}
在这一步中,会初始化渲染函数相关的属性,包括 $slots
、$scopedSlots
、$createElement
等。同时也会定义 $attrs
和 $listeners
,用于处理继承的属性和监听器。
简单来说,Vue实例的过程中主要做了两件事:一个是初始化vm(各种事件,参数等),一个是挂载Vue实例到'#app'上。
在 Vue.js 中,可以在组件的模板中直接访问 methods
中定义的函数,是因为 Vue 在编译模板的过程中会将 methods
中的函数绑定到组件实例上,使其可以通过 this
直接访问。
这种行为是 Vue 的特性之一,旨在让开发者可以在模板中方便地调用组件实例的方法。当模板编译时,Vue 会将 methods
中的方法添加到组件实例上,使其成为组件实例的属性,因此可以通过 this
访问。
下面是一个简单的示例,说明为什么可以通过 this
直接访问到 methods
中的函数:
new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
},
methods: {
greet() {
console.log(this.message);
}
}
});
在这个示例中,methods
中的 greet
方法被绑定到了组件实例上。在模板中,你可以这样调用这个方法:
<div id="app">
<button @click="greet">Say Hello</button>
</div>
在点击按钮时,Vue 会通过组件实例调用 greet
方法,并在方法内部通过 this.message
访问到 data
中的 message
属性。
需要注意的是,尽管可以通过 this
直接访问 methods
中的函数,但在模板中访问 data
中的属性时,需要使用插值语法或指令,如 {{ message }}
或 v-bind
。这是因为 Vue 在模板编译时会对数据绑定进行特殊处理。
在 Vue.js 中,this
直接访问到 data
里面的数据是因为 Vue 在实例化组件时,会将 data
中的属性代理到组件实例上,从而可以通过 this
直接访问。
这种代理行为使得在组件的各个方法和模板中都可以方便地访问和操作 data
中的数据,而不需要显式通过实例的属性或方法来访问。
以下是一个示例,说明为什么可以通过 this
直接访问 data
中的数据:
new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
},
methods: {
showMessage() {
console.log(this.message);
}
}
});
在这个示例中,message
属性被定义在 data
中。在 methods
中的 showMessage
方法中,可以直接通过 this.message
访问到 data
中的 message
属性。
这种代理机制让代码更加简洁,提高了代码的可读性和维护性。需要注意的是,data
中的属性必须是在实例化时就已经存在的,如果后续添加新的属性,需要使用 Vue 提供的方法进行添加,并且这些新增属性不会被自动代理到实例上。
给我们的辅导服务打个广告。