前端内容-ES6
1、common.js和es6中模块引入的区别
-
Common.js是一种模块系统,主要用于node.j。使用require函数引入模块,使用
module.exports
导出模块特点:
- 动态导入,require可以在函数体中,条件语句动态引入模块
- 同步加载:require是同步的,模块在执行require时会立即加载并返回结果
- 导出的是值的拷贝
-
ES6模块:
ES6模块系统是ECMAScript标准的一部分,使用import和export进行导入和导出模块
export const name = 'shanghai'
特点:
- 静态导入,在文件的顶部定义,在编译时确定依赖关系
- 异步加载
- 导出的是值的引用,这就意味着值发生改变后,所有的引用处发生反映
兼容性和转换:使用Babel或者Webpack等可以将ES6转为Common.js
2、箭头函数
- 箭头函数和普通函数的区别
使用=>来定义箭头函数,省去了function关键字,采取=>定义函数,函数的参数写在=>前的括号,函数体写在后面花括号
区别:
-
语法上来看,箭头函数更简洁、清晰,很便捷
-
箭头函数不会创建自己的this,它是捕获自己定义时所处的外层执行环境的this,并继承这个值。
-
箭头函数继承而来的this指向永远不会改变
-
.call()\.apply()\.bind()
不会改变箭头函数的this指向虽然这些方法可以动态的修改函数执行时的this指向,但是箭头函数的this指向在定义时就已经确认了
-
箭头函数不能作为构造函数使用
构造函数的new做了什么
- JS内部生成一个对象,继承构造函数的原型对象:
Object.create(constructor.prototype)
- 将函数中的this指向该对象
- 执行构造函数中的语句
- 返回该对象的实例
由于箭头函数的this指向在定义时就已经确定,并且无法改变,因此this指向无法随在哪里调用、被谁调用而改变,因此箭头函数不能作为构造函数使用
- JS内部生成一个对象,继承构造函数的原型对象:
-
箭头函数没有自己的
arguments
arguments
对象是一个函数内部的类数组的对象,作用存储函数调用时传入的所有参数。 -
箭头函数没有原型
prototype
-
箭头函数不能用作
Generator
函数,不能使用yeild关键字Generator
函数是一种特殊的函数,function* 语法定义,可以暂停和恢复程序执行,返回一个迭代器对象yield
是 Generator 函数中的关键字,用于暂停函数执行并返回一个值。每次调用迭代器的next()
方法时,函数会从上次暂停的位置继续执行。 -
适合回调函数,避免普通函数的this丢失问题(不再用 var that = this 或者bind)
- 箭头函数的this指向问题
箭头函数没有属于自己的this,它所谓的this是指捕获其所在上下文的this值,作为自己的this,它没有自己独立的this上下文
从作用域机制来看,普通函数的this是在运行时动态绑定的,谁调用函数,this就指向谁;而箭头函数他是在定义时静态绑定的,继承外部词法作用域的this,并不会改变的
从babel转译来看,转译后用闭包(变量 var _this
= this )保存当前作用域的this,箭头函数被转为普通函数,就直接使用闭包保存的_this
- new一个箭头函数会怎么样
报错:TypeError: ArrowFunction is not a constructor
核心原因:
- 没有自己的this,无法通过new绑定到新创建的实例上
- 没有[construct]内部方法,用于创建对象实例这个方法
- 不能使用
arguments
、super
3、var\const\let
区别
-
var
在ES5中,顶层对象和全局对象是等价的,var声明的变量既是全局变量也是顶层变量
使用var声明的变量存在变量提升的现象,就是在编译阶段将其提升到顶部
使用var,能够对一个变量多次声明并且后面会覆盖前面的
函数中使用var声明变量,该变量是局部
-
le
ES6新增的,存在块级作用域,在let命令所在的代码块中有效
不存在变量提升,未声明直接使用会报错(
ReferenceError
),使用let声明变量前,该变量都不可用(暂时性死区)let不允许在相同作用域重复声明
-
const
声明一个只读变量,一旦声明,常量的值就不可变了,实际上是保证变量指向的内存地址所保存的数据不可改动
- 区别
-
变量提升:
-
var声明的变量存在变量提升,值为undefined
-
let和const声明的变量不存在变量提升,即声明的变量一定要在声明后使用,否则报错
这种说法也不全对,只能说暂时性死区使得我们感受不到变量提升的效果,会被提升只是不会被初始化、被引用
从变量赋值的过程来看,创建变量时会在内部中开辟空间,然后初始化,再赋值,但是let不会进行初始化
-
-
暂时性死区
var不存在,let和const存在
-
块级作用域
var不存在,let和const存在
-
重复声明
var允许,且后声明的会覆盖前面声明的,const和let在同一个作用域不允许重复声明
-
修改声明的变量
var和let可以修改,const声明一个只读的常量不可以进行修改
-
使用
优先使用const,其次let,避免var
4、Symbol作用
主要解决ES5中属性名都是字符串导致的属性名冲突问题,保证每个属性名都是独一无二的
Symbol
:表示独一无二的值,继undefined、null、boolean、string、Number、Object
、BigInt
:表示任意精度的整数,ECMAScript2020中引入
通过Symbol函数生成,对象属性名有两种:字符串、Symbol类型
5、ES6新特性
主要有四大类
-
解决原有语法的不足
-
对原有语法进行增强
解构、展开、参数默认值、模板字符串
-
全新的对象、方法、功能
promise、proxy、object的assign、is
-
全新的数据类型和结构
symbol、set、map
6、Map
和Set
用法和区别
- Map
一组键值对[‘key’, ‘value’]的值,和JSON对象类似,其中key不仅可以是字符串也可以是对象
-
常用语法
let map = new Map(); // 添加 map.set('key', 'value') // 是否存在key map.has('key') // 根据key获取value map.get('key') // 删除 map.delete('key')
-
一个key只能对应一个value,后面的值会覆盖前面的值
- Set
类似于数组、且成员的值都是唯一的,打印出来的是一个对象
-
最常用来去重
var arr = [1,2,4,4,3,3,2,5]; var arr2 = [...new Set(arr)] // [1,2,4,3,5]
-
常用语法
初始化需要一个Array数组或者空set // 添加 set.add(); // 删除 set.delete() // 检查是否包含 set.has();
区别
- 查找速度:set更快(0.005ms)map(0.007ms)、array(0.012ms)
- 初始化需要的值不一样:Map需要一个二维数组、Set需要一维数组
- Map和Set都不允许值重复
- Map的键不能修改,但是键对应的值可以修改;Set不能通过迭代器修改值,因为其值就是键
- Map是键值对的存在,值也不能作为键;而Set没有value只有key,value就是key
7、Map
和WeakMap
WeakMap
是ES6新增的一种集合类型、“弱映射”;只能将对象作为键(null除外)
强引用:
在 JavaScript 中,当一个变量直接指向一个对象时,就创建了一个强引用。只要有强引用指向对象,垃圾回收机制就不会回收该对象占用的内存 。
弱引用:
与强引用不同,弱引用不会阻止对象被垃圾回收机制回收 ,即使存在对对象的弱引用,但只要没有其他强引用指向该对象,垃圾回收器就可以随时回收该对象占用的内存。在 JavaScript 中,可以通过 WeakMap
和 WeakSet
来创建弱引用关系,另外,在 ES2018 引入了 WeakRef
类来更直接地创建和管理弱引用。
- 不可遍历:
WeakMap
对键名引用是弱引用,内部成员是会取决于垃圾回收机制有没有执行,前后成员个数可能会不一致。而垃圾回收机制的执行又是不可预测的,因此不可遍历
应用场景:防止内存泄漏(循环引用)、缓存场景
8、属性的5种遍历方法
for..in
:循环遍历对象的自身和继承的可枚举属性(不含Symbol)Object.keys
:返回一个数组,包括对象自身的(不含继承)所有可枚举属性的键名Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。- Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名
- Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
9、Reflect
对象的作用
Reflect
是ES6引入的内置静态对象,用于提供JS对象的元编程操作。将语言内部的元操作封装成静态方法。
- 目的:
- 将 Object 对象的一些明显属于语言内部的方法(比如
Object.defineProperty
),放到 Reflect 对象上,未来的新方法将只部署在 Reflect 对象上 - 修改某些 Object 方法的返回结果,使其更加合理
- 让 Object 操作都变成函数行为。某些 Object 操作是命令式的,如
name in obj
和delete obj[name]
,而 Reflect 提供了对应的函数Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
- Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这使得 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础
- 静态方法
方法 功能描述 典型应用场景 关键特性 Reflect.get(target, key[, receiver])
获取对象属性值,支持指定接收者( receiver
用于继承场景)1. 代理对象属性访问(如添加日志、权限校验) 2. 安全获取嵌套属性(避免 undefined
报错) 3. 实现响应式数据的依赖收集- 可配合 Proxy
拦截属性读取 - 支持继承链中的属性访问(通过receiver
)Reflect.set(target, key, value[, receiver])
设置对象属性值,返回布尔值表示成功与否 1. 属性赋值验证(如类型检查、范围限制) 2. 响应式系统(如 Vue 3 追踪属性变化) 3. 数据绑定与更新 - 操作失败返回 false
(而非报错) - 支持拦截并修改赋值行为Reflect.apply(target, thisArg, args)
调用函数并指定 this
和参数列表,类似Function.apply
1. 函数调用装饰器(如日志、性能监控) 2. 动态函数执行(根据参数动态调用) 3. 代理函数调用 - 比 Function.apply
更安全(避免this
指向异常) - 与Proxy.apply
配合拦截函数调用Reflect.construct(target, args[, newTarget])
以 new
方式创建实例,支持指定原型(newTarget
)1. 工厂模式(动态选择构造函数) 2. 定制继承链(修改实例原型) 3. 类的代理与扩展 - 替代 new
操作符的函数式写法 - 支持创建跨原型的实例Reflect.has(target, key)
判断对象是否包含属性(含原型链),等价于 in
操作符1. 隐藏私有属性(如拦截 in
检查) 2. 安全属性存在性判断(避免误判undefined
) 3. 权限控制(限制属性可见性)- 与 Proxy.has
配合拦截in
操作 - 明确区分 “不存在” 和 “值为undefined
”Reflect.deleteProperty(target, key)
删除对象属性,返回布尔值表示成功与否 1. 阻止删除关键属性(如配置项、只读属性) 2. 不可变数据(禁止修改原始对象) 3. 动态清理数据 - 替代 delete
操作符的函数式写法 - 操作失败返回false
(如删除不可配置属性)
- 和Object区别
-
Object
是早期 JS 中对象操作的 “全能工具”,包含构造函数、原型管理、属性定义等命令式方法(如Object.keys
、Object.defineProperty
)。Reflect
是 ES6 为元编程设计的函数式 API,将对象操作(如in
、delete
)转为函数调用,且方法与Proxy
拦截器一一对应。
- 返回值与错误处理:
Object
方法失败时通常直接抛出错误(如Object.defineProperty
修改不可配置属性时)。Reflect
方法操作失败时返回false
(如Reflect.set
赋值失败),更便于通过布尔值判断结果,无需 try/catch。
- 风格与场景:
Object
偏向 “对象管理”(如创建、扩展、序列化),适合常规开发。Reflect
偏向 “操作拦截与默认行为执行”,是元编程的核心工具(如配合Proxy
实现自定义逻辑)
- 与proxy
协作关系
Proxy
用于拦截对象操作(如属性访问、函数调用),通过handler
定义自定义逻辑(如日志、验证)。Reflect
用于执行默认操作,其方法与Proxy
拦截器一一对应(如Proxy.get
对应Reflect.get
),确保在自定义逻辑中能复用原生行为。
10、Proxy
用于定于基本操作的自定义行为
本质上讲,修改的是程序默认行为,形同于在编程语言层面上做修改,属于元编程。此外所谓的元编程就是又叫超编程,指的是某类计算机程序的编写,这类计算机程序的编写或者操作其他程序作为他们的数据,或者运行时完成部分本应该在编译时完成的工作。元编程的优点就是提升我们的开发效率,减少手工编写全部代码。
Proxy
就是这样的,用于创建一个对象的代理,实现基本操作的拦截和自定义
- 语法
var proxy = new Proxy(target, handler)
target
:表示要拦截的目标对象(任何类型的对象、原生数组、函数,甚至是代理)
handler
:以函数作为属性对象,定义了在执行各种操作时代理p
的行为
- 与Reflect关系
如果想要在Proxy内部调用对象的默认属性,使用reflect
原因:
- 语义对齐:Reflect 方法与 Proxy 拦截器一一对应(参数、行为完全匹配),比如
Proxy.get
对应Reflect.get
,能精准承接拦截上下文(如receiver
保证this
指向正确)。 - 行为保真:Reflect 直接复用 JS 引擎的原生逻辑,处理复杂场景(如继承链、getter/setter、不可配置属性)时,比手动实现(如
target[key]
)更准确,避免遗漏细节。 - 逻辑契合:Reflect 方法返回布尔值表示操作成败,与 Proxy 拦截器的返回值要求完全匹配,简化错误处理,让自定义逻辑和默认行为无缝衔接
11、Decorator
本质:装饰器是元编程范式下的声明式扩展工具,核心目标是在不修改原类结构、不使用继承的前提下,动态扩展对象(类、方法、属性)的功能。其本质是接收特定参数并返回函数的高阶函数,通过 @装饰器名
的语法糖简化对目标的包装,实现 “横切逻辑”(如日志、权限、缓存)与 “核心业务逻辑” 的解耦。
- 分类:
装饰器根据作用对象可分为四类,核心差异在于接收的参数和作用时机:
类装饰器:接收类本身作为参数,用于修改类的构造行为或扩展静态属性。
方法装饰器:接收类原型、方法名、属性描述符,用于扩展方法逻辑(最常用)。
属性装饰器:接收类原型和属性名,用于修改属性默认行为(如只读、默认值)
readonly
装饰器
参数装饰器:接收类原型、方法名、参数索引,用于标记参数元数据(如必填校验)。
- 场景
装饰器的价值在逻辑复用和声明式编程中尤为突出,典型场景包括:
- 业务横切逻辑复用:
deprecate
装饰器:标记即将废弃的方法,调用时在控制台警告(“该方法将在 v2.0 移除”);- 权限校验装饰器:
@RequireAuth
拦截未登录用户调用核心方法(如支付、管理操作); - 缓存装饰器:
@Cache(60)
自动缓存方法返回值,减少重复计算或接口请求。
- 代码增强与约束:
如readonly
装饰器强制属性不可修改,validate
装饰器自动校验方法参数类型,通过声明式语法强化代码约束,减少手动校验逻辑。
- 优缺点
1. 优点:
- 声明式清晰:
@log
@readonly
等语法直观表达 “扩展意图”,比高阶函数嵌套更易读; - 逻辑解耦:将日志、权限等 “横切逻辑” 与核心业务分离,避免代码侵入;
- 复用性强:一个装饰器可应用于多个类 / 方法,减少重复代码。
2. 缺点:
- 兼容性依赖编译:原生 JS 不支持,需 TypeScript 或 Babel 编译,增加项目配置成本;
- 调试复杂度:多层装饰器嵌套时(如
@log @cache @auth
),调用栈会变长,调试需追溯装饰器逻辑; - 执行时机陷阱:装饰器在类定义时执行(而非实例化),若内部依赖动态状态(如全局配置),可能导致预期外行为。
- 装饰器目前处于 ECMAScript Stage 3 提案
12、Promise
Promise是一种异步编程的解决方案
优点:链式操作降低编码难度、代码可读性增强
- 状态
三种状态:
pending
:进行中;fulfilled
:已成功;rejected
:已失败
- 特点
- 对象的状态不受外界的影响,只有异步操作的结构可以决定哪一种状态
- 一旦状态改变(pending->fulfilled; pending->rejected),就不会再变,任何事后都可以得到这个结果
- 用法
const promise = new Promise(function(resolve, reject) {})
构造函数接收一个函数作为参数,该函数有两个参数
- resolve:将Promise的状态从pending变成fulfilled
- reject:将Promise的状态从pending变成rejected
1. 实例方法
.then
实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个是rejected状态的回调函数
.then返回的是一个Promise实例,这也就是为什么Promise可以实现链式调用的原因
.catch
其实就是用于指定发生错误时的回调函数,.then(null,rejected)/.then(undefined,rejected)
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获
一般我们使用catch代替then的第二个参数
.finally()
指定不管Promise的对象最后状态如何,都会执行的操作
- Promise构造函数的方法
1. Promise.all
用于将多个Promise实例,包装成一个新的Promise实例
const promise = Promise.all([p1,p2,p3]);- 接收一个数组(迭代对象)作为参数,数组成员均为Promise实例
- P的状态由P1,P2,P3决定,都为fulfilled,则p为fulfilled,返回值组成一个数组,传给p的回调函数
- P1,P2,P3中有一个被rejected,则p变成rejected,第一个被rejected的返回值传给p的回调函数
2. Promise.race()
将多个Promise实例包装成一个新的Promise实例
只要其中一个实例率先改变状态,则p的状态跟着改变
3. Promise.allSettled()
接受一组Promise实例作为参数,包装成一个新的Promise实例,只有等这些参数实例全部返回结果,包装实例才结束,返回每个的详细结果
const p1 = Promise.resolve('成功结果');
const p2 = Promise.reject(new Error('失败原因'));Promise.allSettled([p1, p2]).then(results => {console.log(results);// 输出:// [// { status: 'fulfilled', value: '成功结果' },// { status: 'rejected', reason: Error: 失败原因 }// ]
});
4. Promise.resolve()
将普通对象转为Promise
对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
注意:
- 如果参数是一个具体then()方法的对象,会将这个对象转为Promise对象,然后立即执行对象的then()方法
- 如果不是对象或者不具有then()方法,则转为Promise对象,状态为resolved
5. Promise.reject()
会返回一个新的Promise实例,该实例的状态为rejected
重要:手写Promise及其它方法
// 用类创建Promise,类中需要有执行器const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";class MyPromise {// 初始化参数status = PENDING;value = null;callbacks = [];// 构造函数constructor(executor) {try {// 传递两个函数,绑定thisexecutor(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject(error);}}resolve(value) {if (this.status == MyPromise.PENDING) {this.status = MyPromise.FULFILLED;this.value = value;setTimeout(() => {this.callbacks.map((item) => {item.onFulfilled(this.value);});});}}reject(reason) {if (this.status == PENDING) {this.value = MyPromise.REJECTED;this.value = reason;setTimeout(() => {this.callbacks.map((item) => {item.onRejected(this.value);});});}}/*** Promise.then()方法* - 接收两个参数:成功回调函数onFulfilled和失败回调函数onRejected* - then需要等promise状态改变后才执行,并且异步执行* - then的onFulfilled是返回Promise对象,并且then的状态以这个为准* - then的参数值可以为空也可以传值穿透*/then(onFulfilled, onRejected) {if (typeof onFulfilled != "function") {onFulfilled = (value) => value;}if (typeof onRejected != "function") {onRejected = (reason) => {throw reason;};}let promise = new MyPromise((resolve, reject) => {if (this.status == MyPromise.FULFILLED) {setTimeout(() => {this.parse(promise, onFulfilled(this.value), resolve, reject);});}if (this.status == MyPromise.REJECTED) {setTimeout(() => {this.parse(promise, onRejected(this.value), resolve, reject);});}if (this.status == MyPromise.PENDING) {this.callbacks.push({onFulfilled: (value) => {this.parse(promise, onFulfilled(value), resolve, reject);},onRejected: (reason) => {this.parse(promise, onRejected(value), resolve, reject);},});}});return promise;}// Promise 解析过程,保证Promise/A+parse(promise, result, resolve, reject) {// 检测循环引用,如果promise和result是同一个对象,抛出错误if (promise == result) {throw new TypeError("Chaining cycle detected for promise");}try {// result 是MyPromise的实例if (result instanceof MyPromise) {// 使用result自身的then方法,传入resolve和rejectresult.then(resolve, reject);} else {// 不是resolve(result);}} catch (error) {reject(error);}}// Promise.resolve 将对象转为Promise对象static resolve(value) {return new MyPromise((resolve, reject) => {if (value instanceof MyPromise) {// 使用原Promise值value.then(resolve, reject);} else {// 处理thenable对象const then = value.then;if (typeof then === "function") {then.call(value, resolve, reject);} else {// 处理普通对象或原始值resolve(value);}}});}/*** Promise.reject()* -*/static reject(reason) {return new Promise((resolve, reject) => {reject(reason);});}/*** Promise.all()* - 接收一个数组(迭代对象)作为参数,数组成员均为Promise实例*/static all(promises) {if (!Array.isArray(promises)) {return reject(new TypeError("argument must be an array"));}let values = [];let count = 0;const len = promises.length;return new MyPromise((resolve, reject) => {// 处理空数组的情况if (len === 0) {resolve([]);return;}for (let i = 0; i < len; i++) {MyPromise.resolve(promises[i]).then((value) => {values[i] = value;count++;if (count === len) {resolve(values);}},(reason) => {reject(reason);});}});}/*** Promise.race()* - 将多个Promise实例包装成一个新的Promise实例* - 率先改变状态的实例,则p的状态也变化*/static race(promises) {return new MyPromise((resolve, reject) => {promises.forEach((promise) => {promise.then((value) => {resolve(value);});});});}catch(onRejected) {return this.then(null, onRejected);}
}
- async/await
和Promise有什么关系
es2017的语法,就是generator
+promise的语法糖
await必须在async内部使用,并装饰一个promise对象,async返回对象也是一个Promise对象,
async/awai中的return或者throw会代理自己返回的Promise的resolve/reject。而一个Promise的resolve/reject会使得await得到返回值或抛出异常
13、迭代器
为不同的数据结构提供一个统一的访问接口
迭代器包含一个next方法,调用next函数,返回两个属性:值和布尔值(是否执行完)支持for… of 遍历
- 如何让一个对象成为迭代对象
对象必须实现@@interator,这就意味着在对象(或者其原型链上的其他对象)必须具有一个键为@@interator的属性,可通过Symbol.iterator访问该属性
将myIntrerator对象添加Symbol.iterator属性myIterator[Symbol.iterator] = function() {return { next() {return {value: , done:}}}}
同时在返回的next方法中添加两个属性