前端面试题常考汇总1

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的一种运行机制,它允许非阻塞的异步代码执行。事件循环会不断地检查调用栈是否为空,如果为空,则检查微任务队列,然后执行所有微任务,接着再检查宏任务队列,执行一个宏任务。

宏任务和微任务的区别

  • 宏任务:包括整体脚本、setTimeoutsetInterval等。
  • 微任务:包括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 的触发条件包括:

  • 根元素(
  • 浮动元素(float 不是 none
  • 绝对定位或固定定位元素(position 是 absolute 或 fixed
  • display 为 inline-blockflexinline-flexgridinline-gridtable-celltable-captionflow-root
  • overflow 不是 visible
  • contain 为 layoutcontent, 或 paint

应用场景

  1. 清除浮动:通过触发 BFC 来包含浮动的元素。
  2. 防止外边距合并:两个相邻的兄弟元素的外边距在垂直方向上会合并,触发 BFC 可以防止这种情况。
  3. 自适应两栏布局:利用 BFC 的特性,可以实现左侧固定宽度,右侧自适应的布局。

3. CSS 选择器的优先级是如何计算的?

CSS 选择器的优先级基于选择器的类型,计算规则如下:

  • 内联样式:优先级最高,例如 style="..."
  • ID 选择器:如 #id,优先级为 0,1,0,0
  • 类选择器、属性选择器、伪类选择器:如 .class[attr=value]:hover,优先级为 0,0,1,0
  • 元素选择器、伪元素选择器:如 divp::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 响应式原理

  1. Vue 2(Object.defineProperty)和 Vue 3(Proxy)的响应式实现区别
    • Vue 2:使用Object.defineProperty方法给对象的属性添加getter和setter,以实现响应式。但这种方法有一些局限性,比如无法监听属性的添加和删除,也无法监听数组的变化(虽然Vue做了特殊处理)。
    • Vue 3:使用Proxy对象来创建一个响应式代理。Proxy不仅可以监听属性的读取和设置,还可以监听属性的添加、删除以及数组的变化,提供了更全面的响应式能力。
  2. Vue 3改用Proxy的原因
    • 解决Vue 2的缺陷:如上述所述,Proxy解决了Object.defineProperty无法监听属性添加、删除和数组变化的局限。
    • 更好的性能Proxy的拦截范围更广,可以在一个地方集中处理各种操作,可能有助于优化性能。
    • 更简洁的代码:使用Proxy可以减少一些Vue 2中为了处理特殊情况而增加的复杂代码。

     虚拟 DOM 与 Diff 算法

  1. 虚拟 DOM 的作用
    • 提高渲染效率:虚拟DOM通过JavaScript对象来描述真实的DOM结构,可以在内存中先构建出虚拟DOM树,然后一次性地将其渲染到真实DOM中,减少了直接操作真实DOM的次数,从而提高了渲染效率。
    • 实现跨平台渲染:由于虚拟DOM是JavaScript对象,因此可以很容易地将其渲染到不同的平台上,如Web、小程序、Native等。
  2. 为什么不用直接操作真实 DOM
    • 性能问题:直接操作真实DOM会导致频繁的页面重绘和重排,严重影响性能。
    • 复杂性:直接操作真实DOM需要考虑各种边界情况和浏览器兼容性问题,增加了代码的复杂性。
  3. Vue 的 Diff 算法与 React 的区别
    • Vue:采用双端比较算法,即同时从新旧虚拟DOM树的两端开始比较,找到最小的差异点,然后进行更新。这种方式可以减少不必要的比较次数,提高效率。
    • React:采用递归比较算法,即对新旧虚拟DOM树的每个节点进行递归比较,找到差异点后进行更新。虽然这种方式比较直观,但在处理大型DOM树时可能会比较耗时。

生命周期

  1. beforeCreate 和 created 的区别
    • beforeCreate:实例初始化之后,数据观测(data observer)和事件/侦听器配置之前被调用。此时组件的数据和方法都还未被初始化。
    • created:实例已经创建完成后被调用。在这一步,实例已完成数据观测、属性和方法的运算、watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。
  2. 在哪个阶段可以访问 data 和 methods
    • created阶段可以访问datamethods,因为此时它们已经被初始化。
  3. 如何在 Vue 3 的 Composition API 中模拟生命周期钩子
    • 可以使用onMountedonUpdatedonUnmounted等函数来模拟Vue 2中的生命周期钩子。这些函数是Vue 3提供的Composition API的一部分,用于在组件的不同生命周期阶段执行代码。

组件通信

  1. 父子组件通信
    • 父组件向子组件传递数据:使用props
    • 子组件向父组件发送消息:使用$emit触发事件。
  2. 兄弟组件通信
    • 可以使用事件总线(一个空的Vue实例)来传递消息。
    • 或者使用Vuex等状态管理库来共享状态。
  3. 跨层级通信
    • 使用provideinject来在祖先组件和后代组件之间传递数据。
  4. 在大型项目中,如何设计一个全局状态管理方案
    • 可以使用Vuex或Pinia等状态管理库来集中管理应用的状态。
    • Vuex是一个专门为Vue应用设计的状态管理模式+库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    • Pinia是Vue的另一种状态管理库,它提供了更简洁的API和更好的TypeScript支持。
  5. Vuex 和 Pinia 的区别
    • Vuex:采用单一状态树(Single Source of Truth),即所有的状态都存储在一个大的对象中。这使得状态管理变得集中和可预测。但Vuex的API相对繁琐,且对于TypeScript的支持不够友好。
    • Pinia:提供了更简洁的API和更好的TypeScript支持。Pinia的store是独立的,不需要像Vuex那样创建一个全局的store实例。这使得Pinia在模块化开发时更加灵活。

Composition API

  1. 对比 Composition API 和 Options API 的优缺点
    • Options API
      • 优点:代码结构清晰,易于理解。每个选项都有其明确的作用域,如data用于存储数据,methods用于定义方法。
      • 缺点:当组件变得复杂时,选项之间的关联性可能会变得难以管理。特别是当需要在多个选项之间共享逻辑时,代码可能会变得冗长和重复。
    • Composition API
      • 优点:提供了更好的逻辑复用性和组织性。通过使用setup函数和refreactive等响应式API,可以将相关的逻辑组合在一起,形成一个可复用的模块。这使得代码更加模块化和易于维护。
      • 缺点:对于初学者来说,可能需要一些时间来适应新的API和编程范式。此外,由于Composition API允许在setup函数中直接访问

2025/02/17 

你可能感兴趣的:(前端)