最终实现效果能够通过render函数创建vnode,并通过mount函数挂载到指定的html元素中
响应式部分通过reactive函数对对象进行数据劫持,在数据get时添加依赖,在数据set时遍历依赖数组,执行数据中的函数
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">
<title>Document</title>
</head>
<body>
<div id="app" style="height: 100px;"></div>
<script src="./renderer.js"></script>
<script src="./reactive.js"></script>
<script>
/* --------------------------------------------------渲染器部分----------------------------------
const vnode = h('div', {
class: "vnode",
onClick: () => {
console.log(222)
}
}, [
h('div', null, "1.哈哈哈"),
h('div', null, [
h('div', { class: "1" }, "1"),
h('div', { class: "2" }, "2"),
]),
]);
mount(vnode, document.getElementById("app"))
const vnode1 = h('div', {
class: "vnode",
onClick: () => {
console.log(222)
}
}, [
h('div', null, "1.哈哈哈"),
h('div', null, "2.呵呵呵"),
]);
setTimeout(() => {
patch(vnode, vnode1);
}, 2000); */
/* ---------------------------------------------------------响应式部分-------------------------------------------------------- */
const info = reactive({
name: "woaixc",
age: 18,
});
const info2 = reactive({
name: "woaixc",
age: 18,
});
watchEffect(function () {
console.log("info", info.age);
});
watchEffect(function () {
console.log("info2", info2.age);
});
info.age = 20;
</script>
</body>
</html>
渲染函数部分 renderer.js
function h(tag, props, children) {
return {
tag,
props,
children,
};
}
function mount(vnode, container) {
// 1.创建元素
const el = (vnode.el = document.createElement(vnode.tag));
// 2.创建属性
if (vnode.props) {
for (const key in vnode.props) {
// 2.1 判断如果是事件监听
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key]);
} // 2.1 如果是普通属性
else {
el.setAttribute(key, vnode.props[key]);
}
}
}
// 3.处理children (该处只考虑children为string和array的情况)
if (typeof vnode.children == "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach((item) => {
mount(item, el);
});
}
// 挂载到容器上
container.appendChild(el);
}
// diff算法
function patch(n1, n2) {
// n2.el之前未设置是空的,先使三个el相同,引用类型,更改一个全都改
const el = (n2.el = n1.el);
// 1.对比新旧节点tag
// 新旧节点tag不相同则直接删除旧节点,插入新节点
if (n1.tag !== n2.tag) {
const n1ParentElement = n1.el.parentElement;
// 删除旧节点
n1ParentElement.removeChild(n1.el);
// 插入新节点
mount(n2, n1ParentElement);
} else {
// 2.对比新旧节点props
if (n2.props) {
// 先遍历新的props对象
for (const key in n2.props) {
const newValue = n2.props[key];
const oldValue = n1.props[key];
// key相同的情况下 新旧props的值不同则替换
if (newValue !== oldValue) {
// 如果是事件则绑定事件
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), newValue);
} else {
el.setAttribute(key, newValue);
}
}
// key相同的情况下 新旧props的值相同则维持原样
}
// 遍历旧节点中不存在 新节点props中的属性,删除掉
for (const key in n1.props) {
const value = n1.props[key];
if (!(key in n2.props)) {
// 如果是事件则移除事件
if (key.startsWith("on")) {
el.removeEventListener(key.slice(2).toLowerCase(), value);
} else {
el.removeAttribute(key);
}
}
}
}
// 3.对比新旧节点children
let newChildren = n2.children || {};
let oldChildren = n1.children || {};
// 新节点类型为string
if (typeof newChildren == "string") {
// 旧节点类型为string
if (typeof oldChildren == "string") {
if (newChildren !== oldChildren) {
// 设置元素内展示
el.textContent = newChildren;
}
} else {
// 旧节点类型为array,直接用新节点的string替换
el.innerHTML = newChildren;
}
} else {
// 新节点类型为array
// 旧节点类型为string
if (typeof oldChildren == "string") {
el.innerHTML = "";
newChildren.forEach((item) => {
mount(item, el);
});
} else {
// 新旧节点同时为array的情况下
// 获取公共长度然后遍历比对
const commonLength = Math.min(oldChildren.length, newChildren.length);
// 3.1 遍历公共部分
// [v1,v2,v3,v4]
// [v2,v1]
for (const index in commonLength) {
patch(oldChildren[index], newChildren[index]);
}
// 如果新节点children更长则补充节点,如果就节点children更长则删除节点
if (newChildren.length > oldChildren.length) {
newChildren.slice(commonLength).forEach((item) => {
mount(item, el);
});
}
if (newChildren.length < oldChildren.length) {
oldChildren.slice(commonLength).forEach((item) => {
el.removeChild(item.el);
});
}
}
}
}
}
响应式部分 reactive.js
// 依赖类
class Dep {
constructor() {
// 使用set集合作为依赖收集器,防止同一个依赖多次收集
this.subscribers = new Set();
}
// 依赖添加
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
// 依赖激活,遍历执行依赖中所有effect函数
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
// 执行一次,添加依赖到依赖收集器中
effect();
activeEffect = null;
}
// 使用 Map(target: Map(key: dep))这种数据结构来对依赖进行管理
// 使用weakMap的原因是 weakMap的键是弱引用,弱引用指的是weakMap对键的引用不会影响gc回收,当引用的键在外部被置为null,垃圾回收机制会将其回收掉
let targetMap = new WeakMap();
function getDep(target, key) {
// 根据target 获取Map(key: dep)
let depsMap = targetMap.get(target);
// 如果depsMap不存在
if (!depsMap) {
// 创建MAP
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 根据key 获取 dep类
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
/* -----------------vue2 Object.defineProperty方式做响应式 */
/* function reactive(target) {
if (target) {
// 遍历对象中的属性
for (key in target) {
const dep = getDep(target, key);
let value = target[key];
// 数据劫持
Object.defineProperty(target, key, {
get() {
//添加依赖
dep.depend();
return value;
},
set(newValue) {
value = newValue;
dep.notify();
},
});
}
return target;
}
} */
/* -----------------vue3 通过ES6的Proxy方式做响应式 */
function reactive(target) {
return new Proxy(target, {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
return target[key];
},
set(target, key, newValue) {
const dep = getDep(target, key);
target[key] = newValue;
dep.notify();
},
});
}