2024秋招大厂前端面经202条

2024秋招大厂前端面经213条

1.搭建一个项目的流程

1. 需求分析:在开始任何项目之前,你需要明确项目的目标和需求。这可能涉及到与利益相关者(比如客户、项目经理、团队成员等)的讨论和沟通,理解项目的目标,用户需求,业务逻辑,以及项目的时间线等。
2. 技术选型:在明确了项目需求之后,就需要选择合适的技术栈来实现这些需求。这可能包括编程语言、框架、数据库、版本控制系统、测试工具、构建和部署工具等。
3. 创建项目结构:使用选定的技术栈创建项目的基本结构。如果使用的是像 Create React App 或 Express Generator 这样的脚手架工具,那么这一步可能会自动完成。
4. 编写代码:根据需求开始编写代码。这应该包括前端的 UI 代码、后端的业务逻辑代码、数据库访问代码,以及可能的其他代码(比如单元测试、集成测试等)。
5. 测试:编写和执行测试用例,确保代码的正确性和质量。这可能包括单元测试、集成测试、系统测试和可能的其他测试。
6. 版本控制:使用 Git 或其他版本控制系统来管理代码的版本。这应该包括常规的提交代码、拉取更新、解决冲突等操作。
7. 构建和部署:使用构建工具(比如 Webpack、Grunt、Gulp 等)将代码编译、打包成可以在生产环境运行的代码,然后将其部署到服务器。
8. 监控和维护:在项目上线之后,需要监控项目的运行状况,定位和修复可能出现的问题,并根据用户反馈或新的需求进行必要的维护和更新。

2.合作时代码规范和质量

  1. 代码风格指南:制定并遵循一套代码风格指南对于保持代码的一致性和可读性非常有帮助。这可以是团队制定的自定义规则,也可以是广泛接受的社区风格指南,如 JavaScript 的 Airbnb Style Guide 或 Python 的 PEP 8。
  2. 代码审查:代码审查是一种有效的质量保证方法,它可以帮助发现代码的问题和不足。团队成员可以在合并代码到主分支之前,对其他人的代码进行审查。
  3. 持续集成/持续部署(CI/CD):CI/CD 系统可以在代码提交时自动运行测试、构建和部署流程,确保只有通过测试的代码才能被部署。
  4. 静态代码分析:静态代码分析工具如 ESLint(JavaScript)、Pylint(Python)等可以帮助检测代码中的潜在问题,包括语法错误、未使用的变量、不符合代码风格指南的代码等。
  5. 单元测试和测试覆盖率:编写并维护单元测试可以帮助确保代码的正确性。测试覆盖率工具可以帮助检测哪些代码没有被测试覆盖。
  6. 代码格式化工具:工具如 Prettier(JavaScript)、Black(Python)等可以自动格式化代码,使其符合指定的代码风格。
  7. 文档:好的文档可以帮助团队成员理解和使用代码。文档应该包括代码的功能、API、使用方法、部署流程等信息。
  8. 定期的代码重构:随着项目的发展,代码的结构可能需要进行调整以适应新的需求或改善代码的质量。定期的代码重构可以帮助保持代码的健康。

3.react hooks在使用时有哪些禁忌点

● 不能在循环、条件语句、嵌套函数里使用
● 不能在类组件中使用,只在函数式组件中使用
为什么:
react hooks的数据结构是链表,对顺序有严格要求,如果在循环、条件语句、嵌套函数里使用会导致顺序的不确定性,容易出现错误。

4.useState和setstate是同步还是异步

react18之后是异步的,react 会把一些可以一起更新的 useState/setState 放在一起,进行合并更新。
在实际的 useState/setState 前后各加了段逻辑给包了起来。只要是在同一个事务中的 setState 会进行合并(注意,useState不会进行state的合并)处理。

5.react编译成js

Babel 是一个 JavaScript 编译器,它能将最新的 JavaScript 语法(包括 JSX)转换为旧版本的 JavaScript,以确保在旧的浏览器或环境中也可以运行。当它处理 JSX 时,主要通过以下步骤:

  1. 解析:首先,Babel 会解析(解析)JSX 代码,将其转换为一个称为抽象语法树(AST)的数据结构。在这个阶段,Babel 会理解代码的结构和语义。
  2. 转换:然后,Babel 会通过其插件系统来转换 AST。特别地,对于 JSX,Babel 会使用一个名为 @babel/plugin-transform-react-jsx 的插件(或者在旧的 Babel 版本中是 transform-react-jsx 插件)。这个插件将 JSX 元素转换为 React.createElement() 的调用。例如,这个 JSX 代码片段
