一、真实DOM渲染
<!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>
<h2>哈哈哈</h2>
<h2>呵呵呵</h2>
<div>发发的说法</div>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</body>
</html>
二、vue的渲染器实现
- 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"></div>
<script src="./renderer.js"></script>
<script>
const increment = () => {
console.log("+1");
};
const dcrement = () => {
console.log("-1");
};
//1.通过h函数来创建一个vnode
const vnode = h("div", { class: "title" }, [
h("h2", null, "当前计数:0"),
h("button", { onclick: increment }, "+1"),
]);
//2.通过mount函数,将vnode挂载到div#app上
mount(vnode, document.querySelector("#app"));
//3.创建新的vnode
setTimeout(() => {
const vnode1 = h("div", { class: "why" }, [
h("h2", null, "当前计数:222"),
h("button", { onclick: dcrement }, "+222"),
]);
patch(vnode, vnode1);
}, 2000);
</script>
</body>
</html>
- render.js
const h = (tag, props, children) => {
//vnode -> javascript对象 -> {}
return {
tag,
props,
children,
};
};
const mount = (vnode, container) => {
//vnode -> element
//1.创建出真实元素,并在vnode上保留el
const el = (vnode.el = document.createElement(vnode.tag));
//2.处理props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
if (key.startsWith("on")) {
//对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), value);
} else {
el.setAttribute(key, value);
}
}
}
//3.处理children
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach((item) => {
mount(item, el);
});
}
}
//4.将el挂载到container上
container.appendChild(el);
};
const patch = (n1, n2) => {
if (n1.tag !== n2.tag) {
const n1ElParent = n1.el.parentElement;
n1ElParent.removeChild(n1.el);
mount(n2, n1ElParent);
} else {
//1.取出element对象,并且在n2中进行保存
const el = (n2.el = n1.el);
//2.处理props
const oldProps = n1.props || {};
const newProps = n2.props || {};
//2.1.获取所有的newProps添加到el
for (const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (newValue !== oldValue) {
if (key.startsWith("on")) {
//对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), newValue);
} else {
el.setAttribute(key, newValue);
}
}
}
//2.2.删除旧的props
for (const key in oldProps) {
if (key.startsWith("on")) {
const value = oldProps[key];
el.removeEventListener(key.slice(2).toLowerCase(), value);
}
if (!(key in newProps)) {
el.removeAttribute(key);
}
}
//3.处理children
const oldChildren = n1.children || [];
const newChildren = n2.children || [];
if (typeof newChildren === "string") {
//情况一:newChildren本身是一个 string
//边界情况 (edge case)
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren;
}
} else {
el.innerHTML = newChildren;
}
} else {
//情况二:newChildren本身是一个数组
if (typeof oldChildren === "string") {
el.innerHTML = "";
newChildren.forEach((item) => {
mount(item, el);
});
} else {
//oldChildren [v1,v2,v3]
//newChildren [v1,v5,v6,v8,v9]
//1.前面有相同节点的元素进行patch操作
const commonLength = Math.min(oldChildren.length, newChildren.length);
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i]);
}
//2.newChildren.length > oldChildren.length
if (newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach((item) => {
mount(item, el);
});
}
//3.newChildren.length < oldChildren.length
if (newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach((item) => {
el.removeChild(item.el);
});
}
}
}
}
};
三、响应式系统实现
vue2的响应式
class Dep {
constructor() {
this.subscribers = new Set();
}
addEffect(effect) {
this.subscribers.add(effect);
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
//Map({key:value}):key是一个字符串
//weakMap({key(对象):value}) key是一个对象 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
//1.根据(target)对象取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
//2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue2对raw进行数据劫持
function reactive(raw) {
Object.keys(raw).forEach((key) => {
const dep = getDep(raw, key);
let value = raw[key];
Object.defineProperty(raw, key, {
get() {
dep.depend();
return value;
},
set(newValue) {
if (value !== newValue) {
value = newValue;
dep.notify();
}
},
});
});
return raw;
}
//测试代码
const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.88 });
info.name = "125454";
watchEffect(function () {
console.log("effect1:", info.counter * 2, info.name);
});
watchEffect(function () {
console.log("effect2:", info.counter * info.counter);
});
watchEffect(function () {
console.log("effect3:", info.counter + 10, info.name);
});
watchEffect(function () {
console.log("effect4:", foo.height);
});
info.counter++;
//info.name = "why";
//foo.height = 2;
vue3的响应式系统实现
class Dep {
constructor() {
this.subscribers = new Set();
}
addEffect(effect) {
this.subscribers.add(effect);
}
notify() {
this.subscribers.forEach((effect) => {
effect();
});
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
//Map({key:value}):key是一个字符串
//weakMap({key(对象):value}) key是一个对象 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
//1.根据(target)对象取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
//2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue3对raw进行数据劫持
function reactive(raw) {
return new Proxy(raw, {
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();
},
});
}
//测试代码
const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.88 });
info.name = "125454";
watchEffect(function () {
console.log("effect1:", info.counter * 2, info.name);
});
watchEffect(function () {
console.log("effect2:", info.counter * info.counter);
});
watchEffect(function () {
console.log("effect3:", info.counter + 10, info.name);
});
watchEffect(function () {
console.log("effect4:", foo.height);
});
//info.counter++;
info.name = "why";
//foo.height = 2;
完整的miniVue
目录结构
- index.js
function createApp(rootComponent) {
return {
mount(selecter) {
const container = document.querySelector(selecter);
let isMounted = false;
let oldVnode = null;
watchEffect(function () {
if (!isMounted) {
oldVnode = rootComponent.render();
mount(oldVnode, container);
isMounted = true;
} else {
const newVnode = rootComponent.render();
patch(oldVnode, newVnode);
oldVnode = newVnode;
}
});
},
};
}
- index.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"></div>
<script src="../02_渲染器实现/renderer.js"></script>
<script src="../03_响应式系统/04_响应式vue3实现.js"></script>
<script src="./index.js"></script>
<script>
//1.创建根组件
const App = {
data: reactive({
counter: 0,
}),
render() {
return h("div", null, [
h("h2", null, `当前计数:${this.data.counter}`),
h(
"button",
{
onClick: ()=> {
this.data.counter++;
},
},
"+1"
),
]);
},
};
//2.挂载根组件
const app = createApp(App);
app.mount("#app");
</script>
</body>
</html>
- mini-vue下 index.js
function createApp(rootComponent) {
return {
mount(selector) {
const container = document.querySelector(selector);
let isMounted = false;
let oldVNode = null;
watchEffect(function() {
if (!isMounted) {
oldVNode = rootComponent.render();
mount(oldVNode, container);
isMounted = true;
} else {
const newVNode = rootComponent.render();
patch(oldVNode, newVNode);
oldVNode = newVNode;
}
})
}
}
}
- mini-vue下reactive.js
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
// 1.根据对象(target)取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue3对raw进行数据劫持
function reactive(raw) {
return new Proxy(raw, {
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();
}
})
}
// const proxy = reactive({name: "123"})
// proxy.name = "321";
// // 测试代码
// const info = reactive({counter: 100, name: "why"});
// const foo = reactive({height: 1.88});
// // watchEffect1
// watchEffect(function () {
// console.log("effect1:", info.counter * 2, info.name);
// })
// // watchEffect2
// watchEffect(function () {
// console.log("effect2:", info.counter * info.counter);
// })
// // watchEffect3
// watchEffect(function () {
// console.log("effect3:", info.counter + 10, info.name);
// })
// watchEffect(function () {
// console.log("effect4:", foo.height);
// })
// info.counter++;
// info.name = "why";
// foo.height = 2;
- mini-vue下render.js
const h = (tag, props, children) => {
// vnode -> javascript对象 -> {}
return {
tag,
props,
children
}
}
const mount = (vnode, container) => {
// vnode -> element
// 1.创建出真实的原生, 并且在vnode上保留el
const el = vnode.el = document.createElement(vnode.tag);
// 2.处理props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
if (key.startsWith("on")) { // 对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value);
}
}
}
// 3.处理children
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach(item => {
mount(item, el);
})
}
}
// 4.将el挂载到container上
container.appendChild(el);
}
const patch = (n1, n2) => {
if (n1.tag !== n2.tag) {
const n1ElParent = n1.el.parentElement;
n1ElParent.removeChild(n1.el);
mount(n2, n1ElParent);
} else {
// 1.取出element对象, 并且在n2中进行保存
const el = n2.el = n1.el;
// 2.处理props
const oldProps = n1.props || {};
const newProps = n2.props || {};
// 2.1.获取所有的newProps添加到el
for (const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (newValue !== oldValue) {
if (key.startsWith("on")) { // 对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue);
}
}
}
// 2.2.删除旧的props
for (const key in oldProps) {
if (key.startsWith("on")) { // 对事件监听的判断
const value = oldProps[key];
el.removeEventListener(key.slice(2).toLowerCase(), value)
}
if (!(key in newProps)) {
el.removeAttribute(key);
}
}
// 3.处理children
const oldChildren = n1.children || [];
const newChidlren = n2.children || [];
if (typeof newChidlren === "string") { // 情况一: newChildren本身是一个string
// 边界情况 (edge case)
if (typeof oldChildren === "string") {
if (newChidlren !== oldChildren) {
el.textContent = newChidlren
}
} else {
el.innerHTML = newChidlren;
}
} else { // 情况二: newChildren本身是一个数组
if (typeof oldChildren === "string") {
el.innerHTML = "";
newChidlren.forEach(item => {
mount(item, el);
})
} else {
// oldChildren: [v1, v2, v3, v8, v9]
// newChildren: [v1, v5, v6]
// 1.前面有相同节点的原生进行patch操作
const commonLength = Math.min(oldChildren.length, newChidlren.length);
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChidlren[i]);
}
// 2.newChildren.length > oldChildren.length
if (newChidlren.length > oldChildren.length) {
newChidlren.slice(oldChildren.length).forEach(item => {
mount(item, el);
})
}
// 3.newChildren.length < oldChildren.length
if (newChidlren.length < oldChildren.length) {
oldChildren.slice(newChidlren.length).forEach(item => {
el.removeChild(item.el);
})
}
}
}
}
}