当前位置: 首页 > news >正文

【前端面试】JS篇

作用域与作用域链

1. 什么是作用域?

  • 作用域就是变量或函数生效的范围。
  • 在 JS 中,每个函数都有自己的作用域。
  • 通常有三种作用域:全局作用域、函数作用域、块级作用域。
  • ES6 之后,letconst 引入了块级作用域。

2. 什么是作用域链?

当我们在函数里访问变量时,JS 引擎会先在当前作用域查找,找不到就去上一层作用域,一直查到全局。 这条从里到外的 “查找路径” 就是作用域链。

3. JS 的作用域是怎么确定的?

JS 使用的是词法作用域,作用域在定义函数时就确定,不会因为调用位置而改变。

4. 函数执行时是怎么查找变量的?

每个函数执行时会创建一个“执行上下文”,其中保存了当前作用域中的变量。
当访问变量时,JS 引擎会先看当前上下文有没有,没有就沿着作用域链往外查找,直到全局。
所以变量查找是从内向外

5. 闭包和作用域链有什么关系?

闭包本质上就是作用域链的延伸。当一个内部函数在外部被引用时,它会“保留”创建时的作用域链,从而可以访问外层函数的变量。所以闭包其实是作用域链被延长到函数外部的结果

6. 块级作用域和函数作用域的区别是什么?

函数作用域是由 function 定义的,每次调用都会生成新的作用域实例。
而块级作用域是由 {} 定义的,比如 iffor 或用 letconst 声明的变量。
块级作用域在 ES6 才引入,用于解决变量提升带来的问题。

7. 变量提升和作用域的关系是什么?

在作用域创建阶段,JS 会先扫描声明,把函数声明和 var 声明提升到作用域顶部。
所以虽然变量能提前访问,但值是 undefined,这是因为声明提升但赋值没提升。
letconst 不会被初始化,存在“暂时性死区”。

8. 典型代码例子

var a = 1;
function outer() {var b = 2;function inner() {var c = 3;console.log(a, b, c);}inner();
}
outer();// 当执行 inner() 时,能访问到 a、b、c 三个变量
// [inner作用域] → [outer作用域] → [全局作用域]

变量提升

1. 什么是变量提升?

变量提升是指 在代码执行前,JS 引擎会先扫描当前作用域,把变量和函数声明提升到作用域顶部,但不会提升赋值。

2. var、let、const 的区别?

  • var 有函数作用域、会变量提升;
  • let 和 const 有块级作用域,也会被提升,但存在暂时性死区,不能在声明前使用;
  • const 声明的变量必须初始化且不能重新赋值。

3. 什么是暂时性死区(TDZ)?

暂时性死区是指在块级作用域中,从作用域开始到变量声明语句执行前的这段时间内,该变量不可访问。

4. 函数声明和函数表达式的提升区别?

函数声明会被整体提升,包括函数体
函数表达式只会提升变量名,不会提升函数体。

sayHi(); // ✅ OK
function sayHi() { console.log('hi'); }sayHello(); // ❌ TypeError: sayHello is not a function
var sayHello = function() { console.log('hello'); };

5. 典型陷阱题(输出题)

// 题目一
console.log(a);
var a = 1;
console.log(b);
let b = 2;// undefined
// ReferceError
// 输出解释:var 声明的变量会被提升并初始化为 undefined,let / const 会被提升但存在暂时性死区(TDZ),在声明前访问会报错。// 题目二
var a = 1;
{console.log(a);let a = 2;
}// ReferenceError(TDZ)
// 输出解释:因为在块作用域中,let a 会在编译阶段就“屏蔽”外层同名变量。在 TDZ 期间,任何对 a 的访问都会抛错,不会沿作用域链查找外层的 a。

闭包

1. 什么是闭包?

闭包就是函数记住它定义时的外层作用域
当内部函数被返回或传给外部使用时,它还能访问定义它时的外层变量。

2. 为什么闭包可以访问外层变量?

因为 JS 函数在创建时会形成作用域链,内部函数持有对外层作用域的引用,即使外层函数执行完,变量也不会被销毁。

3. 闭包有什么用?

  • 封装私有变量(模块化、计数器)
  • 保存状态(异步回调、事件处理)
  • 实现函数式技巧(防抖、节流、柯里化)

4. 举个闭包的例子

封装私有变量的计数器

function counter() {let count = 0;return function() {count++;return count;};
}
const c = counter();
c(); // 1
c(); // 2

5. 闭包会造成内存泄漏吗?

闭包本身不会自动泄漏内存,但它会让外层变量长时间驻留在内存中。如果引用了不再需要的对象,且闭包还在被使用,这部分内存就无法回收。使用完闭包后,可手动清空引用(比如置 null),或者确保不保留不必要的闭包引用。

