2、作用域
作用域(Scope)是指在程序中定义变量时,这个变量可以被访问的范围。它规定了变量在何处和何时可见或可访问。
在JavaScript中,有三种主要的作用域类型:
- 全局作用域(Global Scope):全局作用域是在整个程序中都可见的最外层作用域。在全局作用域中声明的变量可以被程序中的任何部分访问,包括函数内部。
- 函数作用域(Function Scope):函数作用域是在函数内部声明变量所创建的作用域。在函数内部声明的变量只能在函数内部访问,外部无法访问。
- 块级作用域(Block Scope):块级作用域是在代码块(如循环、条件语句或花括号内部)中声明变量所创建的作用域。该变量只能在该代码块内部访问,并且在代码块外部不可见。
在ES6之前,JavaScript只有全局作用域和函数作用域。使用关键字var
声明的变量具有函数作用域,而使用let
或const
声明的变量则具有块级作用域。在ES6中引入的let
和const
增加了块级作用域的支持,使得变量的作用域更加灵活和可控。
作用域的目的是控制变量的可见性和生命周期,避免命名冲突和数据泄露。理解作用域有助于编写可维护、易读且低风险的JavaScript代码。
3、箭头函数和普通函数有什么区别
箭头函数(Arrow Function)和普通函数(Regular Function)是JavaScript中用于定义函数的两种不同语法形式,它们之间有以下区别:
- 语法:
- 普通函数:使用
function
关键字声明函数,并且可以使用函数名来调用。 - 箭头函数:使用箭头(
=>
)来声明函数,没有函数名,箭头函数常常是匿名函数,需要通过变量或常量来引用。
this
的指向:
- 普通函数:每个普通函数都有自己的
this
值,指向调用该函数的对象。可以通过call()
、apply()
或bind()
方法改变this
的指向。 - 箭头函数:箭头函数没有自己的
this
绑定,它会继承外部作用域的this
值。换句话说,箭头函数的this
指向的是定义时所在的上下文。
- 构造函数:
- 普通函数:普通函数可以用作构造函数,使用
new
关键字创建实例对象,并且具有自己的原型和构造函数属性。 - 箭头函数:箭头函数不能用作构造函数,不能使用
new
关键字创建实例对象,因为它没有自己的this
绑定和原型。
- 函数体内的
arguments
对象:
- 普通函数:普通函数内部有一个特殊的
arguments
对象,它包含了函数调用时传入的所有参数。 - 箭头函数:箭头函数没有自己的
arguments
对象,访问箭头函数内部的参数可以使用...args
语法从外部作用域传递进来。
- 函数体内的
super
关键字:
- 普通函数:普通函数内部可以使用
super
关键字来调用父类的方法。 - 箭头函数:箭头函数没有自己的
super
绑定,无法直接使用super
关键字,但可以继承外部作用域中的super
。
总的来说,箭头函数和普通函数在语法、this
的指向、构造函数和函数体内的arguments
对象、super
关键字等方面有所区别。选择使用哪种函数形式取决于具体的使用场景和需求。普通函数适合作为构造函数、需要动态绑定this
或使用arguments
对象的情况;而箭头函数则适合需要继承外部作用域的this
、简洁语法以及避免创建新的函数作用域的情况。
4、说说你对Promise的理解
Promise是JavaScript中处理异步操作的一种机制,它可以更优雅地解决回调地狱(callback hell)问题,使异步代码变得更易读和可维护。
Promise有三个状态:
- 进行中(Pending):初始状态,表示异步操作正在进行中,尚未完成。
- 已完成(Fulfilled):表示异步操作已成功完成,并返回了结果值。
- 已拒绝(Rejected):表示异步操作失败或被拒绝,并返回一个错误信息。
使用Promise的主要思想是将异步操作封装在一个Promise对象中,通过链式调用来处理异步操作的结果。Promise提供了then()
和catch()
方法来注册回调函数处理成功和失败的情况。
主要特点包括:
- 链式调用:通过返回新的Promise对象,可以在不同的操作之间进行链式调用,避免了嵌套的回调函数。
- 错误处理:通过
catch()
方法或在then()
中的第二个参数传入的回调函数,可以捕获和处理异步操作中的错误。 - 异步顺序控制:使用Promise可以方便地控制多个异步操作的执行顺序,例如通过
Promise.all()
等方法实现并行或串行执行。 - 只能有一个最终结果:Promise的状态一旦改变,就不能再次改变,只能有一个最终的结果。
- 异常穿透:在Promise链中,如果某个Promise发生错误但没有被捕获处理,错误会沿着链向后传递,直到被捕获为止。
总的来说,Promise提供了一种更优雅、可读性更高的方式来处理异步操作,帮助开发者更好地组织和控制异步代码,并且能够更好地处理错误和异常情况。
5、find和filter的区别
find()
和filter()
都是JavaScript数组的方法,用于对数组进行筛选操作,但它们在使用和功能上有所不同:
find()
方法:
find()
方法用于查找满足条件的第一个元素,并返回该元素的值。- 使用回调函数作为参数,回调函数接受三个参数:当前元素、当前索引和数组本身。
- 当找到满足条件的元素时,
find()
方法会立即停止遍历,返回该元素的值。 - 如果没有找到满足条件的元素,则返回
undefined
。
filter()
方法:
filter()
方法用于筛选出所有满足条件的元素,并返回一个新的数组,包含满足条件的元素。- 同样使用回调函数作为参数,回调函数接受三个参数:当前元素、当前索引和数组本身。
- 对数组中的每个元素都会调用一次回调函数,如果回调函数返回
true
,则将该元素添加到新数组中。 - 如果没有找到满足条件的元素,则返回一个空数组。
总结:
find()
方法用于查找满足条件的第一个元素,返回该元素的值或undefined
。filter()
方法用于筛选出所有满足条件的元素,返回一个新的数组。
因此,如果只需要找到满足条件的第一个元素,可以使用find()
方法;如果需要筛选出所有满足条件的元素,可以使用filter()
方法。
6、some和every的区别
some()
和every()
都是JavaScript数组的方法,用于对数组元素进行条件判断,但它们在使用和功能上有所不同:
some()
方法:
some()
方法用于检查数组中是否至少存在一个元素满足指定条件,如果存在则返回true
,否则返回false
。- 使用回调函数作为参数,回调函数接受三个参数:当前元素、当前索引和数组本身。
- 对数组中的每个元素都会调用一次回调函数,只要有一个元素使得回调函数返回
true
,则some()
方法立即返回true
,不再继续遍历剩余元素。 - 如果数组为空,则
some()
方法始终返回false
。
every()
方法:
every()
方法用于检查数组中的所有元素是否都满足指定条件,如果所有元素都满足,则返回true
,否则返回false
。- 同样使用回调函数作为参数,回调函数接受三个参数:当前元素、当前索引和数组本身。
- 对数组中的每个元素都会调用一次回调函数,只要有一个元素使得回调函数返回
false
,则every()
方法立即返回false
,不再继续遍历剩余元素。 - 如果数组为空,则
every()
方法始终返回true
。
总结:
some()
方法用于检查数组中是否至少存在一个元素满足条件,只要有一个元素满足,则返回true
。every()
方法用于检查数组中的所有元素是否都满足条件,只有所有元素都满足才返回true
。
因此,如果需要判断数组中是否存在满足条件的元素,可以使用some()
方法;如果需要判断数组中的所有元素是否都满足条件,可以使用every()
方法。
7、Promise和async/await有什么区别?
Promise和async/await都是用来处理异步操作的方式,但是它们之间有一些区别。
- 编码方式:使用Promise时需要手动地编写回调函数,而使用async/await则更像是同步代码的写法,更容易理解和维护。
- 可读性:async/await代码相对于Promise的代码更加容易阅读,因为async/await在语法上可以更清楚地表达出异步调用的顺序和逻辑。
- 错误处理:使用Promise时需要编写额外的错误处理代码,而使用async/await则可以使用try-catch语法结构直接进行错误处理。
- Promise.all:Promise.all可以同时处理多个Promise对象,但是async/await需要使用for…of循环来达到同样的效果。
- 适用场景:Promise适用于比较简单的异步操作,而async/await适用于复杂的异步操作,并且在处理数据流时有更好的表现。
六、Vue面试题
1、Vue生命周期
Vue.js 是一种流行的 JavaScript 框架,它有一套完整的生命周期钩子函数,用于在组件的不同阶段执行特定的操作。以下是 Vue 组件生命周期的阶段及其对应的钩子函数:
- 创建阶段(Creation):
beforeCreate
:在实例初始化之后、数据观测 (data observation) 和事件配置 (event/watcher setup) 之前被调用。created
:在实例创建完成之后被立即调用。此时实例已完成数据观测、属性和方法的运算,但尚未挂载到 DOM 上。
- 挂载阶段(Mounting):
beforeMount
:在挂载开始之前被调用,相关的 render 函数首次被调用。mounted
:在实例挂载到 DOM 后调用,此时组件已经在 DOM 中可用。
- 更新阶段(Updating):
beforeUpdate
:在响应式数据更新时被调用,但是 DOM 尚未重新渲染。updated
:在数据变化导致的虚拟 DOM 重新渲染和打补丁到 DOM 后被调用。
- 销毁阶段(Destruction):
beforeDestroy
:在实例销毁之前调用。此时实例仍然完全可用。destroyed
:在实例销毁之后调用。此时所有的事件监听器都已被移除,所有的子实例也都被销毁。
- 错误捕获阶段(Error Capturing):
errorCaptured
:当子孙组件发生错误时被调用。
这些生命周期钩子函数提供了在不同阶段执行代码的机会,例如在组件创建时初始化数据、在组件挂载后发送异步请求、在组件更新前后执行某些逻辑等。通过使用这些钩子函数,可以更好地控制和管理组件的生命周期行为。
2、谈谈你对keep-alive的了解
keep-alive
是 Vue.js 提供的一个抽象组件,用于在组件之间缓存和复用已经渲染的组件实例。它可以显著提高组件的性能和用户体验。
当将一个组件包裹在 keep-alive
组件中时,该组件的状态将会被保留,而不会因为组件的销毁而丢失。这意味着,当组件在 keep-alive
内部切换时,它们不会重复创建和销毁,而是会被缓存起来并重用。这种缓存机制使得在后续渲染时,组件的状态、数据和 DOM 结构都可以得到保留,从而避免了重新渲染和重新初始化的开销。
使用 keep-alive
可以带来以下好处:
- 缓存组件:通过缓存组件实例,避免了重复的创建和销毁过程,提高了组件的渲染效率和性能。
- 保留组件状态:组件在被缓存时,它们的状态、数据和 DOM 结构都会被保留,使得在再次渲染时能够恢复到之前的状态,提升用户体验。
- 提供生命周期钩子:
keep-alive
组件为缓存的组件提供了额外的生命周期钩子函数(activated
和deactivated
),可以在组件切换时执行特定的逻辑。 - 控制缓存策略:
keep-alive
组件还可以通过配置属性include
和exclude
来控制哪些组件应该被缓存,以及哪些组件应该被排除在外。
需要注意的是,keep-alive
组件并不是适用于所有场景。它主要适用于那些在切换或频繁使用时,可以重复利用已经渲染过的组件实例的情况下。在一些动态变化较大或包含有副作用的组件中,可能需要谨慎使用 keep-alive
,并且需要合理配置 include
和 exclude
属性。
总之,keep-alive
是 Vue.js 提供的一个非常有用的组件,通过缓存和复用已经渲染的组件实例,可以提升应用的性能和用户体验。
3、v-if和v-show区别
v-if
和 v-show
都是 Vue.js 框架中用于条件渲染的指令,但它们在实现方式和使用场景上有一些区别。
- 实现方式:
v-if
:根据条件动态地添加或移除 DOM 元素。如果条件为假,则元素从 DOM 中移除,重新满足条件时再添加到 DOM 中。v-show
:通过修改元素的 CSS 样式来显示或隐藏元素。如果条件为假,则将元素的display
样式设置为none
,条件为真时则恢复原始样式。
- 初始渲染开销:
v-if
:在条件切换时,如果条件为真,会进行组件的销毁和重建,开销较高。当条件初始值为假时,在初始渲染时不会被渲染到 DOM 中。v-show
:在条件切换时,只是简单地切换元素的样式(显示或隐藏),开销较低。在初始渲染时,元素会被渲染到 DOM 中,然后根据条件进行显示或隐藏。
- 频繁切换:
v-if
:适合在需要频繁切换条件的情况下使用,因为每次切换时都会进行创建和销毁操作,可以减少内存占用。v-show
:适合在需要频繁切换条件的情况下使用,因为只需要修改样式,不会涉及到创建和销毁操作,但可能会增加 DOM 元素占用的内存。
- 组件初始化时间:
v-if
:在条件为真时才进行组件的初始化和渲染,可以节约初始加载时间。v-show
:无论条件是否为真,组件都会在初始加载时被初始化和渲染。
综上所述,v-if
和 v-show
在使用上有一些区别。如果需要在频繁切换条件、内存占用较高的情况下使用,则推荐使用 v-show
。如果需要在初始渲染时节省加载时间或在条件切换时进行组件的完全销毁和重建,则推荐使用 v-if
。根据具体的需求和场景,选择适合的指令来进行条件渲染。
4、v-if和v-for优先级
在 Vue.js 中,v-if
和 v-for
指令的优先级是不同的。
v-for
指令有更高的优先级,它会在 v-if
之前被解析和执行。
这意味着,在包含 v-for
和 v-if
的元素上,v-for
先被解析,然后根据 v-for
的结果进行循环渲染,而 v-if
则会在每次循环迭代时进行条件判断。换句话说,v-for
决定了循环的次数,而 v-if
决定了是否显示循环的每一项。
下面是一个示例,展示了 v-for
和 v-if
的优先级:
<div> <div v-for="item in items" v-if="item.visible"> {{ item.name }} </div> </div>
在上述示例中,v-for="item in items"
会先被解析和执行,根据 items
数组的长度决定了循环的次数。然后,在每次循环迭代时,v-if="item.visible"
会根据 item.visible
的值来判断是否显示当前循环项。
需要注意的是,由于 v-for
具有较高的优先级,因此在使用 v-if
和 v-for
时,要特别注意两者之间的交互作用。在某些情况下,可能需要使用计算属性或方法来对数据进行预处理,以满足预期的渲染结果。
综上所述,v-for
指令具有比 v-if
更高的优先级,它会在 v-if
之前被解析和执行。因此,在使用 v-if
和 v-for
时,应该注意两者之间的交互作用,并确保达到预期的渲染结果。
5、ref是什么?
在 Vue.js 中,ref
是一个特殊的属性,用于在组件中对 DOM 元素或子组件进行引用。通过 ref
,你可以在组件的实例中访问到这些被引用的元素或组件。
使用 ref
的步骤如下:
- 在模板中给要引用的元素或组件添加
ref
属性,并提供一个唯一的名称。
<template> <div> <input ref="myInput" type="text" /> <button @click="doSomething">Click</button> </div> </template>
- 在组件的实例中,可以通过
$refs
对象来访问ref
引用的元素或组件。
<script> export default { methods: { doSomething() { const inputValue = this.$refs.myInput.value; // 处理获取到的输入值 } } } </script>
上述示例中,我们给 <input>
元素添加了 ref="myInput"
,然后在 doSomething
方法中通过 this.$refs.myInput
来获取该输入框的引用,并通过 .value
获取输入框的值。
需要注意以下几点:
ref
不能在循环中使用,因为它返回一个指向单个元素或组件的引用。- 当
ref
引用的是子组件时,this.$refs
将会是一个对象,其中的属性名就是在ref
中定义的名称,对应的值就是子组件的实例。 ref
引用在组件渲染后才会生效,所以在组件的mounted
钩子函数或之后的生命周期中才能够访问到引用的元素或组件。
总之,ref
是 Vue.js 提供的一种方便的方式,用于在组件中引用 DOM 元素或子组件,并通过 $refs
对象来进行访问和操作。
6、nextTick是什么?
nextTick
是 Vue.js 提供的一个方法,用于在下次 DOM 更新循环结束之后执行延迟回调。它是一种异步执行机制,可以用于确保在更新视图后进行操作。
在 Vue.js 中,DOM 更新是异步的,当响应式数据发生变化时,Vue.js 会对虚拟 DOM 进行更新,并将最终结果反映到实际的 DOM 上。而 nextTick
方法就是用来在这个更新过程完成后执行回调函数的。
使用 nextTick
的步骤如下:
- 在需要等待 DOM 更新完成后执行的地方,调用
this.$nextTick(callback)
方法,传入一个回调函数。
this.$nextTick(() => { // 在 DOM 更新之后执行的操作 });
nextTick
方法返回一个 Promise 对象,你也可以使用async/await
来等待 DOM 更新完成。
async someMethod() { await this.$nextTick(); // 在 DOM 更新之后执行的操作 }
nextTick
的回调函数中可以进行对更新后的 DOM 的访问和操作,例如获取元素的宽高、触发元素的方法或其他一些需要依赖更新后的 DOM 的操作。
需要注意以下几点:
nextTick
是异步执行的,因此回调函数不会立即执行。- 在同一个事件循环中多次调用
nextTick
,只会将回调函数放入一个队列中,在下次 DOM 更新循环时统一执行。 nextTick
是在组件更新之后执行的,所以在组件生命周期的mounted
钩子函数或之后使用nextTick
可以保证 DOM 被正确渲染。- 在单文件组件中,可以使用
this.$nextTick()
来代替Vue.nextTick()
,它是相同的方法。
总之,nextTick
是 Vue.js 提供的一种方式,用于在 DOM 更新完成之后执行回调函数。它可以用来确保在更新后对 DOM 进行操作,以及处理一些依赖更新后的 DOM 的操作。
7、scoped原理
在Vue.js中,scoped
是一个特殊的样式作用域选择器,用于限定组件的样式仅适用于当前组件,防止样式污染和样式冲突。scoped
样式会在编译时为组件中的每个元素添加一个唯一的属性选择器,以确保只有当前组件内的元素会应用这些样式。
使用 scoped
的步骤如下:
- 在组件的
<style>
标签中添加scoped
关键字。
<template> <div class="my-component"> <!-- 组件内容 --> </div> </template> <style scoped> .my-component { /* 组件样式 */ } </style>
- 编译后的结果会给组件模板中的元素自动添加一个唯一的属性选择器,确保样式只应用于当前组件。
<div class="my-component" data-v-xxxxxx> <!-- 组件内容 --> </div>
其中的 data-v-xxxxxx
是用于唯一标识当前组件的样式作用域。
通过这种方式,Vue.js 可以将组件的样式封装在组件本身内部,防止样式冲突。这种封装也使得组件可以更加独立,并且方便地复用和维护。
需要注意以下几点:
- 使用
scoped
样式只对当前组件起作用,不会影响组件的子组件或父组件中的样式。 - 当使用
scoped
样式时,组件内部的样式可以直接使用类选择器或标签选择器,无需添加父级选择器来限定作用域。 - 如果需要在组件内部定义全局样式,可以使用
>>>
或/deep/
修饰符来穿透样式作用域。
例如:
<template> <div class="my-component"> <span class="global-style">全局样式</span> </div> </template> <style scoped> .my-component >>> .global-style { /* 利用 >>> 穿透样式作用域 */ } </style>
总之,scoped
样式是 Vue.js 提供的一种方式,用于限定组件的样式仅适用于当前组件。通过自动添加特定属性选择器的方式,实现了样式的封装和隔离,避免了样式冲突和污染的问题。
8、Vue中如何做样式穿透
在 Vue 中,当你希望在组件中的 scoped
样式中修改其子组件的样式时,可以使用以下两种方式来实现样式穿透:
- 使用 /deep/ 或 >>> 选择器
你可以使用 /deep/
或 >>>
选择器来穿透 scoped
样式。这两个选择器会告诉 Vue 编译器不要将它们视为作用域样式,并允许你在组件内部选择子组件的元素并应用样式。
<template> <div class="parent"> <!-- 父组件 --> <child-component></child-component> </div> </template> <style scoped> .parent >>> .child-class { /* 通过 >>> 或 /deep/ 选择器穿透样式作用域 */ /* 在父组件中修改子组件的样式 */ } </style>
- 使用 CSS Modules
Vue 支持使用 CSS Modules 来进行样式穿透。CSS Modules 是一种用于将 CSS 样式文件模块化的技术,它可以确保每个类名都是唯一的,避免样式冲突。通过 CSS Modules,你可以在组件中引入其他的样式模块,并将其中的类名作为对象来引用。
首先,需要在项目中启用 CSS Modules。你可以通过在 style
标签上添加 module
属性来实现。例如:
<template> <div class="parent"> <!-- 父组件 --> <child-component></child-component> <span :class="$style.childClass"></span> </div> </template> <style module> .parent { /* 父组件样式 */ } .child-class { /* 子组件样式 */ } </style>
然后,在组件中引入样式模块并将其作为对象使用。你可以通过 $style
对象来访问 CSS Modules 中的类名。
上面的例子中,.child-class
类名将变为 $style.childClass
。这样就可以在父组件中通过 $style
对象来修改子组件中的样式。
以上是两种在 Vue 中实现样式穿透的常用方式。你可以根据项目的需求选择适合的方式进行样式穿透。