前端面试考题合集---js篇✅✅✅

目标

将面试中js常考的题目搞清楚,弄明白。了解js的底层机制与原理,学会分析源码最好能手写出来。

js基础

es6新特性

  • 🚀1. 新增了let和const声明变量

let :具有块级作用域 仅在{}内有效,适用于需要重新赋值的变量。 const :定义常量 且也是块级作用域仅在{}内有效,一旦赋值后不可更改,且必须在声明时初始化。

javascript 复制代码
if (true) {
    var a = 10;   // `var` 具有函数作用域
    let b = 20;   // `let` 具有块级作用域
    const c = 30; // `const` 具有块级作用域
}
console.log(a); // ✅ 输出 10
console.log(b); // ❌ 报错:b is not defined
console.log(c); // ❌ 报错:c is not defined

还有就是var会变量提升到作用域的顶部,并初始化为underfined,let/const虽然也会变量提升,但其不会初始化,会处于暂时性死区中,无法访问。

js 复制代码
console.log(a); // ✅ 输出 undefined
var a = 10;

console.log(b); // ❌ 报错:Cannot access 'b' before initialization
let b = 20;

对比图

  • 🧩 2. 箭头函数 箭头函数:更简洁的函数写法,使用=>定义。
js 复制代码
// 传统函数
function add(a, b) {
    return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出 5

那箭头函数与普通函数的区别呢?

js 复制代码
// 箭头函数 this
const obj = {
  name: 'Alice',
  say: () => {
    console.log(this.name) // undefined (继承全局作用域的 this)
  },
}
obj.say()

// 普通函数 this
const obj = {
  name: 'Alice',
  say: function () {
    console.log(this.name) // "Alice" (this 指向 obj)
  },
}
obj.say()

// 箭头函数 不能作为构造函数
const Person = (name) => {
  this.name = name
}
const p = new Person('Alice') // TypeError: Person is not a constructor

// 普通函数 构造函数
function Person(name) {
  this.name = name
}
const p = new Person('Alice')
console.log(p.name) // "Alice"

// 箭头函数 ...args
const add = (...args) => {
  console.log(args) // [1, 2, 3]
}
add(1, 2, 3)

// 普通函数 arguments
function add() {
  console.log(arguments) // Arguments(3) [1, 2, 3]
}
add(1, 2, 3)

// 箭头函数 不支持 `bind/call/apply`
const obj = {
  value: 42,
}
const arrowFn = () => {
  console.log(this.value)
}
arrowFn.call(obj) // undefined

// 普通函数 支持 `bind/call/apply`
const obj = {
  value: 42,
}
function normalFn() {
  console.log(this.value)
}
normalFn.call(obj) // 42

📦 3. 模板字符串

使用反引号 `````, 支持多行字符串变量插值

js 复制代码
const a = 5;
const b = 10;
const message = `The sum of ${a} and ${b} is ${a + b}.`;
console.log(message); // 输出: The sum of 5 and 10 is 15.

📚 4. 解构赋值 快速提取对象或数组中的数据。

比如数组解构:

js 复制代码
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 输出 1 2 3

对象解构:

ini 复制代码
const person = { name: "Alice", age: 25 };
const { name, age } = person;
console.log(name, age); // 输出 Alice 25

🛠️ 5. ... 扩展运算符

用于展开数组或对象。

js 复制代码
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2); // 输出 [1, 2, 3, 4, 5]

🧪 6. Promise以及async/await promise可以参考我的这篇文章🧠前端面试高频考题---promise,从五个方面搞定它🛠️ 前言 在面试之中关于promise经常被问起!这也许是 - 掘金

async/await下面会专门来讲。

🧱 8. 类

在es6之前js实现面向对象编程主要是通过构造函数加原型,但这样比较繁琐,es6引入了类使其更清晰易懂。

js 复制代码
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hi, my name is ${this.name}`);
    }
}

const p1 = new Person("Alice", 25);
p1.greet(); // Hi, my name is Alice

🚀9. 新增setmap两种新的数据结构 Set 是一种不允许重复值 的数据结构,类似于数组,但不会存储重复值

Map键值对存储结构 ,类似于对象 {},但键可以是任何类型(包括对象、函数)。

JS 原型和原型链

这可以参考我的这篇文章:js原型

JS 继承以及继承有几种方式?

