前端面试题详解整理4|-CSS 实现一个圆 ,异步,元素垂直水平居中, flex 属性 ,H5 中的块元素和行内块元素,判断数据类型,Promise 的方法

157 阅读14分钟

字节前端日常实习(无转正)一面

1、自我介绍

2、CSS 实现一个圆

要使用 CSS 实现一个圆形,可以使用以下方法:

  1. 使用 border-radius 属性设置圆角为 50%。
  2. 将宽度和高度设置为相等的值。
  3. 可以使用 flex 或 grid 布局使元素保持正方形。

下面是一个示例:

.circle {
  width: 100px;
  height: 100px;
  background-color: red;
  border-radius: 50%;
}
<div class="circle"></div>

这样就创建了一个红色的圆形元素,其直径为 100px。

3、让元素垂直居中和水平居中的方法

要让元素实现垂直居中和水平居中,可以使用多种方法,以下是其中几种常用的方法:

  1. 使用 Flexbox 布局:
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}
  1. 使用绝对定位和负边距:
.container {
  position: relative;
}

.element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 水平垂直居中 */
}
  1. 使用 Flexbox 和 margin 属性:
.container {
  display: flex;
}

.element {
  margin: auto; /* 水平垂直居中 */
}
  1. 使用 Grid 布局:
.container {
  display: grid;
  place-items: center; /* 水平垂直居中 */
}

这些方法都能够实现元素的水平居中和垂直居中,具体选择哪种方法取决于项目需求和布局的复杂程度。 4、讲讲 flex 属性

Flexbox(弹性盒子布局)是一种用于在容器中进行布局的现代 CSS 技术。Flexbox 提供了一种灵活的方式来排列、对齐和分配空间,特别适用于创建响应式和动态布局。

Flexbox 中的核心概念是容器和项目。容器是包含项目的父元素,而项目是容器内部的子元素。以下是 Flexbox 中常用的一些属性:

  1. display:

    • display: flex;: 将容器设置为弹性盒子,使其内部的子元素成为弹性项目。
  2. 容器属性:

    • flex-direction: 设置主轴的方向(row、row-reverse、column、column-reverse)。
    • justify-content: 定义了项目在主轴上的对齐方式(flex-start、flex-end、center、space-between、space-around)。
    • 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)。
  3. 项目属性:

    • flex-grow: 定义了项目的放大比例,默认为 0。
    • flex-shrink: 定义了项目的缩小比例,默认为 1。
    • flex-basis: 定义了项目在主轴上的初始大小,默认为 auto。
    • flex: 是 flex-grow, flex-shrinkflex-basis 的简写形式。
    • order: 定义了项目的排列顺序,默认为 0。

Flexbox 的强大之处在于它提供了灵活的布局方式,能够轻松实现各种复杂的布局需求,同时也能很好地适应不同尺寸和设备的屏幕。 5、H5 中的块元素和行内块元素
在 HTML5 中,元素通常被归类为块级元素(Block-level elements)和内联元素(Inline elements)。

  1. 块级元素(Block-level elements):

    • 块级元素通常会在页面中创建一个块,独占一行,其宽度会填充父容器的整个宽度。
    • 常见的块级元素包括 <div><p><h1><h6><ul><ol><li><table><form> 等。
  2. 内联元素(Inline elements):

    • 内联元素通常不会创建新的块,它们会在一行内显示,并且宽度只会占据其内容所需的空间。
    • 常见的内联元素包括 <span><a><strong><em><img><input><button><label> 等。

另外,HTML5 中还有一种特殊的元素称为行内块元素(Inline-block elements),它们具有块级元素的特性,但又像内联元素一样在一行内显示。常见的行内块元素包括 <div>(当设置为 display: inline-block; 时)、<span><img>(当设置为 display: inline-block; 时)等。

这些元素的分类有助于页面布局和样式设计。通过合理地选择和使用这些元素,可以更好地组织页面结构,并实现丰富的页面布局效果。

