2023年前端校招面试经验总结

644 阅读12分钟

概述

从整个面试体验来看,比较重视一些基础知识的考察。如果有项目,那会对项目进行提问,对于项目一般是问某一块功能如何做的。我将在过程里向大家展示面试的提问流程,针对于面试官的问题,我将在面试题中贴出答案,供大家学习参考。

过程

一面

面试官:首先做个自我介绍

面试官:询问项目功能、职责、团队人数、具体功能实现、怎么定的接口(整体时间占比很大)

面试官:既然你提到了react的不同版本,那请说一下你在使用react的时候版本选择和区别以及原因。

我:我是从16.8之前的版本开始学的react,在16.8版本之前因为没有更新hooks,所以为了保存状态,所以基本都是使用类组件进行开发,但是在16.8版本后加入了hooks,让函数组件能够使用状态,并且react官方也开始提倡使用函数式编程,所以我在开发的过程中都是使用函数组件配合hocks来开发的。

面试官:你列举几个你使用过的几个hooks

面试官:说一下你知道哪些常用的react库

面试官:讲一下 原型链、闭包、常用的数组方法、箭头函数、改变this指向的方法、promise

面试官:Linux常用命令、如何查看端口

面试官:最后做一道场景编程题,类型:数组转树

二面

面试官:先自我介绍

面试官:技术上的难点,如何解决的

面试官:结合vue的源码和原理,讲一下遇到的问题

面试官:讲一下url输入浏览器后的整个过程

面试官:两道编程题,第一道动态规划,第二道快排

面试题

列举React中常见的Hooks

React中常见的Hooks包括useStateuseEffectuseContextuseReduceruseMemouseCallback等,它们提供了对React状态和生命周期的管理,便于我们编写复杂的组件。

  1. useState: 用于在函数组件中添加state状态。useState返回一个数组,其中第一个变量是状态,第二个是用于修改状态的方法,也就是setState
  2. useEffect: 用于在组件挂载或渲染时执行一些副作用操作,例如数据请求、动画等。useEffect可以传入两个参数,第一个参数是需要执行的函数,第二个参数是当需要执行的变量发生改变时才重新执行useEffect。常见用法是在空数组中传入,表示只在组件挂载和卸载时执行一次,类似于类组件的 componentDidMountcomponentWillUnmount生命周期。
  3. useContext: 用来跨组件层级获取和使用React的 context上下文的值。useContext接受一个context对象(React.createContext返回值),返回该上下文的当前值。
  4. useReducer: 用于组件中复杂状态的管理,类似于Redux中的storeuseReducer接受一个reducer函数和初始状态,返回一个数组,包括当前状态和dispatch函数(用于分发action以更新状态),可以将其传递给子组件。
  5. useMemo: 用于性能优化,计算和缓存经常需要更新的计算结果。useMemo接受两个参数,第一个是需要缓存的函数,第二个是需要更新时的依赖数组。只有依赖发生改变时,useMemo才会重新计算结果。
  6. useCallback: 与useMemo类似,使用useCallback包装函数以便在引用相同时不重复计算。useCallback接受与useMemo相同的参数,但返回一个回调函数(通常经常传递给子组件)。

使用这些Hooks,可以简化代码并增加可维护性,同时也增加了组件的灵活性。

原型链

JavaScript的原型链是一种通过对象间的链接关系所构成的链式结构,用于实现基于原型的继承。每个对象都有一个原型对象,即 __proto__ (已被标准化) 或 [[Prototype]],它定义了该对象的属性和方法。如果在当前对象上找不到所需的属性或方法,则引擎会继续搜索其原型对象,直到找到该属性或方法为止(如果没有找到则返回 undefined)。

当对象 A 调用一个方法或访问一个属性时,引擎会首先检查该对象本身是否具有该属性或方法,如果没有,它将查找对象 A 的原型对象是否具有该属性或方法。如果仍然找不到,引擎将继续搜索该原型对象的原型对象,直到找到该属性或方法为止。

