5.ES6

11 min

super 原理

父类的 prototype.constructor.call

ES module vs common.js

CJS: this exports module.exports 是一个东西

  • CMJ 是社区标准,ESM 是官方标准
  • CMJ 是使用 API 实现的模块化,ESM 是使用新语法实现的模块化
  • CMJ 仅在 node 环境中支持,ESM 各种环境均支持
  • CMJ 是动态的依赖,同步执行。ESM 既支持动态,也支持静态,动态依赖是异步执行的。
  • CMJ 不支持 Tree Shaking。ESM 支持 Tree Shaking
  • ESM 导入时有符号绑定???,CMJ 只是普通函数调用和赋值:
  • CMJ 导入的值是拷贝,ESM 导入的值是实时绑定

ES6 模块化如何改善代码结构和可维护性?

  1. 明确导入导出
  2. 避免全局命名污染:通过局部作用域限制了变量的可见性
  3. 提高代码复用性
  4. 促进团队协作
  5. 优化加载性能

ES6 模块化如何解析的

https://juejin.cn/post/7166046272300777508#heading-15

let vs const vs var

var 的鸡肋:

  1. 反直觉的函数作用域
  2. 反直觉的变量提升
  3. 自作主张的挂载 var a = 1 // 等同于 window.a = 1

var、let、const 三者区别可以围绕下面五点展开: 三者都有变量提升,只是 let 和 const 提升后,也就是先被创建出来但并未赋值 undefined,有暂时性死区; 暂时性死区就是提升的那一行声明变量的那一行中间的区域,称为暂时性死区,不能访问变量

  • 污染全局:var(会被挂载到全局),window.a, 但是 let 不会,但是二者都可以跨越 script 标签
  • 变量提升: var let const
  • 暂时性死区 temporal die zone:let const
  • 块级作用域{}:let const
  • 重复声明:var
  • 修改声明的变量:var let

promise 和 async/await 的优缺点

Promise 更适合链式调用,可读性较差但灵活;

async/await 更像同步代码的写法,可读性强,语义清晰。

async/await 的原理

async function 一定返回一个 Promise

await 是等待这个 Promise 的结果,暂停当前函数的执行(非阻塞)

遇到 await,函数挂起,JS 引擎把后续代码包装成回调函数,注册到 Promise 的 .then();

当前任务结束,事件循环会在未来某个时间点继续执行这个回调;

错误会被抛出,可以通过 try/catch 捕获。

本质上就是 “暂停 + 回调注册 + 恢复执行”

介绍下垃圾回收

不再需要但还占用内存的叫做垃圾。

主流策略是:标记清除(用的比较多)

  1. 标记空间中的可达值:根开始遍历,可遍历到的是可达的
  2. 回收「不可达」的值所占据的内存
  3. 做内存整理

v8 会使用分代垃圾回收机制来优化垃圾回收: 具体来说:浏览器将数据分为两种,一种是「临时」对象,一种是「长久」对象

  • 长久对象有:生命周期很长的对象,比如全局的 window、DOM、Web API 等等,这类对象称为老生代,慢慢回收,用主回收器

  • 临时对象有:函数内部声明的变量,或者块级作用域中的变量。当函数或者代码块执行结束时,作用域中定义的变量就会被销毁。这类对象称为新生代对象,很快就变得不可访问,会用副回收器

浏览器中不同类型变量的内存释放时机

  1. 栈内存中的变量:基本类型变量:作用域结束自动释放
  2. 堆内存中的变量
    • 局部对象:失去引用时由垃圾回收器释放
    • 全局对象:页面关闭或手动解除引用时释放
  3. 特性情况
    • 闭包变量:闭包对象失去引用时释放
    • 相关变量:元素移除且解除引用时释放

哪些情况会导致内存泄露?如何避免?

  1. 意外的全局变量

    • 使用严格模式 'use strict'
    • 始终使用 let/const 声明变量
    • ESLint 配置 no-undef 规则
  2. 被遗忘的定时器和事件监听器,记得清除

  3. DOM 引用

map/set 和 weakmap/WeakSet

waekxxx 没有 遍历/size/clear 方法,是因为 key 是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。

const cache = new Map();
cache.set(someObject, data);
const cache = new WeakMap();
cache.set(someObject, data);
// 当 someObject 不再被引用时,数据会被自动回收

WeakMap 只接受对象(null 除外)和 Symbol 值作为键名

WeakMap 的键名所指向的对象,不计入垃圾回收机制。

键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。 大佬链接

介绍下迭代器

是一个具有 next() 方法的对象,每次调用返回一个 { value, done } 对象。

setTimeout(fn, 0) 多久执行?

根据 HTML 5 标准,setTimeout 推迟执行的时间,最少是 4 毫秒。如果小于这个值,会被自动增加到 4。这是为了防止多个 setTimeout(f, 0) 语句连续执行,造成性能问题。

