跳到主要内容

面试题合集

1.vue2 和 vue3 双向绑定的原理是什么?​

  1. 响应式原理不同,Object.defineProperty 和 new Proxy()
  2. $set 在 vue3 为 undefined
  3. vue2 为 options api;vue3 是 composion api 和 setup 语法糖
  4. v-if 和 v-for 的优先级不同
    1. 在 Vue 2 中,当它们同时出现在同一个元素上时,v-for的优先级高于v-if。
    2. 在 Vue 3 中,这种行为有所改变。Vue 3 中,v-if的优先级高于v-for。这种改变可以提高性能,避免了不必要的循环渲染。
  5. ref 和 $children 不同
    1. 在 Vue 2 中,ref通常用于获取 DOM 元素的引用或者创建响应式数据
    2. 在 Vue 3 中,ref也可以用于创建响应式数据,但它的功能更加强大,可以用于包裹普通 JavaScript 值、DOM 元素、甚至是组件实例。使用ref创建的数据,可以通过.value来访问或修改。

Vue 2 使用了基于Object.defineProperty的双向绑定原理:

<!DOCTYPE html>
<html>
<head>
<title>基于 Object.defineProperty 的双向绑定示例</title>
</head>
<body>
<div id="app">
<input
id="inputField"
type="text"
/>
<p id="outputText"></p>
</div>

<script>
const data = {
message: ''
};
const inputField = document.getElementById('inputField');
const outputText = document.getElementById('outputText');
Object.defineProperty(data, 'message', {
get() {
return this._message;
},
set(value) {
this._message = value;
inputField.value = value; // 更新输入框的值
outputText.textContent = value; // 更新文本显示
}
});
inputField.addEventListener('input', function () {
data.message = inputField.value;
});
</script>
</body>
</html>

Vue 3 引入了基于Proxy的双向绑定原理:

<!DOCTYPE html>
<html>
<head>
<title>基于 Proxy 的双向绑定示例</title>
</head>
<body>
<div id="app">
<input
id="inputField"
type="text"
/>
<p id="outputText"></p>
</div>

<script>
const data = {
message: ''
};
const inputField = document.getElementById('inputField');
const outputText = document.getElementById('outputText');
const dataProxy = new Proxy(data, {
get(target, property) {
return target[property];
},
set(target, property, value) {
target[property] = value;
inputField.value = value; // 更新输入框的值
outputText.textContent = value; // 更新文本显示
return true;
}
});
inputField.addEventListener('input', function () {
dataProxy.message = inputField.value;
});
</script>
</body>
</html>

2.vue 的 render 函数是什么? render 函数和 template 模板有什么关系?​

  • Vue 中的 render 函数是用于创建虚拟 DOM(Virtual DOM)的 JavaScript 函数。与 template 模板不同,render 函数是更底层、更灵活的方式来定义 Vue 组件的渲染逻辑。

总之,template 是一种声明性的模板语法,适合大多数情况,而 render 函数提供了更高级和灵活的方式来定义组件的渲染逻辑,可以用于处理一些复杂的场景。在大多数情况下,你可以使用 template 来构建组件,但如果需要更多控制权,可以使用 render 函数。

3.vue 如何解析指令? 模板变量? html 标签​

Vue 解析指令、模板变量和 HTML 标签的过程是在模板编译阶段完成的,该阶段将模板字符串转换为可执行的渲染函数。

  • Vue 使用正则表达式和解析器来识别和解析指令
  • 解析模板变量:Vue 在编译过程中会识别这些插值表达式,并将它们替换为生成虚拟 DOM 的代码。
    • 例如,对于 {{ message }},Vue 会生成一个虚拟 DOM 节点,该节点的值将由 message 变量的当前值填充。
  • Vue 在编译模板时也会解析 HTML 标签,包括普通的 HTML 元素标签和自定义组件标签。当 Vue 遇到自定义组件标签时,它会查找并解析对应的组件定义,并将其转换为渲染函数。

4.vue 是怎么解析 template 的? template 会变成什么?​

  • 解析模板:Vue 会解析传入的 template 字符串,其中包含了 HTML 标记、指令(如 v-if、v-for 等)、插值表达式(如{{ message }})等。
  • 生成抽象语法树(AST):Vue 会将解析后的模板转换成一个抽象语法树(AST),这个树结构表示了模板中的各种元素、指令和数据绑定。
  • 生成渲染函数:基于 AST,Vue 会生成一个渲染函数,这个函数会接收应用的状态(即 data 属性)作为参数,并返回一个虚拟 DOM。
  • 渲染虚拟 DOM:当应用的状态发生变化时,Vue 会调用渲染函数,生成新的虚拟 DOM。然后,Vue 会使用新的虚拟 DOM 与之前的虚拟 DOM 进行比较,找出变化的部分。
  • 更新实际的 DOM:最后,Vue 会根据虚拟 DOM 的变化,批量更新实际的 DOM,以反映应用状态的变化。