const element = <h1 className="greeting">Hello, world!</h1>;

将被转换为:

const element = React.createElement('h1', {
   className: 'greeting'}, 'Hello, world!'); 
  1. 生成:最后,Babel 会将转换后的 AST 再生成回 JavaScript 代码。
    这个过程是在构建过程中自动完成的,通常使用诸如 Webpack 这样的构建工具,或者是 Create React App 这样的脚手架工具。
    需要注意的是,尽管 Babel 将 JSX 转换为了普通的 JavaScript,但是转换后的代码仍然依赖于 React(因为它使用了 React.createElement())。这就是为什么在你的 JSX 文件中需要导入 React 的原因。

6.react更新的原理

React 的更新原理基于它的两个核心概念:虚拟 DOM (Virtual DOM)和 Reconciliation(协调)过程。
以下是 React 进行更新的主要步骤:

  1. 触发更新:当一个组件的 state(状态)或 props(属性)发生变化时,React 将开始更新过程。这个变化可能是由于用户交互、父组件重新渲染,或者是网络响应等引起的。
  2. 生成新的虚拟 DOM:React 会通过调用相应组件的 render 方法来生成一个新的虚拟 DOM 树。
  3. Reconciliation(协调):然后,React 会进行 Reconciliation 过程,也就是比较新旧虚拟 DOM 树的差异。React 通过高效的差异比较算法(Diffing Algorithm)来确定哪些部分需要进行更新。这个过程被称为 “diffing”。
  4. 更新真实 DOM:基于 Reconciliation 的结果,React 然后会更新那些需要改变的真实 DOM 节点。这个阶段被称为 “commit” 阶段。React 尽可能地减少操作真实 DOM,因为直接操作 DOM 是比较消耗性能的。
  5. 生命周期方法:在整个更新过程中,React 会在特定的时刻调用一些生命周期方法,如 componentDidUpdate 和 componentWillUnmount 等。这为开发者提供了在更新过程中添加自定义逻辑的机会。
    React 的这种更新机制,通过尽可能减少真实 DOM 的操作,提高了应用的性能。它使得开发者能够以声明式的方式编写 UI,而无需关心如何应用更新,大大提高了开发效率。

7.什么是虚拟DOM

由 JavaScript 对象表示,映射到实际的 DOM 节点。这是一种对真实 DOM 的抽象表示,这种表示形式可以让我们在不直接操作 DOM 的情况下改变我们的视图。
下面是虚拟 DOM 的工作流程:

  1. 当视图的状态发生变化时(例如,用户输入数据,网络响应返回等),新的虚拟 DOM 被创建。
  2. 新的虚拟 DOM 与旧的虚拟 DOM 进行比较(这通常被称为 “diffing”)。
  3. 通过这种比较,计算出需要在真实 DOM 中做出哪些改变。
  4. 这些改变(被称为 “patches”)被应用到真实的 DOM,这个过程被称为 “reconciliation”。
    这种方式的主要优点是它允许开发者写出像操作真实 DOM 一样的代码,而无需关心性能问题,因为操作虚拟 DOM 的代价更小,并且最终操作真实 DOM 的次数被最小化了。

react有哪些api来实现虚拟DOM

  • React.createElement():这个 API 是创建一个新的 React 元素(即虚拟 DOM 节点)的基础。你可以使用它来创建一个新的元素。
React.createElement(type, [props], [...children])
  • React.Component:这是 React 的基础组件类。创建一个 React 组件就意味着创建了一个自定义的虚拟 DOM 节点。
class MyComponent extends React.Component {
   
  render() {
   
    return <div>Hello World</div>;
  }
}
  • React.PureComponent:这是一个与 React.Component 相似的组件类,但它实现了 shouldComponentUpdate(),对 props 和 state 进行浅比较,如果没有改变则不重新渲染,从而提高性能。
class MyPureComponent extends React.PureComponent {
   
  render() {
   
    return <div>Hello World</div>;
  }
}
  • React.Fragment:这是一个特殊类型的 React 元素,它让你可以返回多个元素,而无需额外创建一个包装元素。
function MyComponent() {
   
  return (
    <React.Fragment>
      <div>Hello</div>
      <div>World</div>
    </React.Fragment>
  );
}
  • Hooks:React 的 Hooks API(例如 useState,useEffect)让你在函数组件中可以使用 state 和生命周期方法,这使得虚拟 DOM 的创建和更新更加灵活和方便。