1. 原型链继承

核心思路: 让子类的 prototype 指向父类实例。

javascript 复制代码
function Parent() {
  this.name = 'Parent'
}
Parent.prototype.sayHello = function () {
  console.log('Hello from Parent')
}

function Child() {}
Child.prototype = new Parent() // 继承 Parent
Child.prototype.constructor = Child

const child = new Child()
console.log(child.name) // "Parent"
child.sayHello() // "Hello from Parent"

优点: 父类方法可复用 ❌ 缺点: 1. 共享引用类型属性(如 arr = [] 会被多个实例共享),2. 无法向父类构造函数传参

2. 借用构造函数继承

核心思路: 在子类构造函数中使用 call 继承父类属性。

javascript 复制代码
function Parent(name) {
  this.name = name
}
function Child(name, age) {
  Parent.call(this, name) // 继承 Parent
  this.age = age
}
const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18

优点: 1. 解决原型链继承共享问题,2. 可传参 ❌ 缺点: 无法继承父类原型上的方法

3. 组合继承(原型链 + 构造函数继承,最常用)

核心思路: 结合前两种方式,继承属性用构造函数,继承方法用原型链

javascript 复制代码
function Parent(name) {
  this.name = name
}
Parent.prototype.sayHello = function () {
  console.log('Hello from Parent')
}

function Child(name, age) {
  Parent.call(this, name) // 第 1 次调用 Parent
  this.age = age
}

Child.prototype = new Parent() // 第 2 次调用 Parent
Child.prototype.constructor = Child

const child = new Child('Rain', 18)
console.log(child.name, child.age) // "Rain", 18
child.sayHello() // "Hello from Parent"

优点: 解决了前两种方法的缺陷 ❌ 缺点: 调用两次 Parent 构造函数(一次 call,一次 Object.create())

4. Object.create()和寄生式继承 这两种都是基于原型链继承来的,都差不多。

5.es6的class继承

ES6 引入了 classextends 关键字,使得继承更加简洁和直观。

js 复制代码
class Parent {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类的构造函数
    this.age = age;
  }
}

const child = new Child('Child', 5);
child.sayName(); // 输出: Child
console.log(child.age); // 输出: 5

一般在实际开发中更推荐使用这个。

JS 作用域和作用域链

作用域:由可访问范围分为全局作用域,函数作用域,块级作用域。 作用域链:变量查找机制,从当前作用域逐级向上查找,直到全局作用域。

js 复制代码
var a = 'global'

function outer() {
  var b = 'outer'

  function inner() {
    var c = 'inner'
    console.log(a, b, c) // ✅ global outer inner
  }

  inner()
}

outer()
console.log(b) // ❌ ReferenceError: b is not defined
  • inner() 内部:

    • c 在当前作用域内找到,直接使用。
    • b 当前作用域没有,向上查找 outer() 作用域,找到并使用。
    • a outer() 作用域没有,再向上查找全局作用域,找到并使用。
  • 查找顺序当前作用域 → 父级作用域 → 全局作用域

在es6中引入了let和const块级作用域避免了变量提升,以及闭包利用作用域链保留外部作用域的变量。

JS 闭包

闭包的定义:

闭包是指一个函数能够记住并访问它的词法作用域,即使这个函数在其词法作用域之外执行。换句话说,闭包使得函数可以"捕获"并保持对外部变量的引用,即使创建该函数的外部函数已经执行完毕。

实现闭包的基本步骤

  1. 定义外部函数:首先定义一个外部函数,在这个函数中声明一些局部变量。
  2. 定义内部函数:在外部函数内部定义一个或多个内部函数。这些内部函数可以访问外部函数的局部变量、参数以及整个作用域链。
  3. 返回内部函数:让外部函数返回内部函数(或者包含内部函数的对象)。这样即使外部函数已经执行结束,返回的内部函数仍然可以访问外部函数中的变量,因为它们形成了闭包。
js 复制代码
function createCounter() {
  let count = 0 // 私有变量,外部无法直接访问
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count,
  }
}

const counter = createCounter()
console.log(counter.increment()) // 1
console.log(counter.increment()) // 2
console.log(counter.getCount()) // 2
console.log(counter.count) // undefined(外部无法直接访问)