6. 闭包和作用域链有什么关系?

闭包其实就是作用域链延长到函数外部的结果。
内部函数持有外层作用域引用,这条链让函数在外部依然能访问外层变量。

7. 闭包在循环里常见什么问题?

所有闭包共享同一个循环变量,导致闭包内部访问的值都是最后一次循环的结果。

for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 0);
}
// 输出:3, 3, 3
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 0);
}
// 输出:1, 2, 3

this 指向

1. 什么是 this

this 是函数执行时的上下文对象,指向函数运行时所属的对象,而不是函数定义时所属的对象。

2. this 是在什么时候绑定的?

this 的绑定是在函数执行时动态确定的,不同调用方式会导致不同指向。

3. 四种基本调用方式 this 指向

  • 普通函数调用 → 指向全局对象(浏览器中是 window,严格模式下是 undefined)
  • 对象方法调用 → 指向调用该方法的对象
  • 构造函数调用(new) → 指向新创建的实例对象
  • 显式绑定(call / apply / bind) → 指向传入的对象

4. 箭头函数的 this 怎么绑定?

箭头函数没有自己的 this,它会继承定义时外层作用域的 this

5. 在事件处理函数里,this 指向谁?

普通 DOM 事件处理函数中,this 指向触发事件的元素。
如果使用箭头函数,则继承定义时的外层 this。

6. bind、call、apply 的区别?

  • call: 立即执行函数,并指定 this,参数逐个传入
  • apply: 立即执行函数,并指定 this,参数以数组形式传入
  • bind: 返回一个新函数,绑定指定 this,不立即执行

7. bind 可以再改变 this 吗?

bind 一旦绑定 this,就不可再修改,哪怕使用 call/apply 也不会改变绑定的 this。


原型与原型链

在这里插入图片描述

1. 什么是原型?

原型是一个对象,它允许其他对象从它那里继承属性和方法。

2. 原型和构造函数的 prototype 有什么关系?

构造函数的 prototype 指向将来实例的原型对象,实例通过 __proto__ 链接到构造函数的 prototype

3. 什么是原型链?

原型链是对象通过 prototype 属性逐级连接的一条链,用于查找属性或方法。
当访问对象属性时,如果对象本身没有,会沿着原型链向上查找,直到 null 为止。

4. 原型链查找失败会发生什么?

如果沿链都找不到,访问结果返回 undefined;方法调用会报错。

5. New 的实现机制

new 运算符用于创建一个新对象实例,并完成以下四步:

  1. 创建一个新对象
  2. 将新对象的proto指向构造函数的 prototype
  3. 将构造函数内部的 this 指向新对象,执行构造函数代码
  4. 如果构造函数返回的是对象类型,则返回该对象;否则返回新对象
function myNew(Fn, ...args) {// 1. 创建新对象const obj = {};// 2. 关联原型obj.__proto__ = Fn.prototype;// 3. 执行构造函数const result = Fn.apply(obj, args);// 4. 返回对象return typeof result === 'object' && result !== null ? result : obj;
}

6. 原型链与作用域链有什么区别?

  • 原型链用于查找对象的属性和方法,运行时动态查找
  • 作用域链用于查找变量,编译阶段根据词法作用域确定

7. 构造函数和实例的原型关系?

每个实例通过 __proto__ 访问构造函数的 prototype 上的方法,实现方法共享。

8. 如何判断一个属性是实例自身的还是继承的?

  • obj.hasOwnProperty('prop') → 判断是否为自身属性
  • 如果返回 false,说明该属性是通过原型链继承的

9. 修改实例的 __proto__ 会改变原型链

改变实例的原型链,属性查找路径随之改变,只影响当前实例,不影响构造函数或其他实例。

10. 原型方法共享导致引用类型属性被所有实例共享

原型上的引用类型属性被所有实例共享,修改会影响所有实例,生产中建议放到构造函数内部。


异步机制(事件循环)

1. 什么是事件循环?

JS 是单线程语言,通过事件循环机制实现异步执行。
当主线程执行同步代码时,异步任务(宏任务、微任务)会被放入任务队列,主线程空闲后依次执行。

2. JS 真的只有一个线程吗?

JS 只有一个主线程执行 JS 代码,但浏览器或 Node 内部可以多线程处理 I/O、定时器等,执行完毕后通过事件循环回到主线程。

3. JS 的任务队列分哪几类?

  • 宏任务 :setTimeout、setInterval、setImmediate、I/O 等
  • 微任务 :Promise.then / catch / finally、process.nextTick(Node)、MutationObserver

4. 宏任务和微任务有什么执行顺序?

每次事件循环中先执行一个宏任务,然后执行该宏任务产生的所有微任务,再进行下一轮宏任务。