介绍一下 Promise 以及用法

Promise 用于避免回调地狱(Callback Hell),提高异步操作的可读性和可维护性

Promise(A).catch(f1).then(f2) ,f1 执行后 f2 会执行吗,为什么

  • 如果 f1 处理了错误并返回一个值,f2 会接收到该值。
  • 如果 f1 抛出错误或返回一个被拒绝的 Promise,f2 不会执行,错误会继续向下传递。

假设我的 promise 里面全部都是同步代码,promise 里面没有做任何异步,不写 pending 是不是也可以?

不行,一个是违反 Promise/A+ 规范,且状态不可逆的特性里,需要对 状态进行 pending 的判断

在 setTimeout 里产生一个 promise,当前的 promise 会在当前次执行掉吗?还是在下一次循环里执行?

settimeout 的整个回调函数先进入宏任务队列,一步一步走

Promise 值穿透是什么

指在 .then() 链中,如果某个 then 没有传入处理函数,Promise 会自动将上一个 resolve 的值原样传递给下一个 then;

Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)

逐步解析(重点在于 .then(…) 中的非函数传入行为,根据 A+ 规范,resolve 里返回非函数,会覆盖为 value=> value, 2 和 Promise.resolve(3) 都不是函数

Promise 的常用方法

  • Promise.resolve: 创建一个状态为“已完成(fulfilled)”的 Promise
  • Promise.reject: 创建一个状态为“已拒绝(rejected)”的 Promise
  • Promise.all: 按序返回成功结果,如果一个失败就立刻失败
  • Promise.allSettled: 按序返回成功/失败结果 [{statue: 'fulfilled',value:'xxx'},{statue:'rejected',reason:'xxx'}]
  • Promise.any: 返回第一个成功,所有都失败,则失败
  • Promise.race: 比赛,谁先执行完就返回谁,无论成功失败
  • Promise.withResolvers(2024 new):暴露其 resolve/reject 方法的写法
    const { promise, resolve, reject } = Promise.withResolvers();
    
    setTimeout(() => {
      resolve("OK");
    }, 1000);
    
    promise.then(console.log); // 1 秒后输出:OK
    
  • Promise.try (2025 new):Promise.try 可接收同步或异步函数,然后做统一的错误处理。这等同于 new Promise(resolve => resolve(f())),相比 Promise.resolve().then(f) 会少浪费一个 tick。

Promise.all 是串行还是并行

Promise.all 会**并发**执行所有传入的 Promise,等待全部完成后再进入 .then(),但按序返回结果 

ECMAScript stage 有哪几个阶段

  • Stage 0 : Strawman(草案,任何人都可以提出想法,非正式提案
  • Stage 1: Proposal(提案), 开始讨论问题、动机、初步 API 设计
  • Stage 2: Draft(草案), 草案文本写入规范,API 设计明确
  • Stage 3: Candidate(候选), 已完成设计,征求实现者(浏览器/Node)反馈 |
  • Stage 4: Finished(完成) , 被采纳为正式标准,进入 ECMAScript 规范

介绍下 Proxy

用于创建一个代理对象,可以拦截和自定义对另一个对象的各种操作,如:读取属性、设置属性、函数调用等。

  • vue2 用 Object.defineProperty(), vue3 用 Proxy;
  • Object.defineProperty() 只能监听已有属性 ,Proxy 可动态监听新增/删除)
  • Object.defineProperty() 在数组响应式支持需要需要重写数组方法,Proxy 原生支持
  • Object.defineProperty() 兼容性更好,支持 IE,proxy 不行,是新语法

介绍下 Reflect

理解成是“更底层、更标准”的 Object 方法集合

  • Reflect.get(obj, key)obj[key] 更一致
  • 搭配 Proxy 更方便,handler 中常用 Reflect 保持行为一致
  • 返回值更统一(true/false), 比如 Reflect.set() 不抛错,而是返回布尔值
  • 替代一些 Object 方法,如 Reflect.ownKeys() 替代 Object.keys() + Object.getOwnPropertySymbols()

介绍下 Symbol

初衷是为了解决对象属性 key 重复的问题, 接收 string | number | undefined;

const a = Symbol()
const a1 = Symbol('key')
const a2 = Symbol(1)
const a3 = Symbol(false) // 报错

如何让两个 Symbol 全等?

`Symbol.for('xxn') === Symbol.for('xxn')` for 方法会去找是否已注册过当前 key,如果有就直接拿来用,不会再创建

如何遍历带有 symbol 的对象?

  • for in 不行
  • Object.keys 不行
  • Object.getOwnProperty 不行
  • Object.getOwnPropertySymbols 可以,但是其他属性读不到
  • Relect.OwnKeys 可以的