6、js 数据类型、判断数据类型的方法,call、apply、bind 方法、为什么不使用 toString 而是 Object.prototype.toString()
JavaScript 中的数据类型包括原始数据类型(Primitive data types)和对象类型(Object types)。

  1. 原始数据类型:

    • 原始数据类型包括:字符串(String)、数字(Number)、布尔值(Boolean)、null、undefined 和 Symbol(ES6 新增)。
  2. 对象类型:

    • 对象类型包括:对象(Object)、数组(Array)、函数(Function)、正则表达式(RegExp)等。

判断数据类型的方法有多种,常见的方法包括:

  • 使用 typeof 操作符
  • 使用 instanceof 操作符
  • 使用 Object.prototype.toString.call() 方法

例如:

console.log(typeof "hello"); // 输出 "string"
console.log(typeof 123); // 输出 "number"
console.log(typeof true); // 输出 "boolean"
console.log(typeof {}); // 输出 "object"
console.log(typeof []); // 输出 "object"
console.log(typeof function() {}); // 输出 "function"

console.log([] instanceof Array); // 输出 true
console.log({} instanceof Object); // 输出 true

console.log(Object.prototype.toString.call("hello")); // 输出 "[object String]"
console.log(Object.prototype.toString.call([])); // 输出 "[object Array]"

关于 callapplybind 方法:

  • 这三个方法都是用来改变函数的执行上下文(即 this 的指向)。
  • callapply 方法都是立即执行函数,唯一的区别在于参数的传递方式不同:call 方法接收参数列表,而 apply 方法接收一个参数数组。
  • bind 方法是创建一个新函数,并将原函数的执行上下文(this)绑定到指定对象,但不会立即执行。 bind方法创建一个新的函数,将原先函数的执行上下文绑定到指定对象当中不会立即执行

关于为什么不直接使用 toString 方法而是 Object.prototype.toString 方法:

  • 在 JavaScript 中,每个对象都有一个 toString 方法,用于返回对象的字符串表示形式。

  • 但是,直接调用 toString 方法可能会因为对象覆盖了该方法而返回意外的结果。

  • 为了确保获取准确的对象类型,通常使用 Object.prototype.toString 方法,该方法会返回一个表示对象类型的字符串,例如 "[object Object]""[object Array]" 等。

7、let、const、var,暂时性死区,先 console.log 输出变量再声明 和 如果先使用一个函数,再使用 var 声明,分别会报什么错误
在 JavaScript 中,如果先使用一个变量或函数而后再声明它,会导致不同的行为:

  1. 先使用再声明变量:

    • 如果先使用一个变量而后再声明它,JavaScript 解释器会报错 ReferenceError: Cannot access 'variable' before initialization,因为在变量声明前尝试访问该变量会导致引用错误。
    console.log(variable); // ReferenceError: Cannot access 'variable' before initialization
    var variable = "example";
    
  2. 先使用再声明函数:

    • 如果先调用一个函数而后再声明它,JavaScript 解释器不会报错,而是将函数声明提升到作用域顶部。这种现象被称为函数声明提升(Function Declaration Hoisting)。
    functionExample(); // 函数调用不会报错
    function functionExample() {
        console.log("Function Example");
    }
    

    这是因为 JavaScript 在解释代码时会先将函数声明提升到作用域的顶部,所以在调用函数之前已经存在了函数的声明。但需要注意的是,对于函数表达式,不会发生声明提升。

总结:

  • 对于变量,先使用再声明会报引用错误。

  • 对于函数声明,先调用再声明不会报错,因为函数声明会被提升到作用域的顶部。