5. 宏任务和微任务执行顺序的经典例子

当有同步代码 + Promise.then + setTimeout 时,执行顺序是:

  1. 先执行同步代码
  2. 再执行微任务队列(Promise.then)
  3. 最后执行下一轮宏任务(setTimeout)

6. async/await 与 Promise.then 的执行顺序?

await 后面的表达式会被封装成微任务,和 Promise.then 在同一队列中。
举例:在宏任务里执行 await 会产生微任务,执行顺序和 Promise.then 相同。

7. 事件循环中微任务可能阻塞宏任务吗?

会。如果微任务不断产生新微任务(如 Promise.then 中再生成 Promise.then),当前宏任务会一直被阻塞,下一轮宏任务永远无法执行。 这是微任务可能造成“饥饿宏任务”的问题。


异步编程模型(async/await)

1. async/await 是什么?

async/await 是基于 Promise 的语法糖,用于更直观地书写异步代码。

  • async 声明函数,保证函数返回一个 Promise
  • await 暂停函数执行,等待 Promise 完成,然后返回结果

2. async 函数返回的是什么?

总是返回一个 Promise,如果函数内部 return 一个普通值,等价于 Promise.resolve(value)

3. await 的本质是什么?

await的本质是暂停当前async函数的执行,等待右侧表达式(通常是 Promise)的状态变为fulfilledrejected,并将结果作为返回值,同时将await之后的代码包装成微任务加入队列。

4. await 会阻塞主线程吗?

不会,async 函数暂停的是自身的执行(函数内部),主线程仍然可以继续处理其他任务。

5. 多个 await 的顺序问题

多个 await 是串行执行


Promise

1. 什么是 Promise?

Promise 是“用于处理异步操作的对象”,其核心是通过状态管理(pending → fulfilled/rejected)解决回调地狱问题。

2. Promise.then / catch 的执行时机?

Promise 的回调是微任务,当前宏任务执行完之后立即执行,优先于下一轮宏任务。

3. 多个 Promise.then 执行顺序?

链式调用的 then 会按注册顺序执行,前一个 then 的返回值会作为后一个 then 的参数。

4. Promise.all 和 Promise.race 的区别?

  • Promise.all([p1, p2]) → 所有 Promise 完成后返回结果,任意失败则立即 reject
  • Promise.race([p1, p2]) → 谁先完成就返回谁的结果(成功或失败)

5. Promise.resolve / Promise.reject 有什么作用?

  • Promise.resolve(value) → 返回一个立即 resolve 的 Promise
  • Promise.reject(reason) → 返回一个立即 reject 的 Promise
    可用于统一异步处理和链式调用

6. Promise 链式调用是怎么实现的?

每个 then 都会返回一个新的 Promise 对象,新 Promise 的状态由前一个 then 的回调返回值决定,从而实现链式传递。

7. then/catch 返回值为非 Promise 会发生什么?

会被自动包装成已 resolve 的 Promise,传给下一个 then。


深拷贝 / 浅拷贝

1. 什么是浅拷贝?

浅拷贝只复制对象的第一层属性,如果属性是引用类型(对象、数组),拷贝的只是引用地址。
改变原对象中的引用值,会影响拷贝对象。

2. JS 中有哪些浅拷贝方式?

  • Object.assign()
  • 展开运算符 { ...obj }
  • Array.prototype.slice() / concat()
    都只会拷贝一层。

3. 什么是深拷贝?

深拷贝会递归复制对象的所有层级,使新对象完全独立于原对象。
修改原对象不会影响拷贝对象。