vue 源码学习总结 深入解析 template 编译成 render 函数过程

5.vue 的 keep-alive 的作用是什么?怎么实现的?如何刷新的?​

主要作用:

  1. 缓存组件实例
  2. 避免重复渲染
  3. 保存组件状态
  4. 条件缓存

实现原理:

  1. 缓存组件实例:当 <keep-alive> 包裹的组件首次渲染时,Vue.js 会将该组件的实例保存在内部的缓存列表中。
  2. 组件切换:当需要重新渲染包裹在 <keep-alive> 中的组件时(例如,通过路由切换或条件显示/隐藏),Vue.js 会首先检查缓存中是否已经有了这个组件的实例。
  3. 复用缓存:如果缓存中存在该组件的实例,Vue.js 会选择性地将缓存中的实例重新挂载到 DOM 上,而不是重新创建一个新的实例。这样可以避免不必要的渲染和数据初始化过程。
  4. 生命周期钩子:当组件从缓存中取出并重新挂载时,Vue.js 会调用组件的一系列生命周期钩子,如activated和deactivated,以便让开发者可以在组件复用和销毁时执行特定的逻辑。
  5. 缓存策略:Vue.js 还提供了一些缓存策略选项,允许你控制缓存的行为,例如设置缓存的最大数量,或定义哪些组件需要被缓存。

总之,<keep-alive> 的实现原理是基于 Vue.js 的组件生命周期和虚拟 DOM 渲染流程,通过缓存和复用组件实例,以提高性能和用户体验。

刷新缓存:

  1. 使用 include 和 exclude 属性。
  2. 调用 $forceUpdate() 方法。
  3. 动态改变组件的 key 属性。

6.Vue2.0 v-for 中 :key 到底有什么用?​

需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点。所以一句话,key 的作用主要是为了高效的更新虚拟 DOM。

7.v-for 和 v-if 使用问题​

v-for 的优先级比 v-if 的优先级高,在遍历每一条 item 后,会执行 v-if,会造成不必要的计算,影响性能。

解决方法:通过使用 computed 属性,来筛选出符合条件项,然后再使用 v-for 遍历

8.实例属性 data 为什么是一个 function​

组件是一个可复用的实例,当你引用一个组件的时候,组件里的 data 是一个普通的对象,所有用到这个组件的都引用的同一个 data,就会造成数据污染。

将 data 封装成函数后,在实例化组件的时候,我们只是调用了 data 函数生成的数据副本,避免了数据污染。

9.hash 和 history 模式的区别​

hash 模式:在浏览器 url 中提现为#,#以及之后的内容用window.location.hash读取。特点:hash 虽然在 URL 中,但不被包括在 HTTP 请求中;用来指导浏览器动作,对服务端安全无用,hash 不会重加载页面。

hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

history 模式:history 采用 HTML5 的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及 popState 事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。

10.computed 和 watch​

Computed Properties(计算属性)​

计算属性是基于它们所依赖的数据进行动态计算的属性。当其所依赖的数据发生变化时,计算属性会重新计算其值。Vue 会缓存计算属性的值,只有在它所依赖的响应式数据发生变化时,才会重新求值。

计算属性的原理是基于 Vue 的响应式系统。当计算属性依赖的响应式数据发生变化时,Vue 会检测到这种变化,并重新计算计算属性的值。这种机制保证了计算属性的响应性和高效性。

计算属性的实现​

在 Vue.js 2 内部,当你定义一个计算属性时,Vue 会将其转换成一个称为 Watcher 的对象,并将其添加到组件实例的 vm 对象中。当依赖的响应式数据发生变化时,该 Watcher 对象会收到通知并重新计算其值。

Vue 的响应式系统通过 Object.defineProperty 或者 Proxy 来劫持对响应式数据的访问和修改,当数据被访问时,会进行依赖收集,当数据变化时,会通知所有依赖该数据的 Watcher 对象进行更新。

<!-- ParentComponent.vue -->
<template>
<div>
<p>Computed Property: {{ sum }}</p>
<input
type="number"
v-model="a"
/>
+
<input
type="number"
v-model="b"
/>
=
<input
type="number"
v-model="sum"
/>
</div>
</template>

<script>
export default {
data() {
return {
a: 1,
b: 2
};
},
computed: {
sum: {
get: function () {
console.log('get');
return Number(this.a) + Number(this.b);
},
set: function (newValue) {
// 在这里进行计算属性变化后的处理
console.log('sum 更新为:', newValue);
}
}
},
methods: {
addA() {
this.a++;
},
updateSum() {
this.addA();
this.sum = this.a + 10;
}
}
};
</script>

