JavaScript面试题笔记

一、基础语法类(必问)

1. JavaScript 的数据类型有哪些?基本类型和引用类型的区别?

解答

  • 数据类型分类

    • 基本类型(值类型):StringNumberBooleanNullUndefinedSymbol(ES6)、BigInt(ES10);
    • 引用类型:Object(包含 ArrayFunctionDateRegExp 等)。
  • 核心区别

    维度 基本类型 引用类型
    存储位置 栈内存(栈区) 堆内存(堆区),栈存引用地址
    赋值方式 赋值 "值本身"(值传递) 赋值 "引用地址"(引用传递)
    可变性 不可变(比如 let str = 'a'; str[0] = 'b' 无效) 可变(比如 let arr = [1]; arr[0] = 2 生效)
    比较方式 比较 "值是否相等" 比较 "地址是否相等"(比如 {} === {}false
2.null和undefined的区别:

undefined 是声明了变量但未赋值,表示未定义。

null 是手动赋值为空,表示空值,是一个空对象。

原型链的终端就是null空对象。

2. 什么是 NaN?如何判断一个值是否是 NaN?
  • NaN(Not a Number):表示 "非数字",但类型是 Numbertypeof NaN === 'number');
  • 特性:NaN !== NaN(唯一不等于自身的值);

总结:判断一个值能不能当数字用,不能就会返回true,能,返回false

3. 说说 ===== 的区别?

解答

  • ===(严格相等):值 + 类型 都相等才返回 true(无隐式类型转换);
  • ==(松散相等):先做隐式类型转换,再比较值是否相等;
javascript 复制代码
0 == ''; // true(都转成 0)
null == undefined; // true(特殊规则)
[] == false; // true([] 转成 0,false 转成 0)
{} == '[object Object]'; // true({} 转成字符串 "[object Object]")

// 严格相等无坑
0 === ''; // false
null === undefined; // false

开发建议:优先用 ===,避免隐式转换导致的意外结果。

4.数据类型的判断

typeof判基本类型,instanceof判对象、数组。

5.this指向问题
  • 普通函数调用 → this 指向 window
  • 对象方法调用 → this 指向 这个对象
  • 构造函数 new → this 指向 新创建的实例对象
  • 箭头函数没有自己的 this ,指向外层作用域的 this

3 个改变this指向的方法:

  • call
  • apply
  • bind

二、作用域与闭包(重点)

1. 什么是作用域?JS 有哪些作用域?

解答

  • 作用域:变量 / 函数的可访问范围,控制变量的可见性和生命周期,避免命名冲突;
  • 分类:
    1. 全局作用域 :在代码任何地方可访问(比如 window 上的变量),页面关闭才销毁;
    2. 函数作用域:仅在函数内部可访问(ES5 仅函数有作用域);
    3. 块级作用域 :ES6 新增,由 let/const + {} 产生(if/for/while 等块内),块外不可访问;
2. 什么是闭包?闭包的用途和缺点?

闭包定义 :函数嵌套时,内部函数引用外部函数的变量 / 函数,导致外部函数执行完后,其作用域不会被销毁,内部函数依然能访问这些变量(核心:保留外部作用域的变量);

javascript 复制代码
function outer() {
  let num = 0; // 外部函数变量
  // 内部函数引用外部变量,形成闭包
  return function inner() {
    num++;
    console.log(num);
  };
}
const fn = outer();
fn(); // 1(outer 执行完,但 num 未销毁)
fn(); // 2
  • 用途
    1. 封装私有变量(比如模拟类的私有属性);
    2. 缓存数据(比如计算结果缓存,避免重复计算);
    3. 防抖 / 节流函数的实现;
  • 缺点
    • 闭包会保留外部作用域的变量,导致变量无法被垃圾回收,内存占用增加,滥用可能导致内存泄漏;
    • 解决:不用时手动解除引用(fn = null)。
3. 什么是变量提升?var/let/const 的区别?

变量提升 :JS 引擎在执行代码前,会把 var 声明的变量和函数声明 "提升" 到当前作用域顶部(赋值不会提升);

变量提升只提升变量的声明,不提升赋值,声明前使用的话会得到undefined,而不会报错。

const/let也有变量提升,但是不让用,不能像var一样提前用,所以一般说let和const没有变量提升。

