5.ES6
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 模块化如何改善代码结构和可维护性?
- 明确导入导出
- 避免全局命名污染:通过局部作用域限制了变量的可见性
- 提高代码复用性
- 促进团队协作
- 优化加载性能
ES6 模块化如何解析的
https://juejin.cn/post/7166046272300777508#heading-15
let vs const vs var
var 的鸡肋:
- 反直觉的函数作用域
- 反直觉的变量提升
- 自作主张的挂载
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 捕获。
本质上就是 “暂停 + 回调注册 + 恢复执行”
介绍下垃圾回收
不再需要但还占用内存的叫做垃圾。
主流策略是:标记清除(用的比较多)
- 标记空间中的可达值:根开始遍历,可遍历到的是可达的
- 回收「不可达」的值所占据的内存
- 做内存整理
v8 会使用分代垃圾回收机制来优化垃圾回收: 具体来说:浏览器将数据分为两种,一种是「临时」对象,一种是「长久」对象
长久对象有:生命周期很长的对象,比如全局的 window、DOM、Web API 等等,这类对象称为老生代,慢慢回收,用主回收器
临时对象有:函数内部声明的变量,或者块级作用域中的变量。当函数或者代码块执行结束时,作用域中定义的变量就会被销毁。这类对象称为新生代对象,很快就变得不可访问,会用副回收器
浏览器中不同类型变量的内存释放时机
- 栈内存中的变量:基本类型变量:作用域结束自动释放
- 堆内存中的变量
- 局部对象:失去引用时由垃圾回收器释放
- 全局对象:页面关闭或手动解除引用时释放
- 特性情况
- 闭包变量:闭包对象失去引用时释放
- 相关变量:元素移除且解除引用时释放
哪些情况会导致内存泄露?如何避免?
意外的全局变量
- 使用严格模式
'use strict' - 始终使用
let/const声明变量 - ESLint 配置
no-undef规则
- 使用严格模式
被遗忘的定时器和事件监听器,记得清除
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 可以的