4. 深拷贝有哪些常见实现?

  • 简单对象可用 JSON.parse(JSON.stringify(obj)
  • 复杂对象(包含函数、日期、循环引用等)需用递归或库(如 lodash.cloneDeep)

5. JSON.parse(JSON.stringify(obj)) 有什么缺点?

  • 无法拷贝函数、undefined、Symbol
  • 会丢失原型链
  • 无法处理循环引用
  • Date 会被转为字符串

6. 深拷贝与浅拷贝的根本区别?

浅拷贝复制引用地址(同一块堆内存),深拷贝创建新对象(不同内存空间)。
判断标准是修改原对象是否会影响拷贝对象。

7. lodash 的 cloneDeep 是如何实现的?

它通过递归遍历对象的每个属性,处理各种类型(对象、数组、Map、Set、Date 等),并用 WeakMap 解决循环引用。


类型判断与转换

1. JS 有哪些数据类型

  • 基本数据类型(栈内存): Number、BigInt(es2020)、String、Boolean、Undefined、Null、Symbol(es6)
  • 引用数据类型(堆内存): Object

2. Symbol 和 BigInt 解决了什么问题?

  • Symbol:解决对象属性名冲突问题;
  • BigInt:解决 Number 超过 2^53 - 1 的精度丢失问题。

3. symbol 的使用场景

Symbol 用于创建唯一且不可枚举的标识符,常用于防冲突、私有化和自定义对象行为

  1. 避免对象属性名冲突
  2. 定义私有属性 / 内部方法
  3. 定义常量枚举值
  4. 作为唯一标识

4. typeof 能判断哪些类型?有哪些局限?

typeof 可以判断基本类型(number、string、boolean、undefined、symbol、bigint)和 function。
但它无法区分 null 与对象,且对数组、对象、正则等都返回 'object'

5. 为什么 typeof null === 'object'

因为早期 JS 用低位标识类型,null 的二进制标识全为 0,被错误识别为 object 类型的标签,属于历史遗留问题。

6. 如何判断数组?

Array.isArray() 最准确,也可用 Object.prototype.toString.call(arr) === '[object Array]'

7. instanceof 的原理是什么?

instanceof 运算符的原理是检查一个对象的原型链上是否存在指定构造函数的 prototype 属性。

8. 什么时候会发生隐式类型转换?

主要在四种场景:

  1. 字符串拼接(+
  2. 比较运算符(==
  3. 逻辑运算(&&||
  4. 模板字符串插值

9. 为什么 [] == ![]true

  • ![]false
  • [] 转为基本类型 ""
  • "" == false → 都转数字 0 → 相等。

10. 为什么 [] + {}{} + [] 结果不同?

  • [] + {}'[object Object]'(空数组转字符串 + 对象转字符串)
  • {} + []0(被当作代码块 + 数组转数字)

11. Number()parseInt()parseFloat() 有何区别?

  • Number():整体转数值(空字符串 →0,null→0undefined→NaN
  • parseInt():从左到右解析整数(遇到非数字停止)
  • parseFloat():解析浮点数。

防抖与节流

1. 什么是防抖(debounce)?

防抖是指在事件频繁触发时,只在最后一次触发后的一段时间才执行函数。
如果在等待时间内事件又触发,就重新计时。

常见应用:搜索框输入联想、窗口 resize、input 校验等。

2. 什么是节流(throttle)?

节流是指在高频触发事件中,让函数固定时间间隔内最多执行一次。

常见应用:滚动监听、鼠标移动、窗口拖拽等。

3. 防抖和节流为什么能优化性能?

因为它们减少了函数在短时间内的频繁执行,避免了高频 DOM 操作或计算,降低浏览器负担,提高页面流畅度。

4. 使用防抖/节流有哪些注意点?

  • 注意 this 和参数传递(需用箭头函数或 apply 绑定)。
  • 在 React/Vue 中使用要注意组件卸载时清除定时器。
  • 如果是防抖,建议配置“立即执行”选项(leading / trailing)。

执行上下文与调用栈

1. 什么是执行上下文?

执行上下文是 JS 代码在运行时的环境,每当函数被调用时,都会创建一个新的执行上下文,用来存储该函数的变量、作用域链、this 指向等信息。

2. 执行上下文有哪几种?

  • 全局执行上下文:页面加载时创建,只有一个。
  • 函数执行上下文:每次函数调用都会创建一个新的。
  • Eval 执行上下文:很少使用,一般忽略。

3. 执行上下文的创建过程是什么?

执行上下文创建分两步:

  1. 创建阶段:确定变量、函数声明、this,形成词法环境和变量环境。
  2. 执行阶段:执行代码,变量赋值、函数调用等正式运行。

4. 什么是调用栈?

调用栈是 JS 引擎用来管理执行上下文的一种结构(栈结构:后进先出)。
每当函数被调用时,其上下文会被压入栈顶,执行完后再弹出。

5. 为什么会出现栈溢出

当函数无限递归或调用层级过深时,新的执行上下文不断入栈,而栈空间有限,最终导致栈溢出错误。

6. 执行上下文和作用域链的关系是什么?

执行上下文包含作用域链。
当查找变量时,会从当前上下文的词法环境开始,一层层往外查找父级词法环境,直到全局为止。

垃圾回收机制(GC)

1. 什么是垃圾回收?

垃圾回收是 JS 引擎自动释放不再被引用的内存的过程。
也就是说,当一个对象不再被任何引用指向时,它就会被判定为“可回收”,随后被 GC 机制清除。

2. JS 中常见的垃圾回收算法有哪些?

最常见的两种算法:

  • 标记清除 → 主流算法
    • 从根对象(如全局对象 window)出发,标记所有可达对象;未被标记的对象被回收。
  • 引用计数 → 早期使用
    • 记录每个对象被引用的次数,引用数为 0 时回收。
    • 引用计数容易出现“循环引用”无法释放的问题,因为两个对象互相引用时,引用计数永远不为 0。

3. V8 引擎的垃圾回收机制是怎样的?

V8 采用 分代式垃圾回收

  • 新生代:存活时间短的小对象,使用 Scavenge 算法(复制 + 清理)
  • 老生代:存活时间长的对象,使用 标记清除 + 标记整理 + 增量标记算法(是 V8 优化策略,把标记过程拆成多次小任务,避免阻塞主线程)

4. 什么是内存泄漏?

内存泄漏是指某些对象不再需要,但仍然被引用着,导致 GC 无法回收,从而浪费内存。

5. 常见的内存泄漏场景?

  • 全局变量未释放
  • 闭包使用不当
  • DOM 元素引用未清除
  • 定时器(setInterval)未清除
  • 缓存对象持续增长(如 Map、WeakMap 不当使用)

6. 如何排查内存泄漏?

  • Chrome DevTools → Performance / Memory
    • 录制 Heap Snapshot → 查看 retained size
  • 观察内存曲线
    如果多次操作后内存不下降,就是泄漏。

7. 如何避免或优化 GC?

  • 尽量减少全局变量
  • 使用完 DOM 要及时移除引用
  • WeakMap / WeakSet 存储临时引用
  • 清除定时器和监听器
  • 避免滥用闭包

8. 为什么 WeakMap 不会引发内存泄漏?

因为它的键是弱引用,键对象一旦不可达就会被 GC 自动回收。

模块化机制

1. 说说 JS 模块化的发展历程?

早期 JS 没有模块系统,只能靠全局变量和 IIFE(立即执行函数)来组织代码。
后面社区出现:

  • CommonJS(Node.js)——同步加载,用 requiremodule.exports
  • AMD(浏览器)——异步加载,用 definerequire
  • CMD(SeaJS)——按需加载,用 define(function(require, exports, module){})
  • ES Module (ESM) —— 原生模块化标准,用 importexport,支持静态分析和 Tree Shaking

2. 为什么 CommonJS 不适合浏览器?

因为它是同步加载模块的,而浏览器加载脚本是网络请求,无法保证同步完成。

3. AMD 和 CMD 区别?

  • AMD(RequireJS):依赖前置,提前加载所有依赖
  • CMD(SeaJS):依赖就近,按需加载

4. CommonJS 与 ESModule 区别

对比点CommonJSESModule
加载方式同步异步(编译阶段确定依赖)
导出内容值拷贝(导出时就确定值)值引用(实时绑定)
运行时机运行时加载编译时静态分析
语法require / module.exportsimport / export
Tree Shaking不支持支持
环境Node.js浏览器 & Node.js (ESM 模式)

5. 什么是 ESM“实时绑定”?

ESModule 导出的变量与原始模块的变量引用相同,原模块更新值时,导入方看到的也是最新的。

6. 为什么 ESM 能做 Tree Shaking?

因为它是静态结构,编译时就能知道哪些变量没被用到,可以在打包时剔除。

7. Node.js 模块加载机制

  1. 缓存优先:先检查 require.cache,若有缓存直接返回;
  2. 内置模块:如 fspath,优先级最高;
  3. 文件模块:按绝对路径 / 相对路径查找(补全 .js/.json/.node 后缀);
  4. 第三方模块:查找当前目录 node_modules,若不存在则递归向上查找父目录的 node_modules,直到根目录或找到模块。

8. 浏览器里如何使用 ES Module?

  • 使用 <script type="module">
  • 自动启用严格模式
  • 默认延迟执行(类似 defer
  • 只能通过 同源或 CORS 方式加载模块

9. import()require 区别?

对比维度requireimport()
加载方式同步(阻塞)异步(非阻塞,返回 Promise)
加载时机运行时运行时(但路径需符合静态规则)
返回值module.exports的值拷贝模块命名空间对象(实时绑定)
适用规范CommonJSESM
典型场景Node.js 同步加载按需加载、异步场景、浏览器 ESM

事件模型与捕获冒泡

1. DOM 事件流的三个阶段是什么?

  • 捕获阶段(从 window → 目标元素)
  • 目标阶段(事件到达目标元素本身)
  • 冒泡阶段(从目标元素 → window 反向传播)

2. 哪个阶段先触发?

捕获先于冒泡,但事件默认只在冒泡阶段触发(除非显式设置第三个参数为 true)。

3. 哪些事件不会冒泡?

blurfocusmouseentermouseleave 不会冒泡。

4. 事件捕获和事件冒泡的区别?

对比项捕获(capture)冒泡(bubble)
方向从外到内从内到外
监听方式addEventListener(type, fn, true)默认 false
默认执行阶段不默认触发默认触发
应用场景事件委托前置判断、埋点常规事件绑定、委托处理

5. 同一个元素上既注册捕获又注册冒泡,执行顺序?

执行顺序是:先捕获 → 后冒泡。

6. 什么是事件委托(事件代理)?

事件委托是利用事件冒泡,将子元素事件交由父元素监听和处理的机制。
比如列表点击,用父节点代理所有子项点击事件。

优点:

  • 减少事件绑定数量
  • 动态新增子元素仍能响应
  • 提高性能(尤其是大量节点)

7. 怎么阻止事件冒泡?怎么阻止默认行为?

event.stopPropagation() // 阻止冒泡
event.preventDefault()  // 阻止默认行为(如表单提交)

8. event.targetevent.currentTarget 的区别?

  • event.target触发事件的具体元素(事件源);
  • event.currentTarget当前正在处理事件的元素(即绑定事件监听的元素),不一定是父元素(可能是自身)。

9. 事件监听中 this 指向谁?

普通函数中,this 指向绑定事件的元素(等价于 event.currentTarget)。
若用箭头函数,则 this 指向定义时的上层作用域,不指向 DOM。

10. 实际开发中捕获阶段什么时候有用?

  • 全局埋点:在捕获阶段尽早监听点击/输入行为
  • 阻止某些冒泡逻辑:如 modal 遮罩层
  • 与第三方组件库冲突时,优先处理事件

11. addEventListener 默认是捕获还是冒泡?

默认是冒泡。

addEventListener 第三个参数默认为 false 代表事件冒泡,若为 true 则执行事件捕获行为。

深浅比较(== vs ===)

1. == 的比较过程是什么样的?

  1. 如果类型相同,直接比较值。
  2. 如果是 nullundefined,相等。
  3. 如果是数字和字符串,先把字符串转成数字。
  4. 如果是布尔值,先把布尔转成数字(true→1, false→0)。
  5. 如果是对象和基本类型,先对对象调用 valueOf()toString() 转成基本类型再比较。

2. 常见易错表达式及结果

表达式结果原因
null == undefined✅ true特殊规则
null === undefined❌ false类型不同
[] == ![]✅ true![]false0[]0
[] == 0✅ true转换后都是 0
[1] == 1✅ true[1]'1' → 1
{} == {}❌ false引用类型不同地址
NaN == NaN❌ falseNaN 不等于任何值,包括自身
0 == false✅ truefalse → 0
' \t\n' == 0✅ true空白字符串转数字为 0

3. Object.is()=== 有什么区别?

两者几乎相同,但在这两个特殊情况不同:

  • Object.is(+0, -0) → false(区分正负 0)
  • Object.is(NaN, NaN) → true(认为 NaN 相等)

setTimeout、Promise、Async/Await 的区别

  1. 执行优先级:同步代码 → Promise.then/catch(微任务) → setTimeout(宏任务);
  2. 异步流程控制:setTimeout 是回调式异步,Promise 是链式异步(解决回调地狱),async/await 是同步写法的异步(更简洁);
  3. 错误处理:setTimeout 回调错误需内部捕获,Promise 用 .catch (),async/await 用 try/catch。

for…in 和 for…of 用法

  • for…in 遍历对象可枚举属性(包括原型链),for…in 遍历 “键名”
  • for…of 遍历可迭代对象(数组、字符串、Map、Set 等),for…of 遍历 “键值”
    追问: 数组用 for…in 有什么问题?
    答: 会遍历索引字符串,甚至包括手动添加的属性,不推荐。

普通函数和箭头函数的区别

对比项普通函数箭头函数
this 指向调用时动态绑定(谁调用指向谁)定义时静态绑定外层作用域的 this
arguments有自己的 arguments没有,继承外层作用域的 arguments
prototype有 prototype没有 prototype
构造函数可作为构造函数使用(可 new)不能作为构造函数(不能 new)
语法相对冗长更简洁,常用于回调函数
适用场景普通方法、构造函数回调、函数式编程场景

Ajax 工作原理

  • 通过 XMLHttpRequestfetch 向服务器发送异步请求,不刷新页面即可获取数据。
    流程:创建对象 → open() → send() → 监听 readyState。
  • 流程图

在这里插入图片描述

数组的常用方法

不会改变原数组

方法作用
map()返回新数组,对每个元素执行回调
filter()过滤符合条件的元素
reduce()累积计算(如求和、扁平化)
concat()合并数组
slice()截取数组的一部分
find() / findIndex()查找元素或下标
includes()是否包含某值
join()转字符串
flat() / flatMap()扁平化数组

会改变原数组

方法作用
push() / pop()尾部增删
shift() / unshift()头部增删
splice()增删改指定位置元素
sort()排序(默认按字符串)
reverse()反转

对类数组对象的理解,如何转为数组

  • 类数组: 具有 length 和索引属性但无数组原型方法的对象(如 arguments、NodeList)。
  • 转换方法
  1. 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
  1. 扩展运算符,需类数组实现迭代器
[...arr]
  1. 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
  1. 通过Array.from方法来实现转换
Array.from(arrayLike);

为什么 0.1 + 0.2 !== 0.3,如何让其相等

  • 原因:浮点数二进制精度问题导致存储误差
  • 解决方案
  1. 将其转为整数,再相加转回小数
  2. 使用 toFixed 方法,只保留一位小数点

substring 和 substr 的区别

  • substring(start, end):第二个参数是结束索引(不含)
  • substr(start, length):第二个参数是长度(已被弃用,是早期的非标准方法)

异步编程的发展历程

  1. 回调函数(Callback):最早的方式,容易产生“回调地狱”。
  2. Promise:提供链式调用和状态管理,解决回调嵌套问题。
  3. Generator + co:通过 yield 暂停异步流程,写同步风格的异步代码。
  4. async/await:Promise 的语法糖,写起来像同步,最直观。

JS 实现继承的七种方式

  • 原型链继承

    • 概念:子类的原型指向父类实例,继承父类属性和方法。
    • 优点:简单,实现原型方法共享,内存占用少。
    • 缺点
      • 不能向父类构造函数传参
      • 引用类型属性会被所有实例共享
      • 无法实现多继承
  • 借用构造函数(经典构造函数继承)

    • 概念:在子类构造函数中调用父类构造函数,用 callapply 绑定 this
    • 优点
      • 可以向父类构造函数传参
      • 每个实例都有自己的属性,不会共享引用类型
    • 缺点
      • 方法必须在构造函数中定义,无法复用(每个实例都创建一份)
      • 无法继承父类原型方法
  • 组合继承(原型链 + 构造函数) → 最常用

    • 概念:结合原型链和构造函数继承,既继承属性又继承方法。
    • 优点
      • 可以向父类构造函数传参
      • 方法共享在原型上,实例不会重复创建
      • 避免引用类型共享问题
    • 缺点
      • 调用了两次父类构造函数(一次给实例属性,一次给原型)
      • 相比寄生组合继承稍微低效
  • ES6 class extends

    • 概念:ES6 新语法,使用 extendssuper() 实现继承。
    • 优点
      • 语法清晰,面向对象风格明显
      • 自动处理原型链和构造函数调用
      • 方法天然在原型上共享
    • 缺点
      • 语法糖,底层仍是组合继承的原理
      • 不能完全解决多继承问题(需要 mixin)
  • 寄生组合继承 → ES5 最优方案

  • Object.create

  • 复制继承(浅拷贝/深拷贝继承属性)

ajax、axios 和 fetch 的区别

特性Ajax(XHR)AxiosFetch
底层XMLHttpRequest封装 XHR原生 Promise API
返回值回调PromisePromise
发送 JSON需手动序列化自动序列化手动 JSON.stringify
浏览器兼容性老旧浏览器支持同 XHRIE 不支持,需要 polyfill
请求取消需要手动内置 cancel tokenAbortController

Set 和 Map 的区别

特性SetMap
数据结构值的集合(不重复)键值对集合
键类型只能存值本身,唯一键可以是任意类型(对象、函数)
遍历顺序按插入顺序按插入顺序
典型应用去重数组对象映射、缓存

Map 和 Object 的区别

特性ObjectMap
键类型字符串或 Symbol任意类型(对象、函数等)
默认键值对数量不固定可以直接通过 size 获取
遍历for…in(要过滤原型属性)map.forEach / for…of
性能小量对象快大量键值对快

map() 和 forEach() 的区别

  1. 返回值

    • map():会返回一个新数组(由回调函数的返回值组成)。
    • forEach()没有返回值(返回 undefined)。
  2. 用途区别

    • map():用于转换数组(需要结果)。
    • forEach():用于遍历执行操作(只做副作用,如打印、修改外部变量)。
  3. 是否可链式调用

    • map():返回新数组 → 可继续 .filter().reduce() 等链式操作。
    • forEach():返回 undefined不能链式调用
  4. 是否可中断循环

    • 两者都不能用 break / return 提前跳出。若需中断,用普通 for / for...of
  5. 性能差异

    • 基本相似,map()略慢(因需创建新数组)。

一句总结:
map() 用于生成新数组forEach() 用于遍历执行副作用,功能相似但用途不同。

set 和 weakSet 区别

特性SetWeakSet
元素类型任意值只能是对象
是否可遍历可遍历不可遍历
是否阻止垃圾回收会阻止不阻止(弱引用)

map 和 weakMap 的区别

特性MapWeakMap
键类型任意类型只能是对象
是否可遍历可遍历不可遍历
是否阻止垃圾回收会阻止不阻止(弱引用)

JS 中取整的方法

  • Math.floor():向下取整
  • Math.ceil():向上取整
  • Math.round():四舍五入
  • Math.trunc():去掉小数部分
  • 位运算:|0~~(只对 32 位有效)
  • parseInt():把字符串转整数(要注意非数字字符会截断)

ES6 新特性

  • 块级作用域letconst
  • 模板字符串`Hello ${name}`
  • 解构赋值const {a, b} = obj;
  • 箭头函数
  • 默认参数
  • 扩展运算符...(数组、对象、参数)
  • Promise
  • 模块化import / export
  • Class 语法糖
  • Symbol

设计模式

设计模式概念前端实例
单例模式确保一个类只有一个实例,并提供全局访问全局状态管理对象(Redux store、Vuex store)、全局配置对象
工厂模式通过统一接口创建不同类型对象,隐藏具体实现创建不同类型的图表、表单控件或组件
观察者模式对象状态变化时,自动通知依赖它的所有对象Vue 响应式数据、数据绑定、跨组件事件监听
发布-订阅模式事件驱动,发送者和接收者解耦,通过事件频道通信Node.js 的事件模块、浏览器自定义事件、组件间事件通信
装饰器模式在不修改原对象的基础上,动态扩展对象功能React 高阶组件(HOC)、ES7 装饰器给类或方法增加功能

RAF 和 RIC 是什么?

RAF 用于动画,保证和浏览器刷新率同步;RIC 用于后台空闲任务,不阻塞渲染,二者都是浏览器性能优化手段。

RAF(requestAnimationFrame)

  1. 作用
    • 浏览器提供的 动画渲染优化 API
    • 在下一次浏览器重绘之前执行回调函数
  2. 特点
    • 回调执行频率与屏幕刷新率同步(一般 60FPS)
    • 浏览器空闲时才执行,节省 CPU
    • 浏览器切换到后台标签页时暂停,减少无用计算
  3. 应用场景
    • 页面动画、Canvas / WebGL 渲染、滚动动画

RIC(requestIdleCallback)

  1. 作用
    • 浏览器提供的 空闲时间回调 API
    • 当主线程空闲时执行回调,不影响关键渲染
  2. 特点
    • 可指定 timeout ,保证在一定时间内执行
    • 优先级低,适合非关键任务
  3. 应用场景
    • 批量处理非紧急任务、预加载、日志上报

http://www.dtcms.com/a/601524.html

相关文章:

  • 网站模板怎么用法企业做pc网站需要什么资料
  • 简单医院网站wordpress xiu 5.5
  • APP上架应用市场全解析:安卓商店与苹果App Store流程及资质要求
  • ECS 事件监控钉钉通知优化实践
  • 2025年ChatGPT Plus、Pro、Business用户Codex使用限制详解(附Codex额度查询入口)
  • Android垃圾回收算法详解
  • wordpress做管理网站百度网盟有哪些网站
  • 东莞企业网站哪家好平顶山网站建设电话
  • 【开题答辩全过程】以 基于Vue的列车信息查询系统为例,包含答辩的问题和答案
  • AXI-5.5 Memory protection and the Realm Management Extension
  • 用c++求第n个质数
  • 三合一网站建站如何在工商局网站上做网登
  • 网工_存储技术
  • PostIn从初级到进阶(1) - 创建第一个项目
  • 深入理解 C++ 类型转换:从 C 语言兼容到 C++ 增强特性
  • 网站营销的优势哪个网站做音基的题不花钱
  • 织梦教育咨询企业网站模板wordpress手机文章列表
  • 模电基础:深度负反馈的放大倍数估算
  • 代码随想录算法训练营第 34 天 | 01 背包理论基础 - 二维数组、01 背包理论基础 - 一维数组、416. 分割等和子集
  • 滚珠导轨使用中的维护禁忌与正确做法
  • 上海做网站公司排名WordPress 类型 网页
  • [AI tradingOS] AI决策引擎 | decision/engine.go | 交易哲学prompts
  • 网站推广营销策略公司的网站怎么做
  • docker run hello-world失败、报错
  • 多媒体消息支持 - 全面提升系统对文字、图片、视频、文件和语音的处理能力
  • 重庆建设厅的网站首页o2o网站源码app
  • 2018年临沂建设局网站越秀seo搜索引擎优化
  • C++系列之刷题系列(树)
  • 07-ES分布式搜索引擎高级
  • NVIDIA Orin NX使用Jetpack安装CUDA、cuDNN、TensorRT、VPI时的error及解决方法