在上面的示例中,sum 计算属性定义了 set 函数,当尝试给 sum 赋值时(虽然实际上是不被允许的),set 函数会被调用,并打印出新的值。但是请注意,computed 属性并不是通过直接赋值来修改的,而是由其依赖的响应式数据的变化间接地影响其值的。

Watchers(观察者)​

Watcher 是一个能够监听到 Vue 实例数据变化的机制。可以利用它来执行自定义的逻辑。

Watcher 的原理是在数据发生变化时执行指定的回调函数。这个回调函数可以执行任何逻辑,比如发送 HTTP 请求、触发动画效果等。

Watcher 的实现​

当你使用 watch 选项时,Vue 会创建一个 Watcher 对象来监视数据的变化,并在数据变化时执行相应的回调函数。这个 Watcher 对象与计算属性的 Watcher 对象相似,但是它执行的逻辑不同,主要是执行你定义的回调函数。

在内部实现上,Vue 会为每个 watch 选项创建一个独立的 Watcher 对象,并在数据变化时调用对应的回调函数。

总的来说,computed 和 watch 的实现都依赖于 Vue 的响应式系统和 Watcher 对象,通过对响应式数据的监听和计算来实现相应的功能。

12.在 vue 中监听页面滚动​

<template>
<div class="main-container">
<p
v-for="(t, i) in page"
:key="i"
>
{{ t }}
</p>
</div>
</template>

<script>
export default {
name: 'Test',
data() {
return {
page: new Array(20).fill().map(i => Math.random().toString(36).substring(2))
};
},
mounted() {
document.addEventListener('scroll', this.scroll);
},
beforeDestroy() {
document.removeEventListener('scroll', this.scroll);
},
methods: {
scroll() {
const scrollHeight = document.body.scrollHeight; // 页面高度
const screenHeight = window.screen.height; // 屏幕高度
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 页面滚动卷去高度
console.log('页面高度:', scrollHeight, '\r\n屏幕高度:', screenHeight, '\r\n页面滚动卷去高度:', scrollTop);
}
}
};
</script>

<style lang="less" scoped>
p {
display: block;
background-color: #e9a4a4;
min-width: 200px;
height: 44px;
line-height: 44px;
margin: 20px auto 0;
border: 1px solid #a5c5b5;
border-radius: 2px;
}
</style>

13. this.$nextTick​

Vue.js 利用了浏览器的事件循环机制,确保 this.$nextTick 中的回调函数在当前 DOM 更新周期之后执行。这样做的好处是,你可以在更新视图之后立即访问 DOM,确保获取到最新的 DOM 元素状态。

在具体实现上,Vue.js 使用了微任务(microtask)或宏任务(macrotask)来实现 this.$nextTick。

14. 在 vue2 中,直接改变数组中某项的值为什么不会在页面更新?​

在 Vue 2 中,直接改变数组中某项的值不会触发页面更新的原因是因为 Vue 的响应式系统无法检测到这种修改。

Vue 2 的响应式系统是基于 ES5 的Object.defineProperty实现的。当 Vue 将一个对象转换成响应式对象时,它会遍历对象的所有属性,并使用Object.defineProperty对每个属性进行拦截,当属性被访问或修改时,Vue 能够捕获到并通知相关的依赖进行更新。但是,这种拦截只能对对象的属性进行,而不能对数组的索引进行拦截。

当直接修改数组中的某项值时,例如 arr[0] = newValue,这种修改操作并不会触发Object.defineProperty的拦截器,因此 Vue 无法检测到数组元素的变化,也就无法触发页面的更新。

为了解决这个问题,Vue 提供了一些特殊的数组方法,如push、pop、shift、unshift、splice、sort、reverse等,这些方法在内部已经进行了重写,并且能够通知 Vue 进行页面的更新。如果想要修改数组中的某项值并触发页面更新,可以使用Vue.set或者数组的修改方法来实现。

下面是 Vue 2 中对数组的部分源码:

javascriptCopy codeconst arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
// 保存原始方法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args);
const ob = this.__ob__; // 获取到数组对应的 Observer 对象
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted); // 对新增的元素进行响应式处理
ob.dep.notify(); // 通知依赖更新
return result;
});
});

在这段代码中,Vue 首先创建了一个原型为Array.prototype的对象arrayMethods,然后遍历了数组的常用变更方法(如push、pop、shift等),对每一个方法进行了重写。在重写的方法中,Vue 进行了两个操作:

  1. 执行原始方法:首先调用原始方法执行数组的变更操作,例如push方法会向数组末尾添加元素。
  2. 手动通知:在数组变更操作执行完成后,手动通知数组对应的 Observer 对象进行依赖的更新。

通过这种方式,Vue 能够实现对数组中某个索引位置的值的改变的监听,并在数组发生变化时,及时通知相关的依赖进行更新。