写一个简单的响应式。
不是vue的源码更多的是一个理解。
首先需要遍历对象,为对象的每个属性设置setter
和getter
。
function observer(obj) {
const vm = {};
for (const prop in obj) {
if (Object.hasOwnProperty.call(obj, prop)) {
const dep = new Dep();
Object.defineProperty(vm, prop, {
get() {
dep.depend();
return data[prop];
},
set(val) {
data[prop] = val;
dep.notify();
}
})
}
}
return vm;
}
收集依赖通过Dep
实例进行收集,并且收集的是Watcher
实例。
Dep
class Dep {
deps = [];
notify() {
const deps = this.deps;
this.deps = [];
for (let i = 0; i < deps.length; i++) {
const watcher = deps[i];
schedule.push(watcher);
}
schedule.nextTick();
}
depend() {
watcher && this.deps.push(watcher);
}
}
Watcher
class Watcher {
constructor(data, prop, cb) {
this.data = data;
this.prop = prop;
this.cb = cb;
this.value = this.get();
}
get() {
watcher = this;
let value = this.data[this.prop];
watcher = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb(this.value, oldValue);
}
}
watcher
实例的执行是异步任务,并且微任务的优先级更高。需要调度器Schedule
class Schedule {
_task = new Set();
push(watcher) {
this._task.add(watcher);
}
_run() {
const task = this._task;
this._task = new Set();
for (const watcher of task) {
watcher.update();
}
}
nextTick() {
Promise.resolve().then(this._run.bind(this));
}
}
完整案例
<!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="first">1</div>
<script>
let watcher = null;
class Schedule {
_task = new Set();
push(watcher) {
this._task.add(watcher);
}
_run() {
const task = this._task;
this._task = new Set();
for (const watcher of task) {
watcher.update();
}
}
nextTick() {
Promise.resolve().then(this._run.bind(this));
}
}
const schedule = new Schedule();
class Dep {
deps = [];
notify() {
const deps = this.deps;
this.deps = [];
for (let i = 0; i < deps.length; i++) {
const watcher = deps[i];
schedule.push(watcher);
}
schedule.nextTick();
}
depend() {
watcher && this.deps.push(watcher);
}
}
class Watcher {
constructor(data, prop, cb) {
this.data = data;
this.prop = prop;
this.cb = cb;
this.value = this.get();
}
get() {
watcher = this;
let value = this.data[this.prop];
watcher = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb(this.value, oldValue);
}
}
const data = { count: 1 };
function observer(obj) {
const vm = {};
for (const prop in obj) {
if (Object.hasOwnProperty.call(obj, prop)) {
const dep = new Dep();
Object.defineProperty(vm, prop, {
get() {
dep.depend();
return data[prop];
},
set(val) {
data[prop] = val;
dep.notify();
}
})
}
}
return vm;
}
const observerData = observer(data);
const firstDOM = document.getElementById("first");
const oWatcher = new Watcher(observerData, "count", function (val, oldVal) {
console.log(val, oldVal);
firstDOM.innerHTML = val;
})
firstDOM.onclick = function () {
observerData.count++;
observerData.count++;
observerData.count++;
}
</script>
</body>
</html>
代码是提供一个思路,有些函数并不会进行参数兼容。 响应式的代码主要是看个意思,具体什么深度递归设置 Dep,处理循环引用这些问题不是没考虑到,因为这些代码仅仅提供给大家参考学习。 如果确实有需求的,制作一个完整的 MVVM,可以直接去看 vue 的源码。
在最近几天我把一些常见的前端手写题进行了整理,也希望能有更多的前端爱好者一起来学习和维护。
github地址 :Front-end-handwriting
我同时也将自己21年写的一个vue的音乐项目整理出来放到github上了,有兴趣的朋友可以看一下。
因为你之前努力过了,才会有今天的你。