由于每个对象都具有一个原型对象,所以原型对象也可以具有另一个原型对象,因此形成了原型链。最终的原型对象是 null,即 Object.prototype 的原型对象。这就是 JavaScript 中所有对象的基础,因为 Object.prototype 上定义了 JavaScript 中的基本方法和属性。

使用 JavaScript 的原型链可以实现继承和属性覆盖等操作,可以显著地降低对象的内存使用,并创建更为灵活和可扩展的代码。例如,在创建一个继承自另一个对象的新对象时,可以通过设置新对象的原型对象来使其继承原对象的属性和方法。

原型链上的每个对象都有自己的属性和方法,并继承其原型对象的属性和方法。这种继承模型使得 JavaScript 可以实现更为灵活的面向对象编程模型。

闭包

JavaScript 的闭包是指,在一个函数内部创建另一个函数,这个内部函数可以访问到父函数作用域内的变量和参数,即使在父函数执行结束后,这个内部函数依然可以访问这些变量和参数。这种特性是因为 JavaScript 采用了词法作用域的方式,即函数的执行环境在函数定义的时候就已经确定好了,这个执行环境包括函数自身的变量和形参,以及父执行环境,也就是创建该函数的函数的变量和形参。

闭包可以用来保护变量或数据结构,以避免其被其他外部代码所修改,同时也可以使得代码更加模块化。例如,可以使用闭包来创建私有变量和方法,以避免变量冲突或被其他代码篡改,也可以用多次调用同一个函数来临时保存变量的状态等。

以下是一个简单的闭包例子:

function outerFunction() {
  var outerValue = 'outer';
​
  function innerFunction() {
    console.log(outerValue);
  }
​
  return innerFunction;
}
​
var inner = outerFunction();
inner(); // "outer"

在上面的例子中, innerFunction 是被 outerFunction 创建的一个闭包。它可以访问 outerFunction 作用域中的变量 outerValue,即使 outerFunction 已经执行完毕并且其执行环境已被销毁。

闭包可能会导致内存泄漏问题,即在不再需要访问闭包内部变量时依然占用着内存,因此需要注意内存回收问题。闭包也应该谨慎使用,避免出现意料之外的副作用。

常用的数组方法

JavaScript中常用的数组方法:

  1. pushpop push 方法将一个或多个元素添加到数组末尾,并返回新数组长度

    pop()方法从数组末尾删除一个或多个元素,并返回删除的元素。

  2. shiftunshift shift方法从数组开头删除一个元素,并返回删除的元素;

    unshift 方法在数组开头添加一个或多个元素,并返回新数组长度。

  3. splice用于在数组中插入新元素、删除元素或替换元素。该方法会修改原数组,并返回被修改的元素组成的新数组。

  4. slice用于创建一个新数组,包含原数组从开始位置到结束位置(不包括结束位置)的所有元素。原数组不会被修改。

  5. concat 用于合并两个或多个数组,并返回合并后的新数组。

  6. join将数组中所有元素转换为字符串,并返回一个字符串。可以指定一个分隔符,将字符串根据分隔符分隔。

  7. indexOflastIndexOf``indexOf方法用于在数组中查找一个指定的元素,并返回第一次出现的索引位置;

    lastIndexOf方法用于在数组中从末尾向前查找一个指定的元素,并返回最后一次出现的索引位置。

  8. forEach对数组中的每个元素执行一次指定的函数,返回undefined,不会修改原数组。

  9. map创建一个新数组,其中的元素是由原数组中的每个元素调用一个指定的函数后返回的结果组成。

  10. filter将返回符合过滤条件的数组元素组成的新数组。

  11. reduce将数组中的元素缩减为单个值。该方法通过使用一个函数“归纳”一对值(将其拉倒一个值)来实现。

箭头函数特点

  1. 箭头函数的 this 指向的是其声明时所在上下文的 this,而非使用时所在上下文的 this
  2. 如果箭头函数是在全局作用域中声明的,那么 this 指向全局对象 window
  3. 箭头函数无法通过 bind()call()apply() 方法来改变 this 指向,因为箭头函数的 this 已经被绑定到了函数自身。

