computed和watch有什么区别?
computed:
computed是计算属性,也就是计算值,它更多用于计算值的场景computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算computed适用于计算比较消耗性能的计算场景
watch:
- 更多的是「观察」的作用,类似于某些数据的监听回调,用于观察
props$emit或者本组件的值,当数据变化时来执行回调进行后续操作 - 无缓存性,页面重新渲染时值不变化也会执行
小结:
- 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
- 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
写过自定义指令吗 原理是什么
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
代码语言:txt复制1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5. unbind:只调用一次,指令与元素解绑时调用。原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
nextTick 使用场景和原理
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
相关代码如下
代码语言:javascript复制let callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false; //把标志还原为false
// 依次执行回调
for (let i = 0; i < callbacks.length; i ) {
callbacks[i]();
}
}
let timerFunc; //定义异步方法 采用优雅降级
if (typeof Promise !== "undefined") {
// 如果支持promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== "undefined") {
// MutationObserver 主要是监听dom变化 也是一个异步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== "undefined") {
// 如果前面都不支持 判断setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后降级采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb) {
// 除了渲染watcher 还有用户自己手动调用的nextTick 一起被收集到数组
callbacks.push(cb);
if (!pending) {
// 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false
pending = true;
timerFunc();
}
}Vue3有了解过吗?能说说跟vue2的区别吗?
1. 哪些变化

从上图中,我们可以概览Vue3的新特性,如下:
- 速度更快
- 体积减少
- 更易维护
- 更接近原生
- 更易使用
1.1 速度更快
vue3相比vue2
- 重写了虚拟
Dom实现 - 编译模板的优化
- 更高效的组件初始化
undate性能提高1.3~2倍SSR速度提高了2~3倍

1.2 体积更小
通过webpack的tree-shaking功能,可以将无用模块“剪辑”,仅打包需要的
能够tree-shaking,有两大好处:
- 对开发人员,能够对
vue实现更多其他的功能,而不必担忧整体体积过大 - 对使用者,打包出来的包体积变小了
vue可以开发出更多其他的功能,而不必担忧vue打包出来的整体体积过多

1.3 更易维护
compositon Api
- 可与现有的
Options API一起使用 - 灵活的逻辑组合与复用
Vue3模块可以和其他框架搭配使用

更好的Typescript支持
VUE3是基于typescipt编写的,可以享受到自动的类型定义提示

1.4 编译器重写

1.5 更接近原生
可以自定义渲染 API

1.6 更易使用
响应式 Api 暴露出来

轻松识别组件重新渲染原因

2. Vue3新增特性
Vue 3 中需要关注的一些新功能包括:
framentsTeleportcomposition ApicreateRenderer
2.1 framents
在 Vue3.x 中,组件现在支持有多个根节点
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>2.2 Teleport
Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”
在vue2中,像 modals,toast 等这样的元素,如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难
通过Teleport,我们可以在组件的逻辑位置写模板代码,然后在 Vue 应用范围之外渲染它
<button @click="showToast" class="btn">打开 toast</button>
<!-- to 属性就是目标位置 -->
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg">我是一个 Toast 文案</div>
</div>
</teleport>2.3 createRenderer
通过createRenderer,我们能够构建自定义渲染器,我们能够将 vue 的开发模型扩展到其他平台
我们可以将其生成在canvas画布上

关于createRenderer,我们了解下基本使用,就不展开讲述了
import { createRenderer } from '@vue/runtime-core'
const { render, createApp } = createRenderer({
patchProp,
insert,
remove,
createElement,
// ...
})
export { render, createApp }
export * from '@vue/runtime-core'2.4 composition Api
composition Api,也就是组合式api,通过这种形式,我们能够更加容易维护我们的代码,将相同功能的变量进行一个集中式的管理

关于compositon api的使用,这里以下图展开

简单使用:
代码语言:javascript复制export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value
}
onMounted(() => console.log('component mounted!'))
return {
count,
double,
increment
}
}
}3. 非兼容变更
3.1 Global API
- 全局
Vue API已更改为使用应用程序实例 - 全局和内部
API已经被重构为可tree-shakable
3.2 模板指令
- 组件上
v-model用法已更改 <template v-for>和 非v-for节点上key用法已更改- 在同一元素上使用的
v-if和v-for优先级已更改 v-bind="object"现在排序敏感v-for中的ref不再注册ref数组
3.3 组件
- 只能使用普通函数创建功能组件
functional属性在单文件组件(SFC)- 异步组件现在需要
defineAsyncComponent方法来创建
3.4 渲染函数
- 渲染函数
API改变 $scopedSlotsproperty 已删除,所有插槽都通过$slots作为函数暴露- 自定义指令 API 已更改为与组件生命周期一致
- 一些转换
class被重命名了:v-enter->v-enter-fromv-leave->v-leave-from
- 组件
watch选项和实例方法$watch不再支持点分隔字符串路径,请改用计算函数作为参数 - 在
Vue 2.x中,应用根容器的outerHTML将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。VUE3.x现在使用应用程序容器的innerHTML。
3.5 其他小改变
destroyed生命周期选项被重命名为unmountedbeforeDestroy生命周期选项被重命名为beforeUnmount[prop default工厂函数不再有权访问this是上下文- 自定义指令 API 已更改为与组件生命周期一致
data应始终声明为函数- 来自
mixin的data选项现在可简单地合并 attribute强制策略已更改- 一些过渡
class被重命名 - 组建 watch 选项和实例方法
$watch不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。 <template>没有特殊指令的标记 (v-if/else-if/else、v-for或v-slot) 现在被视为普通元素,并将生成原生的<template>元素,而不是渲染其内部内容。- 在
Vue 2.x中,应用根容器的outerHTML将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x现在使用应用容器的innerHTML,这意味着容器本身不再被视为模板的一部分。
3.6 移除 API
keyCode支持作为v-on的修饰符$on,$off和$once实例方法- 过滤
filter - 内联模板
attribute $destroy实例方法。用户不应再手动管理单个Vue组件的生命周期。
参考:前端vue面试题详细解答
diff算法
<details open=""><summary><b>答案</b></summary>
<p>
</p><p><strong>时间复杂度:</strong> 个树的完全<code> diff</code> 算法是一个时间复杂度为<code> O(n*3)</code> ,vue进行优化转化成<code> O(n)</code> 。</p>
<p><strong>理解:</strong></p>
<ul>
<li>
<p>最小量更新,<code> key</code> 很重要。这个可以是这个节点的唯一标识,告诉<code> diff</code> 算法,在更改前后它们是同一个DOM节点</p>
<ul>
<li>扩展<code> v-for</code> 为什么要有<code> key</code> ,没有<code> key</code> 会暴力复用,举例子的话随便说一个比如移动节点或者增加节点(修改DOM),加<code> key</code> 只会移动减少操作DOM。</li>
</ul>
</li>
<li>
<p>只有是同一个虚拟节点才会进行精细化比较,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p>只进行同层比较,不会进行跨层比较。</p>
</li>
</ul>
<p><strong>diff算法的优化策略</strong>:四种命中查找,四个指针</p>
<ol>
<li>
<p>旧前与新前(先比开头,后插入和删除节点的这种情况)</p>
</li>
<li>
<p>旧后与新后(比结尾,前插入或删除的情况)</p>
</li>
<li>
<p>旧前与新后(头与尾比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧后之后)</p>
</li>
<li>
<p>旧后与新前(尾与头比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
--- 问完上面这些如果都能很清楚的话,基本O了 ---
以下的这些简单的概念,你肯定也是没有问题的啦


