写点什么

手动实现 mini-vue

用户头像
晓枫
关注
发布于: 2020 年 08 月 12 日



实现一个mini版本的vue,帮助你理解vue内部实现原理,梳理整个vue流程。

目标

目标就是手动实现vue的响应式功能,模板解析功能等,后续如果有精力在实现诸如生命周期,计算属性这些功能。

初始化

一开始是准备用纯静态文件搞,但是文件多了不太好分离,而且不能用`import``export`等,感觉很别扭,最终还是准备用webpack打包。

初始化没啥好说的,就是一些webpack简单配置,分文件等,我准备了一份基础目录,你用的时候可以直接clone下来,然后跟着看就行

[基础目录](https://github.com/xiaofeng-bm/mini-vue/tree/init)

observer响应式

源码 说一下vue的响应式原理,这是一道很经典的面试题,学过vue的,或者说专门准备过vue面试的,对这道题或多或少都会有一些理解。

有些人会知道,vue响应式原理的核心就是通过Object.defineProperty这个api来实现的,但是可能对于内部具体的实现原理不是太清楚,这节我就一点一点的带你理清vue中响应式的实现方式。

从结果出发

我们在初始化vue的时候,都是通过new Vue({}),传入一个对象来实现的。我们通过cdn引入vue,并且将vm实例打印出来看看具体都有些什么。

<!-- 通过cdn引入vue -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
msg: "hello world",
html: "<h3>this is v-html</h3>",
text: "<h3>this is v-text</h3>",
value: "123456",
},
});
console.log(vm)
</script>
</body>
</html>

打印出的vm有很多属性,我们重点关注图中标红的两个地方





既然用的时候是通过new Vue({}),那源码中肯定有个Vue构造函数,下面为了方便,用ES6的类来实现,简化代码如下:

class Vue {
constructor(options) {
this.$el = options.el ? document.querySelector(options.el) : '';
this._data = options.data;
}
}

现在data中的数据有了,下一步就是如何将其变为响应式数据。代码如下,我配一些注释,很好理解:

class Vue {
constructor(options) {
this.$el = options.el ? document.querySelector(options.el) : "";
this._data = options.data || [];
this.observer(this._data);
}
observer(data) {
if(!data || typeof data !== 'object') {
return
}
// 遍历data对象
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
// 数据响应式
defineReactive(data, key, value) {
// 如果value类型是object就会自动进行递归
this.observer(value)
// 不熟悉下面这个方法的可以去mdn看一下解释
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 注意:这里不能用data[key]来替代value,因为这样用就又会触发get方法,形成死循环
return value;
},
set: function (newVal) {
// 如果前后值相等就直接return
if (newVal === value) {
return;
}
console.log(`数据改变了,新数据为${newVal}`);
value = newVal;
},
});
}
}

main.js中我们进行初始化,代码如下:

import Vue from "./core/vue";
// 这里将vm实例挂载到window下,这样就可以在console中直接打印出来,方便调试
window.vm = new Vue({
el: "#app",
data: {
msg: "hello world",
html: "<h3>this is v-html</h3>",
text: "<h3>this is v-text</h3>",
value: "",
},
});

测试看一下效果:





改变一下数据看看:





结构优化

上面我们将所有的方法都写到了Vue这个构造函数内,不利于维护,所以将其拆分出来,新建一个observer.js文件,将observerdefineReactive这俩方法copy过去。

// 数据响应式
function defineReactive(data, key, value) {
// 如果value类型是object就会自动进行递归
observer(value);
// 不熟悉下面这个方法的可以去mdn看一下解释
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 注意:这里不能用data[key]来替代value,因为这样用就又会触发get方法,形成死循环
return value;
},
set: function (newVal) {
// 如果前后值相等就直接return
if (newVal === value) {
return;
}
console.log(`数据改变了,新数据为${newVal}`);
value = newVal;
},
});
}
export const observer = (data) => {
// 边界条件判断,同时也是递归的结束条件
if (!data || typeof data !== "object") {
return;
}
// 遍历data对象
Object.keys(data).forEach((key) => {
defineReactive(data, key, data[key]);
});
}

修改vue.js内容如下:

import { proxy } from "./proxy";
class Vue {
constructor(options) {
// 1、通过属性保存选项的数据
this.$el = options.el ? document.querySelector(options.el) : "";
this._data = options.data;
observer(this._data);
}
}
export default Vue;

总结

以上代码就实现了简单的数据响应式,但是瑕疵很多,比如我们在日常开发中获取data中的数据,都是直接通过this.msg这种方式获取,但上面这种需要通过vm._data.msg才能获取到,中间夹杂了一个_data,下一节我们就要讲如何将_data中的数据代理到Vue实例中,方便我们更好的操作数据



用户头像

晓枫

关注

天天爱瞎bb的菜鸟前端 2017.12.13 加入

还未添加个人简介

评论

发布
暂无评论
手动实现mini-vue