闭包的缺点:

  1. 可能导致内存泄漏
  • 闭包会持有外部变量的引用,导致变量无法被垃圾回收
  • 解决方案:手动将变量置为 null 或谨慎管理作用域
  1. 滥用闭包可能影响性能
  • 每次调用都会创建新的作用域,影响垃圾回收机制
  • 适度使用,避免不必要的闭包

如何理解 JS 单线程?

什么是js单线程?

JavaScript 是 单线程 的意思是它只有一个线程来执行代码,这意味着它一次只能执行一个任务。所有的 JavaScript 代码,默认情况下,都会按照顺序在同一个线程中依次执行。单线程的特性使得 JavaScript 相比多线程语言在处理并发时有一些限制,但它也有一套机制来处理异步操作,避免阻塞主线程。 为什么要单线程?

JavaScript 的设计目的是为了简化开发,尤其是在浏览器环境中。单线程可以避免多线程带来的复杂性,比如线程同步、资源竞争等问题。为了不让长时间的任务阻塞 UI 渲染,JavaScript 提供了异步编程的机制。

如何处理并发任务?

  1. 事件循环 :JavaScript 使用事件循环来管理异步任务。通过事件循环,JavaScript 可以在任务执行时不中断主线程的执行。异步任务(比如 setTimeoutPromiseXHR 等)会先进入 消息队列 ,当主线程空闲时,再从队列中取出任务执行。
  2. Web APIs :浏览器提供了 Web APIs (如 setTimeoutfetchDOM 等)来处理一些异步操作。这些操作会被交给浏览器的 API 处理,处理完后通过事件循环机制将回调函数推送到消息队列,等待主线程执行。
  3. 异步编程 :通过 setTimeoutPromiseasync/await 等方式,JavaScript 可以非阻塞地处理 I/O 操作,避免卡住整个程序的执行。

什么是异步?异步的意义是什么?异步函数有哪些?异步函数与同步函数的区别?

什么是异步函数:异步(Asynchronous) 是指代码的执行不会阻塞后续操作,而是允许其他任务继续进行。例如,在 JavaScript 中,某些操作(如网络请求、文件读取等)需要较长时间执行,若使用同步方式(同步代码会阻塞后续代码执行),用户体验会变差,而异步可以让程序继续执行其他任务,在操作完成后再处理结果。

异步的意义:

  • 提升性能:异步操作不会阻塞主线程,可以并行执行多个任务,提高程序运行效率。

  • 提高用户体验 :在前端开发中,异步请求(如 fetch)不会卡住页面,用户可以继续交互。

  • 避免程序卡死:如果使用同步操作读取大文件或请求服务器,可能会长时间卡住程序,而异步可以避免这个问题。

  • I/O 处理:异步特别适用于 I/O 密集型任务(如数据库查询、文件读写、网络请求等)。

常见的异步函数:回调函数,promise,async/await,事件监听。

同步函数和异步函数的区别:

promise的定义及其使用

对于promise可以移步我的这篇文章🧠前端面试高频考题---promise,从五个方面搞定它🛠️ 前言 在面试之中关于promise经常被问起!这也许是 - 掘金

async/await

async/await是基于promise的语法糖,优化了代码风格,可以更清晰的编写异步函数,提高可读性。

  • async 关键字:用于声明一个异步函数,返回值始终是 Promise。
  • await 关键字:只能在 async 函数中使用,等待 Promise 解析(resolve)并返回结果,而不会阻塞线程。

Event Loop ,异步执行顺序,宏任务和微任务

可以查看我之前写的这篇文章传送门

总结

js在前端面试中有着很重比例,值得我们去深挖总结,祝各位面试顺利。

相关推荐
Yolo@~24 分钟前
个人网站:基于html、css、js网页开发界面
javascript·css·html
斯~内克26 分钟前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
dr李四维34 分钟前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久40 分钟前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
浪遏1 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
朴拙数科2 小时前
技术长期主义:用本分思维重构JavaScript逆向知识体系(一)Babel、AST、ES6+、ES5、浏览器环境、Node.js环境的关系和处理流程
javascript·重构·es6
拉不动的猪3 小时前
vue与react的简单问答
前端·javascript·面试
牛马baby3 小时前
Java高频面试之并发编程-02
java·开发语言·面试
旭久4 小时前
react+antd封装一个可回车自定义option的select并且与某些内容相互禁用
前端·javascript·react.js