Vue3+TS Day19 - Vue源码理论、编译器、渲染器、
2021-12-14 本文已影响0人
望穿秋水小作坊
一、Vue源码理论基础
1、DOM是什么的简称?DOM和js之间的关系?
- 【DOM】Document Object Model,文档对象模型:是HTML文档的编程接口。简言之,它会将web页面和脚本语言连接起来。
- 最开始的时候,JavaScript和DOM是交织在一起的,但它们最终演变成了两个独立的实体。JavaScript可以访问和操作存储在DOM中的内容。
2、我们传统的前端开发中,编写的原生HTML,最终被渲染到浏览器上,它是什么样的过程呢?
- 原生HTML元素 -> DOM树 -> 页面展示
3、三大前端流行框架Vue、Angular、React都采用虚拟DOM的技术,说说虚拟DOM的优势?(两方面吧)
- 【首先】直接操作原生DOM有很多限制,比如diff、clone等等;虚拟DOM就是JavaScript对象,操作起来就非常简单方便了。
- 【其次】方便实现跨平台,虚拟DOM可以最终渲染成任意平台的页面(只需要开发对应的渲染器即可)
4、从Vue的template到最终展示在浏览器页面的过程是什么?
image.png5、Vue有三大核心系统,分别是什么?对应什么工作?
image.png image.png二、自己实现一个Mini-Vue
1、为什么在Vue2里面,如果给响应式对象新增属性时,需要调用 Vue.$set() 方法呢?
- 【Object.defineProperty】是劫持对象的属性,如果新增元素,Vue2需要再次调用 definedProperty
2、为什么Vue3选择Proxy呢?
image.png3、Proxy的缺点,导致什么情况下要选择Vue2?
-【Proxy】不支持 【> IE9.0 系列】,所以如果要支持 【> IE9.0 系统】,还是要选择Vue2
4、实现Mini-Vue
image.png- 【 index.html】
// 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="./renderer.js"></script>
<script src="./reactive.js"></script>
<script src="./index.js"></script>
<script>
// 1.创建跟组件
const App = {
data: reactive({
counter: 101
}),
render() {
return h("div", { class: "my-div" }, [
h("h2", { class: "title" }, `当前计数:${this.data.counter}`),
h("button", {
onClick: () => {
this.data.counter++;
console.log(this.data.counter);
}
}, "+1")
])
}
}
// 2.挂载跟组件
const app = createApp(App);
app.mount("#app");
</script>
</body>
</html>
- 【index.js】
// index.js
function createApp(rootComponent) {
return {
mount(selector) {
const container = document.querySelector(selector);
let isMounted = false;
let oldVNode = null;
watchEffect(() => {
if (!isMounted) {
oldVNode = rootComponent.render();
mount(oldVNode, container);
isMounted = true;
} else {
let newVNode = rootComponent.render();
patch(oldVNode, newVNode);
oldVNode = newVNode;
console.log("watchEffect监听到VNode改变,需要patch");
}
})
}
}
}
- 【reactive.js】
// reactive.js
class Dep {
constructor() {
this.subscribers = new Set();
}
addEffect(effect) {
this.subscribers.add(effect);
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let currentEffect = null;
function watchEffect(effect) {
currentEffect = effect;
effect();
currentEffect = null;
}
const targetMap = new WeakMap();
function getDep(raw, key) {
let rawMap = targetMap.get(raw);
if (!rawMap) {
rawMap = new Map();
targetMap.set(raw, rawMap);
}
let keyDep = rawMap.get(key);
if (!keyDep) {
keyDep = new Dep();
rawMap.set(key, keyDep);
}
return keyDep;
}
// vue3对raw进行数据Proxy
function reactive(raw) {
return new Proxy(raw, {
get: function (obj, key) {
if (typeof currentEffect === 'function') {
let dep = getDep(raw, key);
dep.addEffect(currentEffect);
}
return obj[key];
},
set: function (obj, key, newValue) {
if (obj[key] !== newValue) {
obj[key] = newValue;
let dep = getDep(raw, key);
dep.notify();
}
}
});
}
const info = reactive({ counter: 100, name: "why" });
const foo = reactive({ height: 1.8 });
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);
});
console.log("==========以下是响应式触发========1=");
// info.counter++;
// info.name = "kobe";
foo.height = 2;
// 如果我们修改info.counter,应该只触发1、2、3
// 如果我们修改info.name,应该只触发1、3
// 如果我们修改foo.height,应该只触发4
- 【renderer.js】
// renderer.js
function h(tag, props, children) {
return {
tag,
props,
children
}
}
function mount(vnode, container) {
// 创建元素
const el = document.createElement(vnode.tag);
vnode.el = el;
// 添加属性
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);
}
}
}
//处理child
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);
})
}
}
}
}
}