javascript 复制代码
console.log(a); // undefined(var 提升,未赋值)
var a = 1;

fn(); // 1(函数声明整体提升)
function fn() {
  console.log(1);
}

总结:var有变量提升,而,const和let没有变量提升。

特性 var let const
变量提升 有(提升后 undefined) 有(暂时性死区) 有(暂时性死区)
块级作用域
重复声明 允许 不允许 不允许
赋值 可多次赋值 可多次赋值 必须初始化,不可修改引用(基本类型不可改,引用类型可改属性)

暂时性死区let/const 声明的变量,在声明前访问会报错(比如 console.log(b); let b = 2; 报错),避免变量提升的坑。

4.作用域,闭包的用途

函数嵌套函数,内部函数访问外部函数变量

私有化变量、模块化、缓存数据。

三、异步编程(高频)

1. JS 为什么是单线程?异步的解决方案有哪些?

解答

  • 单线程原因:JS 最初设计用于浏览器交互(比如 DOM 操作),如果多线程同时操作 DOM 会导致冲突(比如一个线程加样式,一个线程删节点),所以设计为单线程;
  • 异步解决方案(演进)
    1. 回调函数 :基础(比如 setTimeout、AJAX 回调),缺点:回调地狱(多层嵌套);
    2. Promise :解决回调地狱,支持链式调用(then/catch),三种状态:pending(进行中)、fulfilled(成功)、rejected(失败);
    3. async/await:ES8 新增,Promise 的语法糖,用同步写法实现异步,更易读;
  • 示例(Promise 解决回调地狱):
javascript 复制代码
// 回调地狱(多层嵌套)
setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
  }, 1000);
}, 1000);

// Promise 链式调用
new Promise(resolve => {
  setTimeout(() => {
    console.log(1);
    resolve();
  }, 1000);
}).then(() => {
  setTimeout(() => {
    console.log(2);
  }, 1000);
});

// async/await(更简洁)
async function fn() {
  await new Promise(resolve => setTimeout(() => {
    console.log(1);
    resolve();
  }, 1000));
  await new Promise(resolve => setTimeout(() => {
    console.log(2);
    resolve();
  }, 1000));
}
fn();
2. Promise 的常用方法有哪些?(all/race/allSettled)

解答

  • Promise.all([p1, p2, p3])
    • 所有 Promise 都成功才返回成功结果数组;
    • 只要有一个失败,立即返回失败结果;
    • 场景:并行请求多个接口,全部成功后再处理(比如同时加载图片 + 数据)。
  • Promise.race([p1, p2, p3])
    • 只要有一个 Promise 完成(成功 / 失败),就返回该结果;
    • 场景:请求超时处理(比如接口 5 秒没返回就提示超时)。
  • Promise.allSettled([p1, p2, p3])
    • 所有 Promise 都完成(无论成功 / 失败),返回所有结果(包含状态和值);
    • 场景:需要知道所有请求的结果(比如批量提交表单,不管成功失败都要统计)。
3. 什么是事件循环(Event Loop)?宏任务和微任务的区别?

事件循环:JS 单线程处理异步任务的一套执行机制,核心流程:

  1. 执行同步代码(主线程);
  2. 遇到异步任务,分 "宏任务" 和 "微任务" 加入对应队列;
  3. 同步代码执行完后,先清空所有微任务队列
  4. 执行一次宏任务,再清空所有微任务队列;
  5. 重复步骤 3-4(循环)。
  • 宏任务(Macrotask)
    • 常见:setTimeoutsetIntervalDOM 事件AJAX 请求script 整体代码
  • 微任务(Microtask)
    • 常见:Promise.then/catch/finallyasync/await(本质是 Promise)、queueMicrotask

四、原型与继承(核心原理)

1. 什么是原型和原型链?

解答

  • 原型(prototype)

    • 每个函数(除箭头函数)都有 prototype 属性(原型对象),包含该函数实例共享的属性 / 方法;
    • 每个实例对象都有 __proto__ 属性,指向其构造函数的 prototype
  • 原型链

    • 当访问对象的属性 / 方法时,先找自身,找不到就通过 __proto__ 找构造函数的原型,再找不到就找原型的原型,直到 Object.prototype(顶端),找不到则返回 undefined
  • 示例:

    javascript 复制代码
    function Person(name) {
      this.name = name;
    }
    Person.prototype.sayHi = function() {
      console.log(`Hi, ${this.name}`);
    };
    const p = new Person('张三');
    p.sayHi(); // Hi, 张三(p 自身没有 sayHi,通过 __proto__ 找到 Person.prototype)
    p.toString(); // 找到 Object.prototype.toString(原型链顶端)