改变this指向的方法

call()apply() 方法

call()apply() 方法都可以用来调用函数,并且可以改变函数内部的 this 指向。它们的区别在于传递参数的方式不同。call() 方法需要将参数一个一个地传入,而 apply() 方法需要将参数封装成一个数组作为第二个参数传入。

bind() 方法

bind() 方法可以用来创建一个新的函数,并且可以将其中的 this 指向指定的对象。bind() 方法返回的是一个新函数,原函数并没有被修改。

promise

Promise 是一种处理异步操作的对象,可以将异步操作以同步的方式表达出来,避免了回调地狱(callback hell)的问题,使异步代码更加可读、可维护、可复用。

Promise 对象有三种状态: pendingresolvedrejected,分别代表未完成、已完成和已拒绝。Promise 对象在创建时会立即执行,然后通过 then() 方法指定成功的回调函数和失败的回调函数。

const promise = new Promise((resolve, reject) => {
  // 异步操作
  // 如果成功,调用 resolve,参数为结果值
  // 如果失败,调用 reject,参数为失败原因
});
​
promise.then(result => {
  // 成功的回调函数
}).catch(error => {
  // 失败的回调函数
});

在执行异步操作时,如果操作成功则通过 resolve() 方法将操作结果返回,如果失败则通过 reject() 方法将失败原因返回。通过 then() 方法可以指定成功的回调函数,通过 catch() 方法可以指定失败的回调函数。

Promise.race() 方法接收一个包含多个 Promise 对象的数组,如果其中的任意一个 Promise 对象解决(resolved)或拒绝(rejected),则返回该 Promise 对象的值或拒因。Promise.race() 方法返回的 Promise 对象的状态与第一个解决或拒绝的 Promise 对象的状态一致。

promise.all方法的源码实现

Promise.all()是一个静态方法,它会接收一个可迭代对象,并在里面执行一组 promises,这些 promises 并行执行。它的返回值是一个 promise,将在所有 promises 都完成时完成。

Promise.all = function (promises) {
  return new Promise(function(resolve, reject) {
    var results = [];
    var count = promises.length;
    promises.forEach(function(promise, index) {
      promise.then(function(result) {
        results[index] = result;
        if (--count === 0) {
          resolve(results);
        }
      }).catch(function(error) {
        reject(error);
      });
    });
  });
};

函数将会接受一个数组对象(或任何带有可迭代接口的对象)作为参数。在 Promise.all 的构造函数中,我们定义了一个空数组,用来存储所有 promises 返回的内容。我们维护了一个数量变量,以确保我们在所有 promises 完成之后重新解析结果。对于每个promise,我们执行的操作都是相同的。

当 promise 完成时,它将把结果存储在结果数组中。每次 promise 完成时,我们将计数器减 1。如果计数器变为 0,我们就知道所有 promises 都已经完成并且我们可以解析返回值数组。如果任何 promise 拒绝了,则将拒绝的值传播给新的 promise。

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
​
Promise.all([p1, p2, p3]).then(function(values) {
  console.log(values); // [1, 2, 3]
});

Linux的常用命令

  1. ls:列出当前目录下文件和目录的名称。
  2. cd:切换目录。
  3. pwd:显示当前目录的绝对路径。
  4. mkdir:创建目录。
  5. touch:创建文件。
  6. cat:显示文件内容。
  7. cp:复制文件或目录。
  8. mv:移动或重命名文件或目录。
  9. rm:删除文件或目录。
  10. find:查找文件或目录。
  11. grep:查找文件中指定的字符串。
  12. tar:打包压缩文件。
  13. unzip:解压缩文件。
  14. top:实时显示系统中各个进程资源占用情况。
  15. ps:查看当前系统中进程的状况。
  16. kill:杀死指定进程。
  17. ping:测试网络连接。
  18. ifconfig:查看网络接口信息。
  19. ssh:通过安全加密的方式远程登录到另一台计算机。
  20. scp:在计算机之间安全地复制文件。