JS部分
1. 解释 JavaScript 中的闭包,并举例说明其应用场景
闭包是指函数能够记住并访问它的词法作用域,即使这个函数在词法作用域之外执行。
应用场景:
- 数据隐藏:通过闭包,可以创建私有变量,只通过特定的函数来访问和修改这些变量。
- 回调函数和事件处理:闭包常用于设置回调函数,因为回调函数需要访问其外部函数的变量。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
2. 什么是事件循环(Event Loop)?解释宏任务和微任务的区别
事件循环是JavaScript的一种运行机制,它允许非阻塞的异步代码执行。事件循环会不断地检查调用栈是否为空,如果为空,则检查微任务队列,然后执行所有微任务,接着再检查宏任务队列,执行一个宏任务。
宏任务和微任务的区别:
- 宏任务:包括整体脚本、
setTimeout、setInterval等。 - 微任务:包括
Promise的回调、MutationObserver等。
3. 如何实现一个深拷贝函数?
深拷贝是指复制一个对象及其所有嵌套对象,使得新对象与原对象完全独立。
/**
* 深度拷贝函数,用于复制一个对象及其所有嵌套对象,使得新对象与原对象完全独立。
* @param {any} obj - 需要被深拷贝的对象。
* @returns {any} - 返回深拷贝后的新对象。
*/
function deepCopy(obj) {
// 如果传入的是null、undefined、基本数据类型(string, number, boolean等)或者函数,直接返回,因为这些类型不需要深拷贝。
if (obj === null || typeof obj !== 'object' || typeof obj === 'function') {
return obj;
}
// 处理数组
if (Array.isArray(obj)) {
// 使用map方法创建一个新数组,数组的每个元素都是递归调用deepCopy的结果。
return obj.map(item => deepCopy(item));
}
// 处理普通对象
const copy = {}; // 创建一个空对象作为新对象的容器。
for (const key in obj) {
// 使用hasOwnProperty方法确保只拷贝对象自身的属性,不包括从原型链上继承的属性。
if (obj.hasOwnProperty(key)) {
// 递归调用deepCopy,将每个属性的值深拷贝到新对象中。
copy[key] = deepCopy(obj[key]);
}
}
// 返回深拷贝后的新对象。
return copy;
}
4. 解释 call()、apply() 和 bind() 的区别
- call():调用一个函数,其第一个参数指定
this的值,后面参数逐个传递。 - apply():调用一个函数,其第一个参数指定
this的值,第二个参数是一个数组或类数组对象,其中的数组元素将作为单独的参数传给函数。 - bind():创建一个新的函数,在
bind()被调用时,这个新函数的this被指定为bind()的第一个参数,其余参数将作为新函数运行时的初始参数,供调用时使用。
5. 什么是原型链?如何实现继承?
原型链是JavaScript中实现继承的一种机制。每个对象都有一个原型对象,对象从其原型对象继承属性和方法。如果对象自身没有某个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到为止。
实现继承:
- 原型链继承:通过将一个对象的原型设置为另一个对象来实现继承。
- 构造器继承:使用构造器函数来创建对象,并可以在子构造器中调用父构造器。
- 组合继承:结合了原型链继承和构造器继承的优点,使用原型链继承原型属性和方法,使用构造器继承实例属性。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name); // 构造器继承
this.age = age;
}
Child.prototype = new Parent(); // 原型链继承
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
const child = new Child('Alice', 10);
console.log(child.getName()); // Alice
console.log(child.getAge()); // 10
CSS部分
1. 如何实现一个垂直居中的布局?
Flexbox
.parent {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 假设父容器高度为视口高度 */
}
.child {
/* 子元素样式 */
}
Grid
.parent {
display: grid;
place-items: center; /* 同时水平和垂直居中 */
height: 100vh; /* 假设父容器高度为视口高度 */
}
.child {
/* 子元素样式 */
}
使用绝对定位和变换
.parent {
position: relative;
height: 100vh; /* 假设父容器高度为视口高度 */
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
2. BFC(块级格式化上下文)及其应用场景
BFC(Block Formatting Context) 是一个独立的渲染区域,只有属于同一个 BFC 的元素才会相互影响。BFC 的触发条件包括:
- 根元素(
<html>) - 浮动元素(
float不是none) - 绝对定位或固定定位元素(
position是absolute或fixed) display为inline-block,flex,inline-flex,grid,inline-grid,table-cell,table-caption,flow-rootoverflow不是visiblecontain为layout,content, 或paint
应用场景:
- 清除浮动:通过触发 BFC 来包含浮动的元素。
- 防止外边距合并:两个相邻的兄弟元素的外边距在垂直方向上会合并,触发 BFC 可以防止这种情况。
- 自适应两栏布局:利用 BFC 的特性,可以实现左侧固定宽度,右侧自适应的布局。
3. CSS 选择器的优先级是如何计算的?
CSS 选择器的优先级基于选择器的类型,计算规则如下:
- 内联样式:优先级最高,例如
style="..."。 - ID 选择器:如
#id,优先级为0,1,0,0。 - 类选择器、属性选择器、伪类选择器:如
.class,[attr=value],:hover,优先级为0,0,1,0。 - 元素选择器、伪元素选择器:如
div,p,::before,优先级为0,0,0,1。 - 通配符选择器:如
*,优先级最低,为0,0,0,0。
优先级计算时,按 a,b,c,d 的顺序比较,a 的值越大,优先级越高;如果 a 相同,则比较 b,以此类推。
!important > 内联>id>类>标签>通配符
4. 如何实现一个三角形?
通过设置一个元素的宽度和高度为 0,并使用边框来创建三角形。例如,一个向下的三角形可以这样实现:
.triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 100px solid black;
}
正三角,border-bottom:100px solid black;
5. Flexbox 布局及其常用属性
Flexbox 是一种用于在容器中分布、对齐和排列子元素的一维布局方法。
常用属性:
- 容器属性:
display: flex;:将容器设为 Flex 容器。flex-direction: row | row-reverse | column | column-reverse;:定义主轴方向。justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;:定义项目在主轴上的对齐方式。align-items: flex-start | flex-end | center | baseline | stretch;:定义项目在交叉轴上的对齐方式。flex-wrap: nowrap | wrap | wrap-reverse;:定义项目是否换行。align-content: flex-start | flex-end | center | space-between | space-around | stretch;:多行时的对齐方式。
- 项目属性:
order: number;:定义项目的排列顺序。flex-grow: number;:定义项目的放大比例。flex-shrink: number;:定义项目的缩小比例。flex-basis: length | auto;:定义在分配多余空间之前,项目的默认大小。align-self: auto | flex-start | flex-end | center | baseline | stretch;:允许单个项目在交叉轴上有不同的对齐方式,覆盖align-items。
Vue部分
Vue 响应式原理
-
Vue 2(Object.defineProperty)和 Vue 3(Proxy)的响应式实现区别:
- Vue 2:使用
Object.defineProperty方法给对象的属性添加getter和setter,以实现响应式。但这种方法有一些局限性,比如无法监听属性的添加和删除,也无法监听数组的变化(虽然Vue做了特殊处理)。 - Vue 3:使用
Proxy对象来创建一个响应式代理。Proxy不仅可以监听属性的读取和设置,还可以监听属性的添加、删除以及数组的变化,提供了更全面的响应式能力。
- Vue 2:使用
-
Vue 3改用Proxy的原因:
- 解决Vue 2的缺陷:如上述所述,
Proxy解决了Object.defineProperty无法监听属性添加、删除和数组变化的局限。 - 更好的性能:
Proxy的拦截范围更广,可以在一个地方集中处理各种操作,可能有助于优化性能。 - 更简洁的代码:使用
Proxy可以减少一些Vue 2中为了处理特殊情况而增加的复杂代码。
- 解决Vue 2的缺陷:如上述所述,
虚拟 DOM 与 Diff 算法
-
虚拟 DOM 的作用:
- 提高渲染效率:虚拟DOM通过JavaScript对象来描述真实的DOM结构,可以在内存中先构建出虚拟DOM树,然后一次性地将其渲染到真实DOM中,减少了直接操作真实DOM的次数,从而提高了渲染效率。
- 实现跨平台渲染:由于虚拟DOM是JavaScript对象,因此可以很容易地将其渲染到不同的平台上,如Web、小程序、Native等。
-
为什么不用直接操作真实 DOM:
- 性能问题:直接操作真实DOM会导致频繁的页面重绘和重排,严重影响性能。
- 复杂性:直接操作真实DOM需要考虑各种边界情况和浏览器兼容性问题,增加了代码的复杂性。
-
Vue 的 Diff 算法与 React 的区别:
- Vue:采用双端比较算法,即同时从新旧虚拟DOM树的两端开始比较,找到最小的差异点,然后进行更新。这种方式可以减少不必要的比较次数,提高效率。
- React:采用递归比较算法,即对新旧虚拟DOM树的每个节点进行递归比较,找到差异点后进行更新。虽然这种方式比较直观,但在处理大型DOM树时可能会比较耗时。
生命周期
-
beforeCreate 和 created 的区别:
- beforeCreate:实例初始化之后,数据观测(data observer)和事件/侦听器配置之前被调用。此时组件的数据和方法都还未被初始化。
- created:实例已经创建完成后被调用。在这一步,实例已完成数据观测、属性和方法的运算、watch/event事件回调。然而,挂载阶段还没开始,
$el属性目前不可见。
-
在哪个阶段可以访问 data 和 methods:
- 在
created阶段可以访问data和methods,因为此时它们已经被初始化。
- 在
-
如何在 Vue 3 的 Composition API 中模拟生命周期钩子:
- 可以使用
onMounted、onUpdated、onUnmounted等函数来模拟Vue 2中的生命周期钩子。这些函数是Vue 3提供的Composition API的一部分,用于在组件的不同生命周期阶段执行代码。
- 可以使用
组件通信
-
父子组件通信:
- 父组件向子组件传递数据:使用
props。 - 子组件向父组件发送消息:使用
$emit触发事件。
- 父组件向子组件传递数据:使用
-
兄弟组件通信:
- 可以使用事件总线(一个空的Vue实例)来传递消息。
- 或者使用Vuex等状态管理库来共享状态。
-
跨层级通信:
- 使用
provide和inject来在祖先组件和后代组件之间传递数据。
- 使用
-
在大型项目中,如何设计一个全局状态管理方案:
- 可以使用Vuex或Pinia等状态管理库来集中管理应用的状态。
- Vuex是一个专门为Vue应用设计的状态管理模式+库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Pinia是Vue的另一种状态管理库,它提供了更简洁的API和更好的TypeScript支持。
-
Vuex 和 Pinia 的区别:
- Vuex:采用单一状态树(Single Source of Truth),即所有的状态都存储在一个大的对象中。这使得状态管理变得集中和可预测。但Vuex的API相对繁琐,且对于TypeScript的支持不够友好。
- Pinia:提供了更简洁的API和更好的TypeScript支持。Pinia的store是独立的,不需要像Vuex那样创建一个全局的store实例。这使得Pinia在模块化开发时更加灵活。
Composition API
-
对比 Composition API 和 Options API 的优缺点:
- Options API:
- 优点:代码结构清晰,易于理解。每个选项都有其明确的作用域,如
data用于存储数据,methods用于定义方法。 - 缺点:当组件变得复杂时,选项之间的关联性可能会变得难以管理。特别是当需要在多个选项之间共享逻辑时,代码可能会变得冗长和重复。
- 优点:代码结构清晰,易于理解。每个选项都有其明确的作用域,如
- Composition API:
- 优点:提供了更好的逻辑复用性和组织性。通过使用
setup函数和ref、reactive等响应式API,可以将相关的逻辑组合在一起,形成一个可复用的模块。这使得代码更加模块化和易于维护。 - 缺点:对于初学者来说,可能需要一些时间来适应新的API和编程范式。此外,由于Composition API允许在
setup函数中直接访问
- 优点:提供了更好的逻辑复用性和组织性。通过使用
- Options API:
2025/02/17
9万+

被折叠的 条评论
为什么被折叠?