function MyComponent() {
   
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
   
    document.title = `Count: ${
     count}`;
  });

  return (
    <div>
      <p>You clicked {
   count} times</p>
      <button onClick={
   () => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • JSX:虽然 JSX 不是 React API 的一部分,但它是创建 React 元素的一种更简洁和易于理解的方式。JSX 在编译阶段会被转换成 React.createElement() 调用。
const element = <div>Hello World</div>; // 编译后:React.createElement('div', null, 'Hello World');

import react from 'react’干嘛的

import React from ‘react’ 是在 JavaScript 和 React 项目中引入 React 库的常见方式。这个语句在 ES6 模块系统中,用于导入 React 模块,并将其赋值给名为 React 的变量。
这样,你就可以在你的代码中使用 React 变量来访问 React 库的所有导出,包括 React.Component,React.createElement,hooks 如 React.useState 和 React.useEffect 等。
最重要的是,如果你在你的组件文件中使用 JSX(一种 JavaScript 的语法扩展,看起来像 HTML),你需要导入 React。因为 JSX 最终会被转译为 React.createElement 调用,如果你没有导入 React,这个调用就会失败。
React.createElement() 是 React 的底层 API,用于创建一个 React 元素,即一个虚拟 DOM 节点。在大多数情况下,你可能不会直接使用这个 API,而是使用 JSX 语法,这是一种更简洁、更易读的方式来创建 React 元素。然而,背后的实现其实是 React.createElement()。

8.虚拟DOM的优点

  1. 提高性能:虚拟 DOM 可以在内存中通过 JavaScript 对象进行更快的创建和修改操作。直接操作真实 DOM 通常是昂贵且复杂的,而通过使用虚拟 DOM,我们可以最小化真实 DOM 的操作,从而提高性能。
  2. 跨平台:虚拟 DOM 不只是对浏览器的 DOM 的抽象,也可以作为跨平台的抽象——例如,在 React Native 中,用于创建原生移动应用。
  3. 简化编程模型:虚拟 DOM 提供了一种更声明式的 UI 编程模型。你只需描述你希望 UI 是什么样子,而不需要关心如何去修改每一个 DOM 节点。
  4. 更好的开发者体验:配合各种现代前端框架,虚拟 DOM 可以提供更好的开发体验,如优化的错误提示,以及更方便的组件抽象和复用。
  5. 优化的渲染和更新策略:虚拟 DOM 能够聪明地计算出最少的操作来应用新的状态,这被称为 diffing algorithm 或 reconciliation。

9.JS中的一些数组方法

改变原数组

push(): 向数组的末尾添加一个或多个元素,并返回新的长度。
let array = [1, 2, 3];
array.push(4);
console.log(array); // 输出 [1, 2, 3, 4]
pop(): 从数组中删除最后一个元素,并返回该元素。
let array = [1, 2, 3, 4];
array.pop();
console.log(array); // 输出 [1, 2, 3]
shift(): 从数组中删除第一个元素,并返回该元素。
let array = [1, 2, 3];
array.shift();
console.log(array); // 输出 [2, 3]
unshift(): 向数组的开头添加一个或多个元素,并返回新的长度。
let array = [2, 3];
array.unshift(1);
console.log(array); // 输出 [1, 2, 3]
splice(): 在数组中添加、删除或替换元素。
let array = [1, 3, 4];
array.splice(1, 0, 2); // 在索引 1 的位置添加元素 2
console.log(array); // 输出 [1, 2, 3, 4]

array.splice(1, 1); // 在索引 1 的位置删除一个元素
console.log(array); // 输出 [1, 3, 4]

array.splice(1, 1, 2); // 在索引 1 的位置替换一个元素
console.log(array); // 输出 [1, 2, 4]
reverse(): 颠倒数组中元素的顺序。
let array = [1, 2, 3, 4];
array.reverse();
console.log(array); // 输出 [4, 3, 2, 1]
sort(): 对数组元素进行排序。
let array = [3, 1, 4, 2];
array.sort();
console.log(array); // 输出 [1, 2, 3, 4]

不改变原数组

concat(): 连接两个或更多的数组,并返回结果。
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
let newArray = array1.concat(array2);
console.log(newArray); // 输出 [1, 2, 3, 4, 5, 6]
console.log(array1); // 输出 [1, 2, 3]
join(): 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
let array = [1, 2, 3];
let string = array.join("-");
console.log(string); // 输出 "1-2-3"
slice(): 选取数组的的一部分,并返回一个新数组。
let array = [1, 2, 3, 4, 5];
let newArray = array.slice(1, 3);
console.log(newArray); // 输出 [2, 3]
console.log(array); // 输出 [1, 2, 3, 4, 5]
map(): 通过指定函数处理数组的每个元素,并返回处理后的数组。
let array = [1, 2, 3];
let newArray = array.map(x => x * 2);
console.log(newArray); // 输出 [2, 4, 6]
console.log(array); // 输出 [1, 2, 3]
filter(): 创建一个新的数组,新数组中的元素是通过检查指定函数的返回值并返回所有返回值为true的元素。
let array = [1, 2, 3, 4, 5];
let newArray = array.filter(x => x > 3);
console.log(newArray); // 输出 [4, 5]
console.log(array); // 输出 [1, 2, 3, 4, 5]
reduce(): 将数组元素计算为一个值(从左到右)
let array = [1, 2, 3, 4, 5];
let sum = array.reduce((total, value) => total + value, 0);
console.log(sum); // 输出 15
console.log(array); // 输出 [1, 2, 3, 4, 5]

10.map和forEach的区别

1.返回值 map 方法会返回一个新数组,这个新数组的元素是原数组中每个元素执行提供的函数后的结果。这意味着 map 不会改变原来的数组。

const array = [1, 2, 3];
const newArray = array.map(value => value * 2);
console.log(newArray); // 输出 [2, 4, 6]
console.log(array); // 输出 [1, 2, 3]

另一方面,forEach 方法不返回任何值(或者说返回 undefined)。它仅仅是对数组进行遍历,执行提供的函数。
2.链式调用 因为 map 返回一个新数组,所以可以进行链式调用。也就是说,你可以在一个 map 调用的结果上直接调用另一个数组方法。

const array = [1, 2, 3];
const newArray = array.map(value => value * 2).filter(value => value > 3);
console.log(newArray); // 输出 [4, 6]

另一方面,forEach 因为没有返回值,所以不能进行链式调用。
3.中断 forEach 没有办法在中间停止或者中断循环,除非抛出一个异常。而其他的数组方法,如 every,some,find 等,都可以在满足特定条件时提前终止。

11.数组扁平化

(1)递归实现
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
   
  let result = [];

  for(let i = 0; i < arr.length; i++) {
   
    if(Array.isArray(arr[i])) {
   
      result = result.concat(flatten(arr[i]));
    } else {
   
      result.push(arr[i]);
    }
  }
  return result;
}
flatten(arr);  //  [1, 2, 3, 4,5]

(2)reduce 函数迭代
从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
(3)扩展运算符实现
这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
(4)split 和 toString
可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
(5)ES6 中的 flat
我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。 (6)正则和 JSON 方法 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组。

12.判断是否是数组

● 通过Object.prototype.toString.call()做判断
● 通过原型链做判断 obj.proto === Array.prototype;
● 通过ES6的Array.isArray()做判断
● 通过instanceof做判断
● 通过Array.prototype.isPrototypeOf

13.数组去重

1.使用 SetJavaScript 中的 Set 是一种特殊的类型,它只能存储唯一的值。利用这个特性,我们可以很容易地实现数组去重:

const array = [1, 2, 3, 2, 1];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出 [1, 2, 3]

2.使用 filter我们还可以使用数组的 filter 方法来进行去重

const array = [1, 2, 3, 2, 1];
const uniqueArray = array.filter((value, index, self) => {
   
  return self.indexOf(value) === index;
});
console.log(uniqueArray); // 输出 [1, 2, 3]

在这个例子中,filter 方法遍历数组,只返回那些满足条件的元素。这里的条件是元素的索引必须和它在数组中第一次出现的位置的索引相同,这样就可以排除掉重复的元素。
3.使用 reduce我们也可以使用 reduce 方法来进行去重

const array = [1, 2, 3, 2, 1];
const uniqueArray = array.reduce((accumulator, value) => {
   
  if (!accumulator.includes(value)) {
   
    accumulator.push(value);
  }
  return accumulator;
}, []);
console.log(uniqueArray); // 输出 [1, 2, 3]

在这个例子中,reduce 方法遍历数组,对每个元素执行一个函数,然后累积其结果。这里的函数检查累积器(即已经处理过的元素组成的数组)是否已经包含当前元素,如果没有,则将其添加到累积器中。

14.实现异步的方法

1、回调函数的方式
2、Promise对象
3、生成器函数 Generator/ yield
4、async/await 函数的实现
5、事件监听
6、发布/订阅

15.订阅与发布的实现

发布-订阅模式是一种在软件架构中用于解耦复杂系统的模式。在这种模式中,发布者(publisher)生成事件,而订阅者(subscriber)则监听并对这些事件进行响应。发布者和订阅者之间没有直接的依赖关系,这使得系统的各个部分可以独立地进行更改和扩展。
以下是使用 JavaScript 实现发布-订阅模式的基本示例:

class PubSub {
   
  constructor() {
   
    this.subscribers = {
   };
  }

  // 添加订阅者
  subscribe(event, callback) {
   
    if (!this.subscribers[event]) {
   
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }

  // 移除订阅者
  unsubscribe(event, callback) {
   
    if (!this.subscribers[event]) return;
    this.subscribers[event] = this.subscribers[event].filter((cb) => cb !== callback);
  }

  // 发布事件
  publish(event, data) {
   
    if (!this.subscribers[event]) return;
    this.subscribers[event].forEach((callback) => callback(data));
  }
}

const pubSub = new PubSub();

// 订阅事件
pubSub.subscribe('event1', (data) => console.log(`event1 was published with data: "${
     data}"`));

// 发布事件
pubSub.publish('event1', 'Hello, world!');  // 输出 "event1 was published with data: "Hello, world!""

在这个例子中,PubSub 类管理了一个名为 subscribers 的对象,该对象将事件名称映射到回调函数数组。subscribe 方法用于添加新的订阅者,unsubscribe 方法用于移除订阅者,而 publish 方法则用于发布新的事件,并调用该事件的所有订阅者。

redux 的星号 ES6的generator

在 Redux 相关的代码中,你可能看到了星号*。这个星号通常用在 generator 函数的定义中,表示这是一个 generator 函数。
在 JavaScript 中,generator 是 ES6 引入的一种特殊类型的函数,可以在执行过程中被暂停和恢复。generator 函数的定义语法是在 function 关键字后面添加一个星号,如 function*。

16.proxy和Object.propertyDefine区别

Object.defineProperty 是一个非常强大的工具,允许我们对对象属性进行精细控制,包括其可枚举性、可配置性、可写性以及定义其 getter 和 setter 函数。然而,它也存在一些限制和缺点:

  1. 无法监视整个对象: 使用 Object.defineProperty 只能对单个属性进行操作,如果需要监视整个对象,例如追踪所有属性的变化,那么就需要对每个属性都使用 Object.defineProperty,这可能会非常繁琐。
  2. 不支持数组: Object.defineProperty 无法有效地监视数组的变化。例如,当通过索引直接修改数组或使用数组的 push、pop 等方法时,它无法捕捉到这些变化。
  3. 兼容性问题: Object.defineProperty 在 IE8 及以下版本的浏览器中不被支持。而且,在 IE8 中虽然有 Object.defineProperty,但只能在 DOM 对象上使用,无法在普通的 JavaScript 对象上使用。
  4. 性能问题: 在某些 JavaScript 引擎中,使用 Object.defineProperty 定义的属性可能无法进行优化,这可能导致性能问题。
    这些限制和缺点是在 ECMAScript 6 引入 Proxy 对象的原因之一,Proxy 对象提供了更全面和强大的对象操作拦截能力,可以解决 Object.defineProperty 的一些限制。
    Proxy 对象用于定义在一个对象上的基本操作的自定义行为。这包括属性查找、赋值、枚举、函数调用等等操作。Proxy 可以拦截并自定义这些操作的行为。

17.画三角形

在 CSS 中,我们可以使用 border 属性来创建一个三角形。基本的思路是创建一个元素,将其一侧的边框设置为透明,而另一侧的边框设置为透明或者具有一定的宽度和颜色。
以下是一个创建向上指向的三角形的例子:

.triangle-up {
   
    width: 0;
    height: 0;
    border-left: 50px solid transparent;
    border-right: 50px solid transparent;
    border-bottom: 100px solid red;
}

在这个例子中,三角形的高度由 border-bottom 的宽度决定,而宽度由 border-left 和 border-right 的宽度之和决定。颜色则由 border-bottom 的颜色决定。

18.高度塌陷

在CSS布局中,高度塌陷 (Height Collapse) 是一个常见的问题。它通常发生在当一个元素的子元素都浮动(float)时,该元素的高度会变为0,就像它是空的一样,这就是所谓的“高度塌陷”。

<div style="background-color: lightblue;">
  <div style="float: left; width: 50px; height: 50px; background-color: coral;"></div>
  <div style="float: left; width: 50px; height: 50px; background-color:
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值