8、Promise 的方法、async await、Generator 函数,什么函数能被作为 Generator 函数

  1. Promise的方法:

    • Promise.resolve(value): 返回一个解析过的Promise对象,可以是给定的值或者另一个Promise对象。
    • Promise.reject(reason): 返回一个拒绝的Promise对象,带有一个指定的失败原因。
    • Promise.all(iterable): 返回一个Promise对象,当所有给定的Promise都已经解决或者至少一个Promise被拒绝时,返回的Promise将被解决。
    • Promise.race(iterable): 返回一个Promise对象,一旦迭代器中的其中一个Promise解决或拒绝,返回的Promise就会解决或拒绝。
    • Promise.prototype.then(onFulfilled, onRejected): 添加解决和拒绝回调到Promise链中,返回一个新的Promise对象。
    • Promise.prototype.catch(onRejected): 添加一个拒绝回调到Promise链中,返回一个新的Promise对象。
  2. async/await:

    • async 函数是用来定义一个返回Promise对象的异步函数。在 async 函数内部,可以使用 await 表达式来等待一个Promise对象的解决。
    • await 表达式会暂停 async 函数的执行,等待 Promise 对象的解决,然后恢复 async 函数的执行,并返回解决的值。
  3. Generator函数:

    • Generator函数是ES6引入的一种新的函数类型,通过function*关键字定义。它可以通过yield语句来定义一个生成器对象,该对象可以被迭代器进行迭代。
    • Generator函数内部可以使用yield语句来产生值,函数的执行状态会被保存,直到下一次调用生成器对象的next()方法。
  4. 什么函数能被作为Generator函数:

    • 任何使用 function* 关键字定义的函数都可以被视为 Generator 函数。Generator函数可以产生一个迭代器对象,该对象包含一个next()方法,用于依次获取函数内部yield语句产生的值。

9、箭头函数与普通函数的区别,不能做构造函数,那说说使用new关键字的原理
箭头函数与普通函数的主要区别在于它们的语法形式和作用域绑定:

  1. 语法形式:

    • 箭头函数使用箭头(=>)来声明,通常采用匿名函数的形式。
    • 普通函数使用关键字 function 来声明,可以是匿名函数或具名函数。
  2. this 的指向:

    • 箭头函数的 this 指向在定义时就已经确定,指向其父级作用域中的 this
    • 普通函数的 this 指向在运行时根据调用方式确定,可能会发生动态变化。
  3. 不能用作构造函数:

    • 箭头函数不能通过 new 关键字来实例化,因为箭头函数没有自己的 this,也没有 prototype 属性。
    • 普通函数可以通过 new 关键字来创建实例,通过 prototype 属性继承原型方法和属性。

关于 new 关键字的原理,当我们使用 new 关键字调用函数时,发生了以下几个步骤:

  1. 创建一个新的空对象。
  2. 将这个新对象的原型(__proto__)指向构造函数的 prototype 属性。
  3. 将构造函数的 this 指向这个新对象。
  4. 执行构造函数内部的代码,同时可以通过 this 给新对象添加属性和方法。
  5. 如果构造函数没有显式返回一个对象,则返回这个新对象;否则,返回显式返回的对象。

因此,普通函数通过 new 关键字可以创建实例对象,而箭头函数由于缺少自己的 thisprototype,因此无法被 new 关键字调用。

10、React 生命周期函数
在React中,组件的生命周期函数主要包括以下阶段:

  1. 挂载阶段(Mounting):

    • constructor(): 在组件被创建时调用,用于初始化状态和绑定事件处理函数。
    • static getDerivedStateFromProps(): 在组件接收到新的 props 时调用,在组件初始化和更新阶段都会被调用。
    • render(): 根据组件的 props 和 state 来渲染组件的内容,必须返回一个React元素。
    • componentDidMount(): 组件挂载后调用,可以执行DOM操作、网络请求等副作用。
  2. 更新阶段(Updating):

    • static getDerivedStateFromProps(): 同挂载阶段。
    • shouldComponentUpdate(): 在组件接收到新的 props 或 state 时调用,用于控制是否重新渲染组件,默认返回true。
    • render(): 同挂载阶段。
    • getSnapshotBeforeUpdate(): 在最新的DOM渲染输出被提交到DOM树之前调用,可以用于保存当前DOM状态。
    • componentDidUpdate(): 组件更新后调用,可以执行更新后的DOM操作或发起网络请求等副作用。
  3. 卸载阶段(Unmounting):

    • componentWillUnmount(): 组件即将被卸载和销毁时调用,用于清理定时器、取消订阅等操作。
  4. 错误处理阶段(Error Handling):

    • static getDerivedStateFromError(): 在子组件抛出错误后调用,用于渲染备用UI。
    • componentDidCatch(): 在子组件抛出错误后调用,用于记录错误信息或发送错误报告。

