这是我参与更文挑战的第2天,活动详情查看: 更文挑战
一,前言
上篇,使用 rollup 完成了 Vue2 源码环境的搭建
本篇,介绍 Vue 的初始化流程
二,Vue 简介
简单说明以下两个概念性问题
1,问题:Vue 是 MVVM 框架吗?
在 Vue 官网上是这样说的:
Vue 是一套用于构建用户界面的渐进式框架,
Vue 并没有完全支持 MVVM 模型,但 Vue 的设计受到了它的启发,
变量名 vm 是 vue model 的缩写,表示 vue 实例;
所以,严格来说 Vue 并不是一个 MVVM 框架,因为它没有遵循 MVVM 模式要求:
MVVM 模式,只能通过视图更改数据,只能通过数据更改视图,其他方法都不行;
而 Vue 可以通过 ref 获取 dom 进行操作
2,问题:Vue 的“双向绑定”和“单向数据流”矛盾吗?
不矛盾,这是一个理解上的问题;
双向绑定:指数据变化会更新视图;视图变化也会影响数据;
双向绑定主要依靠 v-model 实现;
单向数据流:指响应式数据,即数据变化后会更新视图;
在Vue2中,响应式数据原理依靠 Object.defineProperty 实现;
三,使用 Vue
1,Vue Demo
以一个简单的 Vue Demo 为例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 模板 -->
<div id=app>{{message}}</div>
<!-- 引入 vue -->
<script src="./vue.js"></script>
<script>
<!-- 初始化 Vue,传入 options 对象 -->
let vm = new Vue({
el: '#app',
// 1,data 是对象
data: {
message: 'Hello Vue'
}
// 2,data 是函数,返回一个对象
// data() {
// return { message: 'Hello Vue' }
// }
});
</script>
</body>
</html>
Vue 初始化时,会传入 el 挂载点、data 数据;
初始化完成后,data 中的 message 将成为响应式数据:当数据变化时会更新视图,视图变化也会影响数据;
2,问题:响应式数据原理
Object.defineProperty 数据劫持(导致 vue2 性能瓶颈,Vue3 采用 Proxy)
3,问题:Vue 中的 data 数据可以是对象吗?
根组件不会被共享,可以是对象也可以是函数;
非根组件必须为函数,否则 data 状态会多组件共享
四,Vue 的初始化操作
Vue 在设计上使用了原型模式,所有功能都通过“原型扩展”的方式进行添加;
1,原型方法 _init
在 Vue 原型上扩展一个 _init 方法(原型方法),用于 Vue 的初始化操作
/**
* vue中的所有功能,都是通过原型扩展的方式添加的
* @param {*} options // new Vue时传入的 options 配置对象
*/
function Vue(options){
this._init(options); // 调用Vue原型上_init方法
}
// 在Vue原型上扩展一个原型方法_init,用于vue的初始化操作
Vue.prototype._init = function(options) {
}
export default Vue;
当 vue 中有很多功能时,就会出现非常多的Vue.prototype.xxx
,可以对不同功能进行模块化处理;
2,重构:原型方法 _init 模块化处理
将用于初始化操作的原型方法_init
,单独抽离成一个独立模块initMixin
,导入src/index.js
中使用:
// src/init.js
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
console.log(options)
}
}
// src/index.js
import { initMixin } from "./init";// 引入initMixin模块
function Vue(options){
this._init(options); // 调用 Vue 原型上的 _init 方法
}
// 调用 initMixin 进行 Vue 初始化操作
initMixin(Vue)
export default Vue;
执行分析:
- 当引入
vue.js
时,通过调用 initMixin 在 Vue 原型上扩展了 _init 方法; - 当执行
new Vue
时,this._init
就会调用原型上的_init方法执行初始化逻辑;
设计分析:
- 在后续的组件化开发中,通过
Vue.extend
创建的子组件会继承 Vue,此时,子组件也能够调用 _init 方法;
3,重构后测试
4,原型方法 _init 的 this 指向
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
console.log(vm) // this 指向当前 vm 实例
}
}
打印原型方法 _init 中的 this:this 指向当前的 vm 实例;
5,挂载 options 选项 vm.$options
用户通过new Vue
实例化时会传入options对象,为了便于vue中其他方法获取options对象,直接将options选项挂载(暴露)到vm实例上(即vm.$options = options
);
此时,其他方法就可以通过vm.$options
获取到Vue初始化时用户传入的options选项了;
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this; // this 指向当前 vm 实例
vm.$options = options; // 将 Vue 实例化时用户传入的 options 暴露到 vm 实例上
}
}
备注:
vm.$xxx
变量命名方式,表示 vue 内部变量;
当前 options 中只有 el 和 data;
- 如果有 el:需要将 data 数据渲染到视图上;
- 如果有 data:需要对 data 进行初始化操作(即状态的初始化);
// src/index.js
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
// new Vue 时,传入 options 选项,包含 el 和 data
initState(vm); // 状态的初始化
// 1,data数据的初始化
if(opts.data){
initData(vm);
}
// 2,props 数据的初始化
// 3,watch 数据的初始化
// 4,computed 数据的初始化
if (vm.$options.el) {
console.log("有el,需要挂载")
}
}
}
这样写显然是不太合理的;下面,优先处理 data 状态的初始化;
6,initState 方法:状态的初始化
在实际使用中,数据不仅来源于传入的 data,还可能来自 props、watch、computed...
所以,就需要一个统一的函数,对数据的初始化进行集中处理:initState 状态初始化方法(同 init.js,依然是模块化的处理思路,创建 state.js);
// src/state.js
export function initState(vm){
// 获取options:_init 中已将 options 挂载到 vm.$options
const opts = vm.$options;
// 1,data数据的初始化
if(opts.data){
initData(vm);
}
// 2,props 数据的初始化
// 3,watch 数据的初始化
// 4,computed 数据的初始化
}
function initData(vm){
console.log("进入 state.js - initData,数据初始化操作")
}
先写好框架,initData方法后续实现;
导入模块并使用 initState 方法,完成状态的初始化操作:
// src/index.js
import { initState } from "./state";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
// 在 new Vue 时,传入的 options 选项中包含 el 和 data
vm.$options = options;
// 状态的初始化
initState(vm);
// 处理数据渲染并挂载到el
if (vm.$options.el) {
console.log("有el,需要挂载")
}
}
}
五,结尾
本篇主要介绍了 Vue 数据的初始化流程,主要包含的几点:
- 1,加载 Vue 时,通过 initMixin 方法,在 Vue 原型上扩展 _init 方法;
- 2,执行 new Vue 时,调用 Vue 原型方法 _init,暴露 options 选项、执行状态初始化和挂载流程;
- 3,initState 状态初始化:对状态的多种来源进行统一的初始化处理;
下一篇,数据劫持:Object.defineProperty;
维护日志
- 20210605:调整文章的目录结构
- 20230107:添加并优化了若干描述和代码注释,使表述更加清晰易懂,更新文章摘要;