现在vue3.0版本已经发布了,各种工具也已经对vue3.0做了支持,最近有时间,想学习下源码,这边文章主要是对vue1.0版本的实现,后续会更新出vue2.0和vue3.0
所有的代码可以复制粘贴,即可正常运行
前言
我们在面试的时候,面试官经常问的一个问题就是,你知道vue响应式的原理吗,说不定还会让你手写一个vue,今天的文章就带你实现一个1.0版本的vue,吊打面试官。
代码分割
我们将代码分为以下几个模块,依次去实现:
- 1.响应式处理
- 1.5设置代理
- 3.模板编译
- 4.响应式更新
需要具备技能
-
Object.defineProperty
的使用。 - 闭包的使用
前期准备
文件目录:我们今天实现的是vue1.0版本的关注vue1.0的文件夹即可
![](https://img.haomeiwen.com/i16428535/6fa416b5c36200d0.png)
reactive.html
内容
<!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" />
<script src="./vue.js"></script>
<title>vue 1.0 源码实现</title>
</head>
<body>
<div id="app">{{counter}}</div>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0,
},
});
setInterval(() => {
app.counter += 1;
}, 1000);
</script>
</body>
</html>
编写代码
1.响应式处理
class Vue {
constructor (options) {
this.$options = options; //接收用户传来的参数
this.$data = options.data;
// 1、对data进行响应式处理
observe (this.$data);
// 1.5、设置代理
// 2.模板编译
}
}
/**
* 将传入的值进行响应式处理
* @param {*} value
*/
function observe (value) {
if (Array.isArray (value)) {
// TODO数组的响应式处理
} else {
// 对象的响应式处理
Object.keys (value).forEach (key => {
defineReactive (value, key, value[key]);
});
}
}
/**
* 利用defineReactive,将对象的属性进行拦截,也就是响应式处理
* @param {*} obj
* @param {*} key
* @param {*} val
*/
function defineReactive (obj, key, val) {
Object.defineProperty (obj, key, {
get () {
console.log ('get', key);
return val;
},
set (newVal) {
if (newVal !== val) {
console.log ('set', key);
val = newVal;
}
},
});
}
理解:
1.我们创建了Vue类,将用户传入的options赋值给$options
,将options.data赋值给$data
2.声明了observe函数,这个函数主要是判断传入的值是对象还是数组(数组的响应式处理暂时没做,只做了对象的响应式处理),如果是对象就遍历这个对象传入到defineReactive
函数中,defineReactive
是闭包
验证:
响应式处理做完了,此时按照我们reactive.html
文件中的代码,定时器访问定时访问app.counter,会触发get方法,从而我们可以在控制台看到输出
结果:
没有任何输出,此时我们还缺一步,设置代理,因为app
是Vue类这个实例,实例上并没有counter这个属性
1.5设置代理
我们在上面的代码,继续编写
class Vue {
constructor (options) {
this.$options = options; //接收用户传来的参数
this.$data = options.data;
// 1、对data进行响应式处理
observe (this.$data);
// 1.5、设置代理
proxy (this);
// 2.模板编译
}
}
/**
* 将传入的值进行响应式处理
* @param {*} value
*/
function observe (value) {
if (Array.isArray (value)) {
// TODO数组的响应式处理
} else {
// 对象的响应式处理
Object.keys (value).forEach (key => {
defineReactive (value, key, value[key]);
});
}
}
/**
* 利用defineReactive,将对象的属性进行拦截,也就是响应式处理
* @param {*} obj
* @param {*} key
* @param {*} val
*/
function defineReactive (obj, key, val) {
Object.defineProperty (obj, key, {
get () {
console.log ('get', key);
return val;
},
set (newVal) {
if (newVal !== val) {
console.log ('set', key);
val = newVal;
}
},
});
}
/**
* 设置代理
*/
function proxy (vm) {
Object.keys (vm.$data).forEach (key => {
Object.defineProperty (vm, key, {
get () {
return vm.$data[key];
},
set (newVal) {
if (newVal !== vm.$data[key]) {
vm.$data[key] = newVal;
}
},
});
});
}
理解:
1.创建proxy方法,将当前的Vue实例传进去,利用defineProperty
方法,将data中的属性,添加到当前实例上面
2.proxy方法是闭包,当访问app.counter的时候,最终会指向defineReactive
中的get方法,返回val
验证:
访问app.counter的时候,是否有输出
结果
输出get counter
2.模板编译
接着上面的代码继续
class Vue {
constructor (options) {
this.$options = options; //接收用户传来的参数
this.$data = options.data;
// 1、对data进行响应式处理
observe (this.$data);
// 1.5、设置代理
proxy (this);
// 2.模板编译
new Compile (options.el, this);
}
}
/**
* 将传入的值进行响应式处理
* @param {*} value
*/
function observe (value) {
if (Array.isArray (value)) {
// TODO数组的响应式处理
} else {
// 对象的响应式处理
Object.keys (value).forEach (key => {
defineReactive (value, key, value[key]);
});
}
}
/**
* 利用defineReactive,将对象的属性进行拦截,也就是响应式处理
* @param {*} obj
* @param {*} key
* @param {*} val
*/
function defineReactive (obj, key, val) {
Object.defineProperty (obj, key, {
get () {
console.log ('get', key);
return val;
},
set (newVal) {
if (newVal !== val) {
console.log ('set', key);
val = newVal;
}
},
});
}
/**
* 设置代理
*/
function proxy (vm) {
Object.keys (vm.$data).forEach (key => {
Object.defineProperty (vm, key, {
get () {
return vm.$data[key];
},
set (newVal) {
if (newVal !== vm.$data[key]) {
vm.$data[key] = newVal;
}
},
});
});
}
/**
* 模板编译
*/
class Compile {
constructor (el, vm) {
this.$el = document.querySelector (el);
this.$vm = vm;
this.compile (this.$el);
}
compile (node) {
node.childNodes.forEach (node => {
if (this.isInter (node)) {
this.update (node);
}
});
}
isInter (node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test (node.textContent);
}
// node {{counter}}
update (node) {
const updater = () => {
node.textContent = this.$vm[RegExp.$1];
};
updater ();
}
}
理解:
1.我们新增了compile这个类,这个类接收一个宿主元素和当前的vm实例
2.利用document.querySelector
查找到这个元素,遍历里面所有的子节点,通过isInter
这个函数校验的子节点,必然是{{***}}
这种类型的节点,将这个节点传入到update函数中,利用正则读取到当前data里面的key,从当前的$vm实例上面读取到key对应的value,然后直接设置node.textContent
完成模板编译
验证:
html界面显示0
结果
模板编译成功,成功显示
响应式更新
我们都知道,响应式更新,是vue里面的精髓,我会竭尽所能,给大家描述清楚,如果有看不懂的,可以私信我,也算是一个交流的过程
class Vue {
constructor (options) {
this.$options = options; //接收用户传来的参数
this.$data = options.data;
// 1、对data进行响应式处理
observe (this.$data);
// 1.5、设置代理
proxy (this);
// 2.模板编译
new Compile (options.el, this);
}
}
/**
* 将传入的值进行响应式处理
* @param {*} value
*/
function observe (value) {
if (Array.isArray (value)) {
// TODO数组的响应式处理
} else {
// 对象的响应式处理
Object.keys (value).forEach (key => {
defineReactive (value, key, value[key]);
});
}
}
/**
* 利用defineReactive,将对象的属性进行拦截,也就是响应式处理
* @param {*} obj
* @param {*} key
* @param {*} val
*/
function defineReactive (obj, key, val) {
const dep = new Dep ();
Object.defineProperty (obj, key, {
get () {
console.log ('get', key);
Dep.target && dep.addDep (Dep.target);
return val;
},
set (newVal) {
if (newVal !== val) {
console.log ('set', key);
val = newVal;
dep.notify ();
}
},
});
}
/**
* 设置代理
*/
function proxy (vm) {
Object.keys (vm.$data).forEach (key => {
Object.defineProperty (vm, key, {
get () {
return vm.$data[key];
},
set (newVal) {
if (newVal !== vm.$data[key]) {
vm.$data[key] = newVal;
}
},
});
});
}
/**
* 模板编译
*/
class Compile {
constructor (el, vm) {
this.$el = document.querySelector (el);
this.$vm = vm;
this.compile (this.$el);
}
compile (node) {
node.childNodes.forEach (node => {
if (this.isInter (node)) {
this.update (node);
}
});
}
isInter (node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test (node.textContent);
}
// node {{counter}}
update (node) {
const updater = () => {
node.textContent = this.$vm[RegExp.$1];
};
updater ();
new Watcher (updater, this.$vm, RegExp.$1);
}
}
/**
* 响应式更新
*/
class Dep {
constructor () {
this.watchers = [];
}
addDep (watcher) {
this.watchers.push (watcher);
}
notify () {
this.watchers.forEach (v => {
v.update ();
});
}
}
class Watcher {
constructor (updateFn, vm, key) {
this.$updateFn = updateFn;
this.$vm = vm;
this.$key = key;
Dep.target = this;
this.$vm[this.$key];
Dep.target = null;
}
update () {
this.$updateFn ();
}
}
理解:
1.创建了Dep和Watcher俩个类
2.vue1.0是一个key对应的一个dep,一个dep对应的多个watcher
3.响应式处理的时候,defineReactive
是闭包环境,不会被回收,直接在当前环境new Dep()
,在compile的时候,new Watcher()
将当前的updater方法传递给Watcher,Watcher在实例话的时候,会访问一下当前的key,从而触发get方法,Watcher又把自己加到Dep.target属性上面,(这就是是经典的发布订阅模式,)之后在get方法中,调用dep.addDep()方法把watcher添加到当前dep的肚子里面,每次set的时候,调用notify方法,从而可以触发更新
验证:
counter一直在加加
结果:
counter一直在变
结束
代码已上传gitee,地址:https://gitee.com/DayLoveNight/vue-code-edit.git,2.0,3.0后续会更新
网友评论