这些生命周期函数使得我们可以在不同阶段执行适当的操作,从而控制组件的行为和状态,实现更丰富的交互和功能。需要注意的是,React 16.3之后推荐使用static getDerivedStateFromProps()getSnapshotBeforeUpdate()等新的生命周期函数代替过时的生命周期函数,同时引入了React Hooks来管理组件的状态和副作用。

11、为什么要使用 hooks,除了复用一些数据状态的逻辑,还有其他原因码吗
使用Hooks的一个主要原因是它提供了一种更简洁、更灵活的方式来管理组件的状态和生命周期。除了可以复用数据状态的逻辑外,Hooks还具有以下几个重要优点:

  1. 更易于理解和维护: Hooks使得组件逻辑更加分离和清晰,使得组件的代码更易于理解和维护。每个状态和副作用都可以独立存在于自己的Hook函数中,不会像传统的类组件那样分散在多个生命周期方法中。

  2. 减少样板代码: 使用Hooks可以减少大量的样板代码,使得组件更加简洁。相比于类组件,Hooks不需要编写构造函数、bind方法和大量的生命周期方法,可以更专注于组件的核心逻辑。

  3. 更好的组件复用性: Hooks使得组件逻辑可以更轻松地提取和复用。通过将状态逻辑封装在自定义的Hook函数中,可以方便地在不同的组件之间共享状态逻辑,从而提高了组件的复用性。

  4. 更好的性能优化: Hooks的设计可以帮助React更好地优化组件的性能。由于Hooks可以在函数组件中直接使用,而不需要通过类组件的实例化和绑定,因此可以更容易地实现一些性能优化策略,比如更精细地控制组件的更新。

总的来说,Hooks是React提供的一种现代化的组件编写方式,它能够带来更好的代码组织、更清晰的逻辑分离、更高的组件复用性和更好的性能优化,是React开发中的一个重要利器。

12、代码:给一个数组的版本号排序
要对数组中的版本号进行排序,首先需要将版本号解析成可比较的格式,然后使用适当的排序方法进行排序。通常,版本号可以解析成数字数组,然后按照数字数组进行排序。以下是一个示例代码:

// 定义包含版本号的数组
let versions = ["1.3.2", "2.0", "1.5", "3.1.4", "1.0.0"];

// 定义解析版本号的函数
function parseVersion(version) {
    return version.split(".").map(Number);
}

// 对版本号数组进行排序
versions.sort((a, b) => {
    const versionA = parseVersion(a);
    const versionB = parseVersion(b);
    // 逐个比较版本号的每个部分
    for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
        const numA = versionA[i] || 0;
        const numB = versionB[i] || 0;
        if (numA !== numB) {
            return numA - numB;
        }
    }
    // 如果所有部分都相等,则按照版本号长度排序
    return versionA.length - versionB.length;
});

// 输出排序后的版本号数组
console.log(versions);

这段代码首先定义了一个解析版本号的函数 parseVersion(),然后使用 sort() 方法对版本号数组进行排序。在排序过程中,首先将版本号解析成数字数组,然后逐个比较每个部分的大小,最终按照版本号的大小进行排序。排序后的版本号数组将按照从小到大的顺序排列。

12、反问

面完第二天下午通知二面
#面经##字节#

作者:起个响亮的名字___
链接:www.nowcoder.com/feed/main/d…
来源:牛客网