1.什么是声明式和命令式?更新局部数据innerHTML和虚拟DOM 以及原生DOM操作之间的性能对比?
=》声明式就是:给参数 然后不用管过程,直接得到结果 命令式:从一步一步从开始到结果, 声明式式命令式的封装
命令式:效率高 声明式:维护方便 简单
=》innerHTML 跟新数据,是必须跟新全部数据,计算量包括,DOM操作
innerHTML原理
(1)创建一个docuoment (2)解析字符串,提取标签 (3)将标签属性按层次生成
(4)删除所有子节点,(5)将创建的dom添加到目标dom上
=》虚拟DOM是,用对象 变量响应的节点和属性,()用原生的DOM方法创建DOM
当更改是,虚拟DOM只修改改变的DOM,不用全部重新创建,innerHTML要全部重来
2.什么是运行时和编译时?纯运行时的例子是?
=》运行时是指,代码直接可以运行,编译时,写一个无法直接执行的代码,编译后可以直接执行,书中的例子是:
1.运行时Render(obj,dom):将虚拟dom(对象)树,渲染到为HTML代码
2.编译:compiler(htmlstr) :将html(类html)代码编译成 树形数据对象(虚拟dom)
由HTML模板-compiter(html)->得到Vnode->Render( obj,root)->HTML
纯编译就是直接将HTML compiler() 为命令代码,document.xx…(性能高,无法预测优化)
纯运行时就是将只有render函数,直接写Vnode,操作对象,来改变HTML(不灵活)
1._DEV_是什么?
简单来说是webpack定义的常量,用于区分生产和发布环境,在vue3源码中也有,当时生产环境的时候,不会打印错误
框架提供的错误提示,在打包的发布版本会自动剔除
(静态分析)
2.tree-shaking是什么?
就是没有引入的代码,和不会执行的代码,不打包到最终结果,用/PURE/来标记,函数内部调用,只要外部函数没有调用过,就可以删除这部分代码,
3.直接是src引入的文件是?webpack编译的文件是?
直接导入有两种:
有两种,低级浏览器IIEF格式的.vue.global.js (压缩版,没有错误提示,tree-shaking不起作用)
高级浏览器支持ESM规范,vue.esm.brower.js (压缩版,没有错误提示,tree-shaking不起作用)
给webpack使用的的vue.esm.bundler.js(去除一切没有用到的代码,tree-shaking起作用)
vue3.如何实现错误处理的?
同一函数处理,fn是执行肯能出错的函数,args是一个参数数组
作者主要是想表达,vue框架实现了错误统一处理,屏蔽某些底层错误,让用户更好的找到错误代码的位置
Vue3源码如下:
function callWithErrorHandling(fn, instance, type, args) {
let res;
try {
// 执行函数并且监控是否参数
res = args ? fn(...args) : fn();
}
catch (err) {
handleError(err, instance, type);
}
return res;
}
handleError
function handleError(err, instance, type, throwInDev = true) {
const contextVNode = instance ? instance.vnode : null;
if (instance) {
let cur = instance.parent;
// the exposed instance is the render proxy to keep it consistent with 2.x
const exposedInstance = instance.proxy;
// in production the hook receives only the error code
const errorInfo = ErrorTypeStrings[type] ;
while (cur) {
const errorCapturedHooks = cur.ec;
if (errorCapturedHooks) {
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) {
return;
}
}
}
cur = cur.parent;
}
// app-level handling
const appErrorHandler = instance.appContext.config.errorHandler;
if (appErrorHandler) {
callWithErrorHandling(appErrorHandler, null, 10 /* APP_ERROR_HANDLER */, [err, exposedInstance, errorInfo]);
return;
}
}
logError(err, type, contextVNode, throwInDev);
}
1.模板声明描述UI和JavaScript对象描述UI的不同之处?
描述ui主要是几个问题:
1.DOM元素:例如div标签
2.标签上的属性:a属性
3.标签上的事件例如:onclick事件
4.标签的层次关系,例如父子元素
Vue的声明描述是
JS对象描述是
const title={
tag:”h1”,
props:{ onClick:handler},
children:[ { tag:”span”} ]
}
JavaScript对象描述更为灵活,而模板声明描述UI更为直观,VUE两者都保留了
虚拟DOM(Vndoe)就是用JS对象描述UI的方式
2.render的h函数是什么作用?
h函数是将声明描述的ui模板文件变为js对象描述的Vnode,
3.实现简单的渲染器(renderer)?
渲染器就是将Vnode对象变成可执行的DOM语句,创建DOM并添加到HTML上
/*
渲染器的作用是将Vnode 变成 HTML对象,并添加到container节点上
*/
function renderer(Vnode, container) {
// 创建顶级的节点
let el = document.createElement(Vnode.tag);
// 添加属性
for (key in Vnode.props) {
// 如果是on开头的是事件
if (/^on/.test(key)) {
// 事件将事件添加 onClick=>click
el.addEventListener(key.slice(2).toLowerCase(), Vnode.props[key]);
}
el;
{
// 普通属性将属性添加到元素上
el.setAttribute(key, Vnode.props[key]);
}
}
if (Array.isArray(Vnode.childrens)) {
// 如果是数组就使用递归遍历下一层
Vnode.childrens.forEach((item) => {
renderer(item, el);
});
} else if (typeof Vnode.childrens === "string") {
el.appendChild(document.createTextNode(Vnode.childrens));
} else {
new Error("子节点格式错误");
}
// 将此节点挂载跟节点上
container.appendChild(el);
}
// Vnode的例子
const demo = {
tag: "div",
props: { onClick: () => alert("hello") },
childrens: [
{
tag: "h1",
props: { title: "h1" },
childrens: "Renderer渲染器",
},
{ tag: "p", props: { class: "red" }, childrens: "这是p标签" },
],
};
// 测试
renderer(demo, document.body);
4.组件的本质是什么?如何用Vnode来描述组件? 如何用renderer来渲染组件?
上面已经实现了用Vnode来描述普通的DOM元素,但是如何描述组件?组件毕竟不是真实的DOM元素。
组件的本质就是一组DOM元素的封装
(从作者这句话中,我似乎知道了vue2中为啥组件只有一个容器元素,其实就是一个Vnode的根元素)
renderer函数的实现:
/*
注意组件也是一系列的DOM组成的,可以将组件设为一个函数,或一个对象,最终返回一个组件根元素的虚拟DOM*/
**// renderer 渲染组件**
function renderer(Vnode, container) {
if (typeof Vnode.tag === "string") {
// 渲染普通元素
mountElement(Vnode, container);
} else if (typeof Vnode.tag === "object") {
// 渲染组件
mountComponent(Vnode, container);
}
}
**// 渲染普通元素,之前写过**
function mountElement(Vnode, container) {
// 创建顶级的节点
let el = document.createElement(Vnode.tag);
// 添加属性
if (Vnode.props) {
// Vnode.props判断要存在
for (key in Vnode.props) {
// 如果是on开头的是事件
if (/^on/.test(key)) {
// 事件将事件添加 onClick=>click
el.addEventListener(key.slice(2).toLowerCase(), Vnode.props[key]);
}
el;
{
// 普通属性将属性添加到元素上
el.setAttribute(key, Vnode.props[key]);
}
}
}
if (Array.isArray(Vnode.childrens)) {
// 如果是数组就使用递归遍历下一层
Vnode.childrens.forEach((item) => {
renderer(item, el);
});
} else if (typeof Vnode.childrens === "string") {
el.appendChild(document.createTextNode(Vnode.childrens));
} else {
return new Error("子节点错误");
}
// 将此节点挂载跟节点上
container.appendChild(el);
}
**// 渲染组件函数,其实就是获得容器节后,和渲染普通节点一样**
function mountComponent(Vnode, container) {
// 获得组件的容器元素的Vnode
const subtree = Vnode.tag.render();
mountElement(subtree, container);
}
上面两个函数实现了,渲染普通DOM元素和组件
下面是测试例子:
模板描述UI的形式
<div>
<div @click="alter('hello')">
<h1 title="h1">Renderer渲染器h1>
<p class="red">这是p标签p>
div>
<myComponent>myComponent>
div>
<template>
<div title="myComponent组件">myComponent的第一个容器元素div>
template>
js对象描述UI的形式
**//组件的例子 组件是一个对象,当然组件也可以是一个函数**
const myComponent = {
render() {
return {
tag: "div", //组件的第一个容器元素
props: { title: "myComponent组件" },
childrens: "myComponent的第一个容器元素",
};
},
};
**// 用Vnode描述组件例子**
const Vnode = {
tag: "div",
props: {},
childrens: [
//第一个div
{
tag: "div",
props: { onClick: () => alert("hello") },
childrens: [
{
tag: "h1",
props: { title: "h1" },
childrens: "Renderer渲染器",
},
{ tag: "p", props: { class: "red" }, childrens: "这是p标签" },
],
},
// 组件
{
tag: myComponent,
// renderer中没有处理组件的属性
// props: {
// color: "red",
// },
},
],
};
最后执行渲染函数,将Vndoe 变成真实的DOM渲染到页面中
renderer(Vnode, document.body);