我们之前说道过Object.defineProperty方法有一关键特性,就是数据劫持,通过get/set 拦截属性的读取和修改操作。Vue主要是通过数据劫持结合发布-订阅模式来实现的,利用Object.defineProperty来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
1. Vue2 实现过程
我们来具体看一下Vue的实现过程吧
-
在Vue类中,接收一个options参数,里面包含data等选项,在构造函数里,我需要将data对象进行响应式处理。这一步应该是通过遍历data的所有属性,并用Object.defineProperty来重新定义它们,使得当属性被访问时收集依赖,被修改时触发更新。
-
对于每个属性,需要有一个Dep(依赖收集器)来管理所有的Watcher实例。每当属性被读取时,就将当前的Watcher添加到Dep中;当属性被修改时,Dep就会通知所有Watcher执行更新操作。
-
既然有Watcher实例,那么就有一个Watcher类,它负责订阅属性的变化,并在变化时执行回调函数,比如更新视图。在Watcher的构造函数中,需要将自己设置为全局的一个目标实例,然后触发属性的getter,从而将Watcher添加到Dep中。
-
还有,可能需要一个编译器(Compiler)来解析模板中的指令,比如v-model,然后初始化相应的数据绑定和事件监听。例如,对于v-model,需要将input元素的值与数据属性同步,并在输入时更新数据属性,从而触发数据的setter,进一步通知更新。
现在,我需要一步步构建这些类。首先是Vue类,它初始化数据,进行响应式处理。然后,每个数据属性都有一个对应的Dep实例。当数据被访问时,收集当前的Watcher;数据变化时,通知Dep里的所有Watcher更新。
然后,Watcher类的作用是连接数据属性和具体的更新函数,比如更新DOM。当数据变化时,Watcher会被触发,执行更新函数。
2. Vue2 组成结构
总体来说应有以下结构:
- Vue类:
作用:
-
接收options,处理data。
-
将data的属性转为getter/setter。
-
调用observe函数进行响应式处理。
- Observer类:
作用:
-
遍历对象的所有属性,转为响应式。
-
每个属性有一个Dep实例。
- Dep类:
作用:
-
收集Watcher实例(subs数组)。
-
提供addSub方法添加Watcher。
-
提供notify方法触发所有Watcher的update。
- Watcher类:
作用:
-
在实例化时,将自身赋值给Dep.target。
-
访问对应的数据属性,触发getter,从而将自身添加到Dep中。
-
定义update方法,执行回调函数(比如更新DOM)。
接下来是具体的代码实现。可能还需要处理多个Watcher的情况,以及如何将它们正确地关联到对应的Dep。
然后,需要处理模板编译。比如,对于v-model,需要找到元素,添加事件监听,当输入时更新数据属性。同时,初始化input的值。对于{{}}文本,需要替换为对应的数据,并建立Watcher,当数据变化时更新文本内容。
不过,我们现在只是简单展示一下实现原理,直接操作DOM元素,而不是完整的编译器。例如,手动绑定一个input和显示元素,展示双向绑定的效果。
现在,我需要将上述思路转化为代码。可能先从Vue类开始:
class Vue{
constructor(options){
this.$options = options;
this._data = typeof options.data === 'function' ? options.data() : options.data;
//调用Observer函数进行响应式处理
new Observer(this._data);
this.compile();
}
compile() {
// 处理v-model绑定
const inputs = document.querySelectorAll('[v-model]');
inputs.forEach(input => {
const key = input.getAttribute('v-model').trim();
input.value = this._data[key];
input.addEventListener('input', (e) => {
this._data[key] = e.target.value;
});
});
// 处理{{ }} 绑定
const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配
elements.forEach(element => {
const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);
if (matches) {
const key = matches[1].trim();
new Watcher(this._data, key, (newVal) => {
element.innerHTML = newVal;
});
// 初始化显示
element.innerHTML = this._data[key];
}
});
}
}
// Observer类:
// 遍历对象的所有属性,转为响应式
// 每个属性有一个Dep实例
class Observer{
constructor(data){
this.walk(data);
}
walk(data){
Object.keys(data).forEach(key =>{
this.defineReactive(data,key,data[key]);
})
console.log(Object.keys(data))
}
defineReactive(obj,key,val){
const dep = new Dep();
Object.defineProperty(obj,key,{
enumberable:true,
configurable:true,
get(){
if(Dep.target){
dep.addSub(Dep.target)
}
return val;
},
set(newVal){
if(newVal === val) return;
val = newVal;
dep.notify();
}
})
}
}
// 收集Watcher实例(subs数组)
// 提供addSub方法添加wathcer
// 提供notify方法触发所有的Watcher 和 update。
class Dep{
constructor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
notify(){
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null;
// 在实例化时,将自身赋值给Dep.target
// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
// 定义update方法,执行回调函数(比如更新Dom)
class Watcher{
constructor(data,key,cb){
this.data = data;
this.key = key;
this.cb= cb;
this.value = this.get();
}
get(){
Dep.target = this; // 将当前Watcher实例赋值给Dep.target
const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
Dep.target = null;//收集完清空
return value;
}
update(){
const newVal = this.data[this.key];
if (newVal !== this.value) {
this.value = newVal;
this.cb(newVal);
}
}
}
以下是完成的可运行代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue2数据的双向绑定</title>
</head>
<body>
<input type="text" v-model="message">
<div id="app">
<p>{{ message }}</p>
</div>
<script>
// 收集Watcher实例(subs数组)
// 提供addSub方法添加wathcer
// 提供notify方法触发所有的Watcher 和 update。
class Dep{
constructor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
notify(){
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null;
// 在实例化时,将自身赋值给Dep.target
// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。
// 定义update方法,执行回调函数(比如更新Dom)
class Watcher{
constructor(data,key,cb){
this.data = data;
this.key = key;
this.cb= cb;
this.value = this.get();
}
get(){
Dep.target = this; // 将当前Watcher实例赋值给Dep.target
const value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中
Dep.target = null;//收集完清空
return value;
}
update(){
const newVal = this.data[this.key];
if (newVal !== this.value) {
this.value = newVal;
this.cb(newVal);
}
}
}
class Vue{
constructor(options){
this.$options = options;
this._data = typeof options.data === 'function' ? options.data() : options.data;
//调用Observer函数进行响应式处理
new Observer(this._data);
this.compile();
}
compile() {
// 处理v-model绑定
const inputs = document.querySelectorAll('[v-model]');
inputs.forEach(input => {
const key = input.getAttribute('v-model').trim();
input.value = this._data[key];
input.addEventListener('input', (e) => {
this._data[key] = e.target.value;
});
});
// 处理{{ }} 绑定
const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配
elements.forEach(element => {
const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);
if (matches) {
const key = matches[1].trim();
new Watcher(this._data, key, (newVal) => {
element.innerHTML = newVal;
});
// 初始化显示
element.innerHTML = this._data[key];
}
});
}
}
// Observer类:
// 遍历对象的所有属性,转为响应式
// 每个属性有一个Dep实例
class Observer{
constructor(data){
this.walk(data);
}
walk(data){
Object.keys(data).forEach(key =>{
this.defineReactive(data,key,data[key]);
})
console.log(Object.keys(data))
}
defineReactive(obj,key,val){
const dep = new Dep();
Object.defineProperty(obj,key,{
enumberable:true,
configurable:true,
get(){
if(Dep.target){
dep.addSub(Dep.target)
}
return val;
},
set(newVal){
if(newVal === val) return;
val = newVal;
dep.notify();
}
})
}
}
new Vue({
data: () => ({
message: "hello world"
})
});
</script>
</body>
</html>
看完了此篇文章,您是不是对Vue2实现原理有了更深刻的理解呢,如果觉得对您有帮助,还请一键三连哦!非常感谢!