# 前言
我们在一开始的使用Vue的时候,是通过Vue.js的形式来引入的,如下:
<div id="app">
{{counter}}
</div>
<script src="./vue.js"> </script>
<script>
const app = new Vue({
el: "#app",
data:{
counter: 1
}
});
setInterval(_=>{
app.counter++;
},1000);
</script>
今天我们先实现第一步:app.counter++可以正常累加。
# 实现我们自己的 MyVue 类
这里会用到上篇文章写的数据响应式的一些实现原理,大家可以参考一下
myVue.js 文件
// 定义响应式属性
function defineReactive(obj, key, val) {
// 递归监测
observe(val);
// 定义响应式
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
// 遍历响应式处理
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
};
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]));
}
class MyVue {
constructor(options) {
// 保存选项
this.$options = options;
this.$data = options.data;
// 对 data 实现数据响应式
observe(options.data)
}
}
以上代码要是实现了两步:
- 实现选项
$options好$data的预存储,方便以后使用 - 实现对
options.data中所有属性进行响应式处理
准备好以后,我们执行以下代码:
<div>
{{counter}}
</div>
<input type="text" name="" id="">
<script src="./src/myVue.js"></script>
<script>
const app = new MyVue({
el: "#app",
data: {
counter: 1
}
});
setInterval(_ => {
app.counter++;
}, 1000);
</script>
预期结果应该是{{counter}}未被编译,直接展示,而定时器会正常执行,并每隔一秒会输出我们定义响应式时defineReactive方法里的get和set里的console输出。
结果是{{counter}}未被编译直接展示没有问题,但console未正常输出,这是为什么呢?
我们改造下代码,把定时里的app.counter改成app.$data.counter,如下:
setInterval(_ => {
app.$data.counter++;
}, 1000);
改造后就可以正常输出了,如下图:
原因也很简单:因为我们只对options.data做了响应式,没有对MyVue实例做响应式,需要我们用一个代理方法默认去把options.data中的属性代理到MyVue类中的this上,这个代理的方法我们叫作proxy,代码如下:
// 定义响应式属性
function defineReactive(obj, key, val) {
// 递归监测
observe(val);
// 定义响应式
Object.defineProperty(obj, key, {
get() {
console.log('get', key, obj);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
// 遍历响应式处理
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
};
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]));
}
// 将传入的对象中的所有key都代理到指定的对象上
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(v) {
vm.$data[key] = v;
}
})
})
}
class MyVue {
constructor(options) {
// 保存选项
this.$options = options;
this.$data = options.data;
// 对 data 实现数据响应式
observe(options.data);
// 做代理
proxy(this)
}
}
这时我们再去执行定时器的代码,如下:
setInterval(_ => {
app.counter++
}, 1000);
可以正常执行了,如下图:
到这里我们已经实现了简单的数据驱动,看过源码的小伙伴都知道,Vue的data分两种形式:数组和对象,我们今天就是模拟实现了对象类型的,那怎么加个判断呢?对不同的数据类型执行不同的观测逻辑,其实这就用到了另一个类:Observer,我们一起改造下代码,如下:
// 定义响应式属性
function defineReactive(obj, key, val) {
// 递归监测
observe(val);
// 定义响应式
Object.defineProperty(obj, key, {
get() {
console.log('get', key, obj);
return val;
},
set(newVal) {
if (newVal != val) {
console.log('set', newVal)
val = newVal;
}
}
})
}
// 遍历响应式处理
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
};
// 创建观测实例
new Observer(obj);
}
// 将传入的对象中的所有key都代理到指定的对象上
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(v) {
vm.$data[key] = v;
}
})
})
}
class Observer {
constructor(obj) {
if (Array.isArray(obj)) {
// 数组类型的响应式观测
} else {
// 非数组类型的响应式观测
this.walk(obj);
}
}
walk(obj) {
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]));
}
}
class MyVue {
constructor(options) {
// 保存选项
this.$options = options;
this.$data = options.data;
// 对 data 实现数据响应式
observe(options.data);
// 做代理
proxy(this)
}
}
改造完成后,再次执行如下代码,应该会正常执行,没有什么变化:
setInterval(_ => {
app.counter++
}, 1000);
好了到此就简单先实现了Vue的数据驱动的原理,希望对你理解Vue有所帮助。下一篇 教你如何写 Vue 源码中关于数据驱动的实现 我们来实现表达式编译、依赖收集、Watcher,欢迎大神们留言评论,nice ~~