ECMASript 6
ECMASript 6
ES6简介
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制 造商协会, 这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该 组织改名为 Ecma 国际。
ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。
ECMA-262标准列表
ECMA-262历史版本列表
TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。其会员都是公司(
其中主要是浏览器厂商:有苹果、谷歌、微软、因特尔等)。TC39 定期 召开会议, 会议由会员公司的代表与特邀专家出席兼容性ECMAScript 6 compatibility table (compat-table.github.io)
ES6更新的内容概括:
表达式:声明、解构赋值
内置对象:字符串扩展、数值扩展、对象扩展、数组扩展、函数扩展、正则扩展、Symbol、Set、Map、Proxy、Reflect
语句与运算:Class、Module、Iterator
异步编程:Promise、Generator、Async

let,const和作用域
var命令会发生“变量提升”现象, 即变量可以在声明之前使用, 值为undefined。为了纠正这种现象,
let、const命令改变了语法行为, 它所声明的变量一定要在声明后使用, 否则报错
概括
声明:
const:声明常量
let:声明变量
var命令在全局代码中执行const命令和let命令只能在代码块中执行
赋值使用:
const命令声明常量后必须立马赋值let命令声明变量后可立马赋值或使用时赋值
声明方法:var、const、let、function、class、import
注意:
- 不允许重复声明
- 未定义就使用会报错:
const命令和let命令不存在变量提升- 暂时性死区:在代码块内使用
const命令和let命令声明变量之前, 该变量都不可用
let
let 关键字用来声明变量, 使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
应用场景:声明重复赋值的变量时可以用这个(基本替代var)
const
onst 关键字用来声明常量 , const 声明有以下特点:
- 不允许重复声明
值不允许修改- 不存在变量提升
- 块级作用域
- 声明必须赋初始值
- 标识符一般为大写
注意: 对象属性修改和数组元素变化不会触发 const 错误
const实际上保证的,并不是变量的值不得改动, 而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值), 值就保存在变量指向的那个内存地址, 因此等同于常量。但对于复合类型的数据(主要是对象和数组), 变量指向的内存地址, 保存的只是一个指向实际数据的指针,
const只能保证这个指针是固定的(即总是指向另一个固定的地址), 至于它指向的数据结构是不是可变的, 就完全不能控制了。。
应用场景:声明对象类型、确定不会再次赋值的变量使用 const , 其他的可以用let
1 | +----------+ +----------+ |
ES5 只有两种声明变量的方法:
var命令和function命令。ES6 除了添加let和const命令, 另外两种声明变量的方法:import命令和class命令。所以 , ES6 一共有 6 种声明变量的方法。
块级作用域
为什么需要块级作用域
没有块级作用域可能出现一些不合理的现象:
1 | var tmp = new Date(); |
没有块级作用域,if语句中的函数定提升,导致前面输出的tmp是undefined
1 | var s = 'hello'; |
没有块级作用域,控制语句结束后,其中的i还能被语句外调用
ES6块级作用域
let实际为JS添加了块级作用域
1 | function f1() { |
上面的函数有两个代码块, 都声明了变量n, 运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n, 最后输出的值才是 10。
块级作用域的出现, 匿名立即执行函数表达式(匿名 IIFE,详见js高级)显得没有必要了
块级作用域和函数声明
ES5 规定, 函数只能在顶层作用域和函数作用域之中声明, 不能在块级作用域声明。
ES5非法定义:
1 | // 情况一 |
上面两种函数声明, 根据 ES5 的规定都是非法的。
但是, 浏览器没有遵守这个规定, 为了兼容以前的旧代码, 还是支持在块级作用域之中声明函数, 因此上面两种情况实际都能运行, 不会报错。
ES6 引入了块级作用域, 明确允许在块级作用域之中声明函数。ES6 规定, 块级作用域之中, 函数声明语句的行为类似于let, 在块级作用域之外不可引用。
1 | function f() { console.log('I am outside!'); } |
1 | // ES5 环境实际运行 |
1 | // 浏览器的 ES6 环境 |
理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于
let, 对作用域之外没有影响。但是, 如果真的在 ES6 浏览器中运行一下上面的代码, 是会报错的
如果改变了块级作用域内声明的函数的处理规则, 显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题 , ES6 在附录 B里面规定, 浏览器的实现可以不遵守上面的规定, 有自己的行为方式。
- 允许在块级作用域内声明函数。
- 函数声明类似于
var, 即会提升到全局作用域或函数作用域的头部。 - 同时, 函数声明还会提升到所在的块级作用域的头部。
上面三条规则只对 ES6 的浏览器实现有效, 其他环境的实现不用遵守, 还是将块级作用域的函数声明当作let处理。
根据这三条规则, 浏览器的 ES6 环境中, 块级作用域内声明的函数, 行为类似于var声明的变量。上面的例子实际运行的代码如下。
1 | // 浏览器的 ES6 环境 |
避免在块级作用域内声明函数。如果确实需要, 也应该写成函数表达式, 而不是函数声明语句。
1 | // 块级作用域内部的函数声明语句, 建议不要使用 |
另外, 还有一个需要注意的地方。ES6 的块级作用域必须有大括号, 如果没有大括号 , JavaScript 引擎就认为不存在块级作用域。
1 | // 第一种写法, 报错 |
ES6解构赋值
概述
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
解构模型
解构的源:解构赋值表达式的右边部分。
解构的目标:解构赋值表达式的左边部分。
数组模型的解构(Array)
基本
1 | let [a, b, c] = [1, 2, 3]; |
可嵌套
1 | let [a, [[b], c]] = [1, [[2], 3]]; |
可忽略
1 | let [a, , b] = [1, 2, 3]; |
不完全解构
1 | let [a = 1, b] = []; // a = 1, b = undefined |
剩余运算符
1 | let [a, ...b] = [1, 2, 3]; |
字符串等
1 | let [a, b, c, d, e] = 'hello'; |
解构默认值
1 | let [a = 2] = [undefined]; // a = 2 |
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
1 | let [a = 3, b = a] = []; // a = 3, b = 3 |
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2
对象模型的解构(Object)
1 | let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; |
可嵌套忽略
1 | let obj = {p: ['hello', {y: 'world'}] }; |
不完全解构
1 | let obj = {p: [{y: 'world'}] }; |
剩余运算符
1 | let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; |
解构默认值
1 | let {a = 10, b = 5} = {a: 3}; |
ES6 Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
基本用法
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
1 | let sy = Symbol("KK"); |
使用场景
作为属性名
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
1 | let sy = Symbol("key1"); |
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
1 | let syObject = {}; |
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
定义常量
在 ES5 使用字符串表示常量(const COLOR_RED = "red";)。但是用字符串不能保证常量是独特的,这样会引起一些问题
使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等
1 | // 使用 Symbol 定义“常量” |
Symbol.for()Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
1 | let yellow = Symbol("Yellow"); |
Symbol.keyFor()Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
1 | let yellow1 = Symbol.for("Yellow"); |
ES6 Map 与 Set
Map对象
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突
1 | var myMap = new Map(); |
Map 的迭代
对 Map 进行遍历,以下两个最高级。
for...of
1 | var myMap = new Map(); |
forEach()
1 | var myMap = new Map(); |
Map 对象的操作
Map 与 Array的转换
1 | var kvArray = [["key1", "value1"], ["key2", "value2"]]; |
Map 的克隆
1 | var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]); |
Map 的合并
1 | var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); |
Set 对象
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
类型转化
Array
1 | // Array 转 Set |
Set 对象作用
数组去重
1 | var mySet = new Set([1, 2, 3, 4, 4]); |
并集
1 | var a = new Set([1, 2, 3]); |
交集
1 | var a = new Set([1, 2, 3]); |
差集
1 | var a = new Set([1, 2, 3]); |
Proxy与 Reflect
概述
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
Proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
1 | let target = { |
get(target, propKey, receiver)
用于 target 对象上 propKey 的读取操作。
1 | let exam ={ |
set(target, propKey, value, receiver)
1 | let validator = { |
第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
1 | const handler = { |
apply(target, ctx, args)
用于拦截函数的调用、call 和 reply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。
1 | function sub(a, b){ |
has(target,propKey)
用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。
1 | let handler = { |
此方法不拦截 for … in 循环。
construct(target, args)
用于拦截 new 命令。返回值必须为对象。
1 | let handler = { |
deleteProperty(target, propKey)
用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。
defineProperty(target, propKey, propDesc)
用于拦截 Object.definePro若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。
描述符对象(propDesc)可以是数据描述符或存取描述符。它们包含一些可选键值对,用来定义属性的各种特性
1 | let handler = { |
getOwnPropertyDescriptor(target, propKey)用于拦截 Object.getOwnPropertyD() 返回值为属性描述对象或者 undefined 。
1 | let handler = { |
getPrototypeOf(target)主要用于拦截获取对象原型的操作:
1 | - Object.prototype._proto_ |
1 | let exam = {} |
返回值必须是对象或者 null ,否则报错。另外,如果目标对象不可扩展(non-extensible),getPrototypeOf 方法必须返回目标对象的原型对象。
1 | let proxy = new Proxy({},{ |
isExtensible(target)用于拦截 Object.isExtensible 操作。
该方法只能返回布尔值,否则返回值会被自动转为布尔值。
1 | let proxy = new Proxy({},{ |
它的返回值必须与目标对象的isExtensible属性保持一致,否则会抛出错误。
1 | let proxy = new Proxy({},{ |
ownKeys(target)用于拦截对象自身属性的读取操作:
1 | - Object.getOwnPropertyNames() |
方法返回的数组成员,只能是字符串或 Symbol 值,否则会报错。
若目标对象中含有不可配置的属性,则必须将这些属性在结果中返回,否则就会报错。
若目标对象不可扩展,则必须全部返回且只能返回目标对象包含的所有属性,不能包含不存在的属性,否则也会报错。
1 | let proxy = new Proxy( { |
preventExtensions(target)拦截 Object.preventExtensions 操作。
该方法必须返回一个布尔值,否则会自动转为布尔值
1 | // 只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false ), |
setPrototypeOf主要用来拦截 Object.setPrototypeOf 方法。
返回值必须为布尔值,否则会被自动转为布尔值。
若目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型。
1 | let proto = {} |
Proxy.revocable()用于返回一个可取消的 Proxy 实例。
1 | let {proxy, revoke} = Proxy.revocable({}, {}); |
Reflect
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect 对象对某些方法的返回结果进行了修改,使其更合理。
Reflect 对象使用函数的方式实现了 Object 的命令式操作。
Reflect.get(target, name, receiver)查找并返回 target 对象的 name 属性。
1 | let exam = { |
Reflect.set(target, name, value, receiver)将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。
1 | let exam = { |
Reflect.has(obj, name)是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
1 | let exam = { |
Reflect.deleteProperty(obj, property)是 delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
1 | let exam = { |
Reflect.construct(obj, args)等同于 new target(…args)。
1 | function exam(name){ |
Reflect.getPrototypeOf(obj)用于读取 obj 的 proto 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。
1 | class Exam{} |
Reflect.setPrototypeOf(obj, newProto)用于设置目标对象的 prototype。
1 | let obj ={} |
Reflect.apply(func, thisArg, args)等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。
1 | Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5 |
Reflect.defineProperty(target, propertyKey, attributes)用于为目标对象定义属性。如果 target 不是对象,会抛出错误。
1 | let myDate= {} |
Reflect.getOwnPropertyDescriptor(target, propertyKey)用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。
1 | var exam = {} |
Reflect.isExtensible(target)用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。
1 | let exam = {} |
Reflect.preventExtensions(target)用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。
1 | let exam = {} |
Reflect.ownKeys(target)用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
1 | var exam = { |
组合使用
Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。
1 | let exam = { |
使用场景拓展
实现观察者模式?????
1 | // 定义 Set 集合 |
ES6字符串
拓展的方法
子串的识别
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
- **includes()**:返回布尔值,判断是否找到参数字符串。
- **startsWith()**:返回布尔值,判断参数字符串是否在原字符串的头部。
- **endsWith()**:返回布尔值,判断参数字符串是否在原字符串的尾部。
1 | const str = 'hello world' |
- 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf 。
- 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它。
字符串重复
1 | const str = "Hello World"; |
字符串补全
- padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
- padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。
1 | console.log("h".padStart(5,"o")); // "ooooh" |
如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
1 | console.log("hello".padStart(5,"A")); // "hello" |
如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
1 | console.log("hello".padEnd(10,",world!")); // "hello,worl" |
常用于补全位数:
1 | console.log("123".padStart(10,"0")); // "0000000123" |
⭐模板字符串
模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
普通字符串
1 | const str = `hello\n world` |
多行字符串
1 | const str = ` |
模板字符串中的换行和空格都是会被保留的
字符串插入变量和表达式
变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式
1 | let name= '张三'; |
字符串中调用函数
1 | let name= '张三'; |
标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串。
1 | alert`Hello world!`; |
当模板字符串中带有变量,会将模板字符串参数处理成多个参数。
1 | function f(stringArr,...values){ |
应用
过滤 HTML 字符串,防止用户输入恶意内容。
1 | function f(stringArr,...values){ |
国际化处理(转化多国语言)
1 | i18n`Hello ${name}, you are visitor number ${visitorNumber}.`; |
ES6数值
数值的表示
二进制表示法新写法: 前缀 0b 或 0B 。
1 | console.log(0b11 === 3); // true |
八进制表示法新写法: 前缀 0o 或 0O 。
1 | console.log(0o11 === 9); // true |
常量
Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差。
属性特性
1 | writable:false |
最大/最小安全整数
安全整数
安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。
最大安全整数
安全整数范围的上限,即 2 的 53 次方减 1 。
1 | Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true |
最小安全整数
安全整数范围的下限,即 2 的 53 次方减 1 的负数。
1 | Number.MIN_SAFE_INTEGER + 1 === Number.MIN_SAFE_INTEGER + 2; // false |
属性特性
1 | writable:false |
方法
Number.isFinite()用于检查一个数值是否为有限的( finite ),即不是 Infinity
1 | console.log( Number.isFinite(1)); // true |
Number.isNaN()用于检查一个值是否为 NaN 。
1 | console.log(Number.isNaN(NaN)); // true |
从全局移植到 Number 对象的方法
逐步减少全局方法,用于全局变量的模块化。方法的行为没有发生改变。
Number.parseInt()用于将给定字符串转化为指定进制的整数。
1 | // 不指定进制时默认为 10 进制 |
Number.parseFloat()用于把一个字符串解析成浮点数。
1 | umber.parseFloat('123.45') // 123.45 |
Number.isInteger() 用于判断给定的参数是否为整数。
1 | Number.isInteger(value) |
Number.isSafeInteger() 用于判断数值是否在安全范围内。
1 | Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1); // false |
MATH对象的扩展
ES6 在 Math 对象上新增了 17 个数学相关的静态方法,这些方法只能在 Math 中调用。
Math.cbrt用于计算一个数的立方根。
1 | Math.cbrt(1); // 1 |
Math.imul两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数
Math.hypot用于计算所有参数的平方和的平方根。
Math.clz32于返回数字的32 位无符号整数形式的前导0的个数。
Math.trunc用于返回数字的整数部分。
Math.fround用于获取数字的32位单精度浮点数形式。
Math.sign判断数字的符号(正、负、0)。
Math.expm1()于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1
Math.log1p(x)用于计算1 + x 的自然对数,即 Math.log(1 + x) 。
Math.log10(x)用于计算以 10 为底的 x 的对数。
Math.log2()用于计算 2 为底的 x 的对数。
双曲函数方法
- Math.sinh(x): 用于计算双曲正弦。
- Math.cosh(x): 用于计算双曲余弦。
- Math.tanh(x): 用于计算双曲正切。
- Math.asinh(x): 用于计算反双曲正弦。
- Math.acosh(x): 用于计算反双曲余弦。
- Math.atanh(x): 用于计算反双曲正切。
指数运算符
1 | 1 ** 2; // 1 |
ES6对象
属性的简介表示法
ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。
1 | const age = 12; |
方法名简写
1 | const person = { |
如果是Generator指向Generator函数 函数,则要在前面加一个星号:
1 | const obj = { |
属性名表达式
ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
1 | const obj = { |
属性的简洁表示法和属性名表达式不能同时使用,否则会报错。
1
2
3
4
5
6
7
8
9
10
11 const hello = "Hello";
const obj = {
[hello]
};
obj //SyntaxError: Unexpected token }
const hello = "Hello";
const obj = {
[hello+"2"]:"world"
};
obj //{Hello2: "world"}\
对象的扩展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
1 | let person = {name: "Amy", age: 15}; |
可用于合并两个对象
1 | let age = {age: 15}; |
注意点:
自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
1
2
3 let person = {name: "Amy", age: 15};
let someone = { ...person, name: "Mike", age: 17};
someone; //{name: "Mike", age: 17}自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
1
2
3 let person = {name: "Amy", age: 15};
let someone = {name: "Mike", age: 17, ...person};
someone; //{name: "Amy", age: 15}拓展运算符后面是空对象,没有任何效果也不会报错。
1
2 let a = {...{}, a: 1, b: 2};
a; //{a: 1, b: 2}拓展运算符后面是null或者undefined,没有效果也不会报错。
1
2 let b = {...null, ...undefined, a: 1, b: 2};
b; //{a: 1, b: 2}
对象的新方法
Object.assign(target, source_1, ···)用于将源对象的所有可枚举属性复制到目标对象中。
1 | let target = { |
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
1
2Object.assign(3); // Number {3}
typeof Object.assign(3); // "object"
因为 null 和 undefined 不能转化为对象,所以会报错:
1 | Object.assign(null); // TypeError: Cannot convert undefined or null to object |
注意点
assign 的属性拷贝是浅拷贝
1 | let sourceObj = { a: { b: 1}}; |
浅拷贝:
1
2
3
4
5
6
7
8
9
10
11
12 ### 定义
浅拷贝是指创建一个新对象,然后将现有对象的非静态字段复制到该新对象。如果字段是基本类型,则直接复制值;如果字段是引用类型,则复制引用而不是实际的对象。
### 特点
- **基本数据类型**:直接复制值。
- **引用数据类型**:只复制引用地址,即两个引用指向同一个内存位置。
#### 示例代码(JavaScript)
let original = { a: 1, b: { c: 2 } };
let shallowCopy = { ...original }; // 使用扩展运算符实现浅拷贝
shallowCopy.b.c = 3;
console.log(original.b.c); // 输出3,因为b引用的是同一个对象
同名属性替换
1 | targetObj = { a: { b: 1, c:2}}; |
数组的处理
1 | Object.assign([2,3], [5]); // [5,3] |
会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
Object.is(value1, value2)用来比较两个值是否严格相等,与(===)基本类似。
ES6数组
数组创建
Array.of()
将参数中的所有值作为元素形成数组
1 | let list = Array.of(1,2,3,[1,2,3],'张三'); |
Array.from()
将类数组对象或可迭代对象转化为数组。
1 | // 参数为数组,返回与原数组一样的数组 |
参数
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike想要转换的类数组对象或可迭代对象。
1 | console.log(Array.from([1, 2, 3])); // [1, 2, 3] |
mapFn可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
1 | console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6] |
thisArg可选,用于指定 map 函数执行时的 this 对象。
1 | let arrayLike = [1, 2, 3]; |
类数组对象
一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。
1 | let arr = Array.from({ |
转换可迭代对象
转换 map
1 | let map = new Map(); |
转换 set
1 | let arr = [1, 2, 3]; |
转换字符串
1 | let str = 'abc'; |
扩展的方法
查找
find()
查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
1 | let arr = Array.of(1, 2, 3, 4); |
findIndex()
查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
arr.findIndex(callback(element[, index[, array]])[, thisArg])
参数说明:
callback:用来测试每个元素的函数,接受三个参数:element(必需):当前遍历的数组元素。index(可选):当前元素的索引。array(可选):调用findIndex的数组本身。
- **
thisArg**(可选):执行callback时使用的this值。
1 | let arr = Array.of(1, 2, 1, 3); |
使用 thisArg 参数
假设我们有一个对象,并希望在回调函数中使用该对象的方法或属性作为条件的一部分。
1 | const threshold = { value: 10 }; |
在这个例子中,thisArg 参数被设置为 threshold 对象,这样在 isAboveThreshold 函数内部可以使用 this.value 来访问 threshold 对象的 value 属性。
填充
fill()
将一定范围索引的数组元素内容填充为单个指定的值。
1 | let arr = Array.of(1, 2, 3, 4); |
copyWithin
将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。
1 | // 参数1:被修改的起始索引 |
遍历
entries()
遍历键值对。
1 | for(let [key, value] of ['a', 'b'].entries()){ |
keys()
遍历键名。
1 | for(let key of ['a', 'b'].keys()){ |
values()
遍历键值。
1 | for(let value of ['a', 'b'].values()){ |
包含
includes()
数组是否包含指定值。
与 Set 和 Map 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。
1 | // 参数1:包含的指定值 |
嵌套数组转一维数组
flat()
1 | console.log([1 ,[2, 3]].flat()); // [1, 2, 3] |
flatMap()
1 | // 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组 |
数组缓冲区
数组缓冲区是内存中的一段地址。
定型数组的基础。
实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
创建数组缓冲区
通过构造函数创建
1 | let buffer = new ArrayBuffer(10); |
视图
视图是用来操作内存的接口。
视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。
DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型。
创建:
1 | // 默认 DataView 可操作数组缓冲区全部内容 |
定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区
创建
通过数组缓冲区生成
1 | let buffer = new ArrayBuffer(10), |
通过构造函数
1 | let view = new Int32Array(10); |
注意要点
length 属性不可写,如果尝试修改这个值,在非严格模式下会直接忽略该操作,在严格模式下会抛出错误。
1 | let view = new Int16Array([1, 2]); |
定型数组可使用 entries()、keys()、values()进行迭代。
1 | let view = new Int16Array([1, 2]); |
find() 等方法也可用于定型数组,但是定型数组中的方法会额外检查数值类型是否安全。concat() 方法由于两个定型数组合并结果不确定,故不能用于定型数组;另外,由于定型数组的尺寸不可更改,可以改变数组的尺寸的方法,例如 splice() ,不适用于定型数组。
1 | let view = new Int16Array([1, 2]); |
所有定型数组都含有静态 of() 方法和 from() 方法,运行效果分别与 Array.of() 方法和 Array.from() 方法相似,区别是定型数组的方法返回定型数组,而普通数组的方法返回普通数组。
1 | let view = Int16Array.of(1, 2); |
定型数组中增加了 set() 与 subarray() 方法。 set() 方法用于将其他数组复制到已有定型数组, subarray() 用于提取已有定型数组的一部分形成新的定型数组。
1 | // set 方法 |
扩展运算符
复制数组
1 | let arr = [1, 2], |
合并数组
1 | console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4] |
ES6函数
函数的参数扩展
默认参数
1 | function fn(name,age=17){ |
使用函数默认参数时,不允许有同名参数。
1 | // 不报错 |
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
1 | function fn(name,age=17){ |
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
1 | function f(x,y=x){ |
上例中当试图初始化x时候并为其赋默认值y,y还没有被初始化,不能作为x的默认值.
不定参数
不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
1 | function f(...values){ |
⭐箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
1 | 参数 => 函数体 |
1 | var f = v => v; |
当箭头函数没有参数或者有多个参数,要用 () 括起来。
1 | var f = (a,b) => a+b; |
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
1 | var f = (a,b) => { |
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
1 | // 报错 |
注意点:没有 this、super、arguments 和 new.target 绑定。
1 | var func = () => { |
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
1 | fconst obj = { |
在这个例子中,sayHelloTraditional方法内的匿名函数有自己的this上下文,它不指向obj对象,所以在默认情况下this.name将是undefined(或全局对象的名字)。而sayHelloArrow方法内的箭头函数并没有自己的this,它捕获并使用了外部sayHelloArrow函数的this值,即obj对象,因此正确地输出了Hello, Qwen。
不可以作为构造函数,也就是不能使用 new 命令,否则会报错
适合使用的场景
将外部this传递到回调函数中
1 | // 回调函数 |
不适合使用的场景
定义函数的方法,且该方法中包含 this
1 | var Person = { |
需要动态 this 的时候
1 | var button = document.getElementById('userClick'); |
button 的监听函数是箭头函数,所以监听函数里面的 this 指向的是定义的时候外层的 this 对象,即 Window,导致无法操作到被点击的按钮对象。
ES6 Class类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
基础用法
类定义
类表达式可以为匿名或命名。
1 | // 匿名类 |
类声明
1 | class Example { |
不可重复声明。
1 | class Example{} |
- 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
- 类中方法不需要 function 关键字。
- 方法间不能加分号。
类的主体
属性
prototype
ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法
1 | Example.prototype={ |
静态属性
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
1 | class Example { |
公共属性
1 | class Person { |
实例属性
实例属性:定义在实例对象( this )上的属性。
1 | class Person { |
name 属性
返回跟在 class 后的类名(存在时)。
1 | let Example=class Exam { |
方法
constructor 方法
constructor 方法是类的默认方法,创建类的实例化对象时被调用。
1 | class Example{ |
返回对象
1 | class Test { |
静态方法
1 | class Example{ |
原型方法
1 | class Example { |
实例方法
1 | class Example { |
类的实例化
new
1 | class Example {} |
实例化对象
共享原型对象
1 | class Example { |
decorator
decorator 是一个函数,用来修改类的行为,在代码编译时产生作用。
类修饰
一个参数
第一个参数 target,指向类本身。
1 | function testable(target) { |
多个参数
嵌套实现
1 | function testable(isTestable) { |
实例属性
上面两个例子添加的是静态属性,若要添加实例属性,在类的 prototype 上操作即可。
方法修饰
3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)。
1 | class Example { |
修饰器执行顺序(由外向内进入,由内向外执行)
1 | class Example { |
封装与继承
getter / setter
在 ES6(ECMAScript 2015)中,JavaScript 引入了类(class)语法,并支持使用 getter 和 setter 来定义对象属性的访问器方法。这些方法允许你以更优雅的方式控制对对象属性的读取和写入操作。
基本概念
get:用于定义一个获取属性值的方法。set:用于定义一个设置属性值的方法。
它们通常成对出现,但也可以单独存在。
类中的 getter / setter 示例
1 | class Person { |
🔍 特点说明
- 使用
get定义的属性是不可调用的方法,像普通属性一样访问(不需要加括号)。 - 使用
get和set可以封装属性访问逻辑,例如验证、格式化等。 - 不需要显式调用函数,而是通过属性名来访问或赋值。
📦 对象字面量中的 getter / setter(ES5+)
除了类之外,getter 和 setter 也适用于普通对象:
1 | const user = { |
🛡️ 注意事项:
- 不能有参数的 getter:getter 方法不能有参数。
- setter 至少一个参数:setter 必须有一个参数,用来接收赋值的值。
- 避免与字段名冲突:通常会使用
_前缀命名内部变量,比如_name,防止无限递归。
🧩 实际用途
- 数据验证(如上面的例子)
- 属性计算(动态生成属性值)
- 兼容性处理(旧接口兼容新实现)
- 隐藏内部状态(封装)
extends
通过 extends 实现类的继承。
1 | class Child extends Father { ... } |
super
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况, super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super 函数。
1 | class A {} |
上面代码中,子类 B 的构造函数之中的 super() ,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B 的实例,因此 super() 在这里相当于 A.prototype.constructor.call(this) 。
1 | class A { |
上面代码中, new.target 指向当前正在执行的函数。可以看到,在 super() 执行时,它指向的是子类 B 的构造函数,而不是父类 A 的构造函数。也就是说, super() 内部的 this 指向的是 B 。
作为函数时, super() 只能用在子类的构造函数之中,用在其他地方就会报错。
1 | class A {} |
第二种情况, super 作为对象时,在普通方法中,指向父类的原型(prototype)对象;在静态方法中,指向父类。
1 | class A { |
super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
1 | class A { |
super.print() 虽然调用的是 A.prototype.print() ,但是 A.prototype.print() 内部的 this 指向子类 B 的实例,导致输出的是 2 ,而不是 1 。也就是说,实际上执行的是 super.print.call(this) 。
如果 super 作为对象,用在静态方法之中,这时 super 将指向父类,而不是父类的原型对象。
1 | class Parent { |
另外,在子类的静态方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例。
1 | class A { |
使用 super 的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
1 | class A {} |
console.log(super) 当中的 super ,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明 super 的数据类型,就不会报错:
1 | class A {} |
由于对象总是继承其他对象的,所以可以在任意一个对象中,使用 super 关键字。
1 | var obj = { |
ES6 Module 的语法
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的 require 、Python 的 import ,甚至就连 CSS 都有 @import ,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
1 | // CommonJS模块 |
上面代码的实质是整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象( _fs ),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。
1 | // ES6模块 |
上面代码的实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各种好处,ES6 模块还有以下好处。
- 不再需要 UMD 模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
- 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者 navigator 对象的属性。
- 不再需要对象作为命名空间(比如 Math 对象),未来这些功能可以通过模块提供。
严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上 “use strict”; 。
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用 with 语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量 delete prop ,会报错,只能删除属性 delete global[prop]
- eval 不会在它的外层作用域引入变量
- eval 和 arguments 不能被重新赋值
- arguments 不会自动反映函数参数的变化
- 不能使用 arguments.callee
- 不能使用 arguments.caller
- 禁止 this 指向全局对象
- 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
- 增加了保留字(比如 protected 、 static 和 interface )
需要注意 this 的限制。ES6 模块之中,顶层的 this 指向 undefined ,即不应该在顶层代码使用 this 。
export命令
模块功能主要由两个命令构成:export和import 。 export 命令用于规定模块的对外接口, import 命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。下面是一个 JS 文件,里面使用 export 命令输出变量。
1 | // profile.js |
另一种export写法:
1 | // profile.js |
export输出函数或者类:
1 | export function multiply(x, y) { |
使用as关键字重命名:
1 | function v1() { ... } |
export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
1 | // 报错 |
function 和 class 的输出,也必须遵守这样的写法:
1 | // 报错 |
export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
1 | export var foo = 'bar'; |
xport 命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的 import 命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。
1 | function foo() { |
import 命令
使用export 命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块。
1 | // main.js |
重命名
为输入的变量重新取一个名字, import 命令要使用 as 关键字,将输入的变量重命名。
1 | import { lastName as surname } from './profile.js'; |
import 命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
1 | import {a} from './xxx.js' |
a 是一个对象,改写 a 的属性是允许的。
1 | import {a} from './xxx.js' |
a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。
路径
import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径, .js 后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
1 | import {myMethod} from 'util'; |
提升
import 命令具有提升效果,会提升到整个模块的头部,首先执行。
1 | foo(); |
静态执行
由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
1 | // 报错 |
多次引入
import 语句会执行所加载的模块,
1 | import 'lodash'; |
目前阶段,通过 Babel 转码,CommonJS 模块的 require 命令和 ES6 模块的 import 命令,可以写在同一个模块里面,但是最好不要这样做。因为 import 在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。
1 | require('core-js/modules/es6.symbol'); |
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号( * )指定一个对象,所有输出值都加载在这个对象上面。
下面是一个 circle.js 文件,它输出两个方法 area 和 circumference 。
1 | // circle.js |
加载模块
1 | // main.js |
上面写法是逐一指定要加载的方法,整体加载的写法如下。
1 | import * as circle from './circle'; |
模块整体加载所在的那个对象(上例是 circle ),应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的。
1 | import * as circle from './circle'; |
export default 命令
从前面的例子可以看出,使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到 export default 命令,为模块指定默认输出。
1 | // export-default.js |
上面代码是一个模块文件 export-default.js ,它的默认输出是一个函数。
其他模块加载该模块时, import 命令可以为该匿名函数指定任意名字。
1 | // import-default.js |
面代码的 import 命令,可以用任意名称指向 export-default.js 输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时 import 命令后面,不使用大括号。
export default 命令用在非匿名函数前,也是可以的。
1 | // export-default.js |
上面代码中, foo 函数的函数名 foo ,在模块外部是无效的。加载的时候,视同匿名函数加载。
下面比较一下默认输出和正常输出。
1 | // 第一组 |
上面代码的两组写法,第一组是使用 export default 时,对应的 import 语句不需要使用大括号;第二组是不使用 export default 时,对应的 import 语句需要使用大括号。
export default 命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此 export default 命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应 export default 命令。
本质上, export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
1 | // modules.js |
正是因为 export default 命令其实只是输出一个叫做 default 的变量,所以它后面不能跟变量声明语句。
1 | // 正确 |
上面代码中, export default a 的含义是将变量 a 的值赋给变量 default 。所以,最后一种写法会报错。
同样地,因为 export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后。
1 | // 正确 |
上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为 default 。
有了 export default 命令,输入模块时就非常直观了,以输入 lodash 模块为例。
1 | import _ from 'lodash'; |
如果想在一条 import 语句中,同时输入默认方法和其他接口,可以写成下面这样。
1 | import _, { each, forEach } from 'lodash'; |
对应上面代码的 export 语句如下。
1 | export default function (obj) { |
上面代码的最后一行的意思是,暴露出 forEach 接口,默认指向 each 接口,即 forEach 和 each 指向同一个方法。
export default 也可以用来输出类。
1 | // MyClass.js |