2. ES6 的 class 继承和 ES5 的原型继承有什么区别?

ES5 原型继承 :通过修改 __proto__prototype 实现,代码繁琐,易出错;

ES6 class 继承

  • 语法糖(底层还是原型),更简洁,接近传统面向对象;
  • class 定义类,extends 继承,super() 调用父类构造函数;

核心区别:ES6 class 更规范、易读,避免了 ES5 手动修改原型的坑(比如忘记修正 constructor

五、DOM/BOM 操作(实战)

1. 如何获取 / 操作 DOM 元素?常用的 DOM 操作方法有哪些?

解答

获取 DOM 元素
javascript 复制代码
// 常用方法
document.getElementById('id'); // 通过 ID(唯一)
document.getElementsByClassName('class'); // 通过类名(伪数组)
document.getElementsByTagName('div'); // 通过标签名(伪数组)
document.querySelector('.class'); // 通过选择器(返回第一个)
document.querySelectorAll('#id'); // 通过选择器(伪数组)
  • 操作 DOM 元素
  1. 修改内容:element.innerText(纯文本)、element.innerHTML(可解析 HTML);
  2. 修改样式:element.style.color = 'red'(行内样式)、element.classList.add('active')(类名);
2. 事件委托(事件代理)是什么?有什么好处?

解答

  • 事件委托 :利用事件冒泡 ,把子元素的事件绑定到父元素上,通过 event.target 判断触发事件的子元素;
  • 好处
  • 减少事件绑定数量,提升性能(比如 1000 个 li 只绑定 1 个事件);
  • 新增子元素无需重新绑定事件(动态渲染的列表尤其适用)。

六、性能优化(加分项)

1. JS 常用的性能优化手段有哪些?

解答

  • 代码层面
    1. 减少不必要的 DOM 操作(DOM 操作是性能瓶颈,缓存 DOM 节点);
    2. 避免频繁重排 / 重绘(比如批量修改样式,先隐藏元素再修改);
    3. 防抖(debounce)/ 节流(throttle)处理高频事件(比如 resize、scroll、输入框输入);
  • 加载层面
  • 懒加载(图片、组件、JS 文件):可视区域内再加载;
  • 代码分割(Code Splitting):把大文件拆分成小文件,按需加载;
  • 缓存(localStorage/sessionStorage 缓存数据,避免重复请求)。
2. 什么是内存泄漏?JS 中常见的内存泄漏场景?
  • 内存泄漏:不再使用的变量 / 对象无法被垃圾回收,持续占用内存,导致页面卡顿、崩溃;
  • 常见场景
    1. 未清理的定时器 / 事件监听(比如 setInterval 未清除,DOM 元素删除后事件未解绑);
    2. 闭包滥用(保留不必要的变量);
    3. 全局变量(比如 a = 1 未声明,挂载到 window 上,不会被回收);
    4. 未清理的 DOM 引用(比如 let div = document.getElementById('div'); div.remove();,但变量 div 仍引用该节点);
  • 解决:不用的变量手动赋值 null,清除定时器 / 事件监听。
相关推荐
计算机学姐2 小时前
基于SpringBoot的宠物诊所管理系统
java·vue.js·spring boot·后端·spring·elementui·宠物
2501_940315262 小时前
【无标题】1302 层数最深叶子节点的和
java·数据结构·算法
悟能不能悟2 小时前
idea默认的快捷键和eclipse配置快捷键对比,列出一些常用的
java·eclipse·intellij-idea
晨晖22 小时前
java容器类的博客
java·开发语言
bug攻城狮2 小时前
Spring Boot项目启动时输出PID、CPU和内存信息的4种方法
java·spring boot·后端·logback
MegaDataFlowers2 小时前
Maven
java·maven
朱一头zcy2 小时前
Java基础复习03:面向对象基础入门(类与对象的概念 构造器 this关键字)
java·笔记
牧天白衣.2 小时前
02-基础语法
java
dawudayudaxue2 小时前
Eclipse安卓环境配置
android·java·eclipse