引言
2015 年,ECMAScript 第六版得以发布,这就是众所周知的 ES6 标准,标志着 JavaScript 语言迎来新纪元。ES6 引入了一些新的语法糖,同时弥补了一些在 ES5 中存在的一些缺陷。
本篇笔记是在学习阮一峰老师的《ES6 入门教程》中做的笔记,主要是记录一些自己不太熟悉的或者和 ES5 差异很大的特性。在学习过程中,可以和别的语言(如 Python)进行对比,也可以看到在某些设计思想上,这些语言也是相通的。
简介
- ES5 其实是 ES3 的升级版本,也就是 ES3.1
- ES4 其实并没有发布,但是增加的一些功能在 ES6 中发布了
- 每年 6 月发布新的标准,ES6 泛指下一代 JavaScript 标准
- Babel 转码器:将 ES6 代码转换成 ES5 代码
- Google 的 Traceur转码器也可以将 ES6 代码转为 ES5 代码
杂记
- 暂时性死区本质:只要一进入当前作用域,所需要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
- 块作用域可以任意嵌套
- 块作用域内可以声明函数,但是只能在该作用域下使用
- 浏览器为了兼容历史代码,在块作用域中声明的函数会提升到全局,类似 var声明的变量了
- ES6 支持 6 中声明关键字: - var
- function
- let
- const
- import
- class
 
- var,- function命令声明的全局变量,依然是顶层对象的属性;- let,- const,- class声明的全局变量,则不再属于顶层对象的属性。二者需要脱钩
- JavaScript 中的顶层对象在各个环境下都是需要存在的,但是在 Node 和浏览器环境中用一套代码想要拿到顶层对象还是有些费劲的。可行的方案如下:1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14(typeof window !== 'undefined' 
 ? window
 : (typeof process === 'object' &&
 typeof require === 'function' &&
 typeof global === 'object')
 ? global
 : this);
 var getGlobal = function () {
 if (typeof self !== 'undefined') { return self; }
 if (typeof window !== 'undefined') { return window; }
 if (typeof global !== 'undefined') { return global; }
 throw new Error('unable to locate global object');
 };
解构
- 解构(类似 Python 中的用法,或者 Rust 中的模式匹配): - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- let [a, b, c] = [1, 2, 3] 
 // c 将接收剩余的数组元素
 [a, b, ...c] = [1, 2, 3, 4, 5]
 [, , c] = [1, 2, 3]
 // 不完全解构也是支持的
 [a, b] = [1, 2, 3, 4]
 // 右值必须要是可迭代的,即实现了 Iterator 接口,否则会出错
 [a, b] = NaN // TypeError: undefined is not a function
- 可设置默认值: - let [foo = true] = [];,只有数组成员严格等于- undefined才会让默认值生效
- 解构失败,变量值为 undefined
- 对象也可以解构: - 1 
 2
 3
 4
 5
 6
 7
 8
 9- let o = {name: "chris", age: 26} 
 let { name, age } = o
 // 可以将对象赋值给某个已有的变量
 const { log } = console
 log('hello')
 // 可以有别名,name 是模式,n 才是被赋值的变量
 ({name: n, age: a} = o)
- 对象解构的本质是,先找到同名属性,再赋值给对应的变量 
- 解构赋值的原则是,只要右值不是对象或数组,都要先转为对象(如数字会转成 Number);而 undefined和null无法转换为对象,所以会报错
- 函数的参数也支持解构赋值
字符串
- "\u{xxxx}"可以表示超过两个字节的 Unicode 字符
- for (let c of 'foo')可以直接遍历字符串,并且可以识别大于- 0xFFFF的码点
- String.raw返回转义字符串:
- String.includes()是否存在子串
- String.startsWith()+- String.endsWith()
- String.repeat()
数值
- 多种进制写法: - 二进制:0b010101
- 八进制:0o123
- 十六进制:0x1234
 
- 二进制:
- 将字符串的多种进制转换成十进制: - Number(target)
- 新增了 Number.isFinite和Number.isNaN方法,但这两种方法只对数值有效,其它一律为 false。注意也有一对全局的函数isFinite和isNaN,它们则会尝试先将输入的参数转换为 Number 后再进行判断,这是最重要的区别
- ES6 将 parseInt和parseFloat挪到Number上了,这样会更加统一
- Number.EPSILON浮点数计算会有精度损失,这个极小值可以用来指定误差范围
- 指数运算符 **,采用的是右结合的模式
- V8 引擎中,指数运算符和 Math.pow算法实现是不同的,对于特别大的结果,二者会有细微差别
函数
- 默认参数:- function say(word = 'hi') { console.log(word) }
- 同一作用域中,不能出现同名函数的声明
- 默认参数是惰性计算,每次调用都会重新计算,这点和 Python 非常不同!!
 
- 函数参数支持解构,且支持默认值
- 指定了参数默认值后,函数的 length属性只会返回未设置默认值的参数个数
- 如果设置了参数,且不是尾参数,则其之后的参数都不算到 length了:(function (a = 0, b, c) {}).length // 0
- 函数如果设置了默认值,则在初始化时会形成一个特殊的作用域: - 1 
 2
 3
 4
 5
 6
 7- const x = 100 
 // 这里的参数 `x` 和 `y` 处于一个作用域中,故 `y` 的默认值就是指向参数 `x`
 function foo(x, y = x) {
 console.log(x, y)
 }
 foo(200) //200, 200
- ES6 规定,只要函数使用了默认值、解构和扩展运算符,就不能在内部显式指定为严格模式 
- 箭头函数(更简洁的匿名函数写法):- 如果直接返回一个对象,需要将对象用圆括号包围,否则会被解释为代码块:let getObj = id => ({ id: id })
- 函数体内的 this是定义时所在的对象,而非使用时所在的对象,也就是在箭头函数中,this是固定的。本质上是因为在箭头函数中,根本就没有this,它指向的是外层的this
 
- 如果直接返回一个对象,需要将对象用圆括号包围,否则会被解释为代码块:
- 不可以作为构造函数,不能对它使用 `new` 命令
- 没有 `arguments` 对象
- 不能使用 `yield`,不可以作为 `Generator`
- 尾调用: - 函数式编程中的一个重要概念,指的是函数的最后一步操作(不一定是最后一行,逻辑上是最后一步)是直接调用另一个函数并返回其结果:1 
 2
 3
 4
 5
 6
 7
 8function foo(x) { return bar(x) } 
 // 以下不符合
 function foo(x) { let y = bar(x); return y }
 function foo(x) { bar(x) }
 function foo(x) { return bar(x) + 1 }
 
- 函数式编程中的一个重要概念,指的是函数的最后一步操作(不一定是最后一行,逻辑上是最后一步)是直接调用另一个函数并返回其结果:
- 尾调用优化:只保留内层函数的调用帧,节约内存 
- ES6 中只要使用了尾递归,就不会出现调用栈溢出,因为只需要维护一个调用帧即可。但其实这个优化只有在严格模式下才会启用。正常模式下,需要通过变量 arguments,func.caller来跟踪函数的调用栈
数组
- 数组复制:const a2 = [...a1]或者const [...a2] = a1
- 可以将字符串转换为数组:const a = [...'hello']
- 扩展运算符 ...可以将任何实现了Iterator接口的对象转换成数组
- Array.from可以接受任意实现了- Iterator接口的对象,转换成数组;也支持 array-like 对象
- Array.of将一组值转换为数组,弥补- Array构造函数在参数个数不同时,行为也不同的毛病。完全可以替代- Array()或者- new Array()
- 提供了几个遍历的接口: - keys()实际就是索引
- values()值
- entries()键值
 
- includes()判断是否包含元素
- flat用于将嵌套数组展开,拉平,默认只展开一层,可指定多层或者- Infinity
- flatMap和- flat类似,但是会对每个元素执行一次回调函数,但它只能展开一层
对象
- 属性或者方法都可以简写啦: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- let [x, y] = [1, 2] 
 const a = {x, y}
 // 等同于
 const a = {x: x, y: y}
 // 方法的简写
 const o = {
 doSomething() { return true }
 // 常规写法
 doSomething: function() { return true }
 }
- 允许使用表达式作为对象的属性名: - 1 
 2
 3
 4
 5
 6- let propKey = 'foo' 
 let obj = {
 [properKey]: true,
 ['a' + 'bc']: 123
 }
- ES6 中属性遍历的方法: - for...in:遍历对象自身和继承的可枚举属性(不含 Symbol 属性)
- Object.keys(obj):对象自身可枚举属性(不含 Symbol 属性)
- Object.getOwnPropertyNames(obj):对象自身的所有属性的键名(不含 Symbol 属性)
- Object.getOwnPropertySymbols(obj):对象自身所有- Symbol属性的键名
- Reflect.ownKeys(obj):对象自身所有的键名(无论是 Symbol 属性或者是别的属性,不关心可否枚举)
 
- super:- 指向对象的原型对象,在这种用法下,只能在对象方法中使用
- 对象方法:必须采用 ES6 方法简写的方式,才会被认定为对象方法
 
- 对象解构赋值: - let { x, y, ...z } = { x: 1, y: 2, a: 10, b: 20 }
- 解构赋值必须是最后一个参数
- 右值必须是对象
- 采用的是浅拷贝
 
- Object.is():- 更加明确的比较对象是否符合 Same-value equlity
- Object.is(NaN, NaN) // true
- Object.is(+0, -0) // false
 
- Object.assign()用于对象合并,就是将源对象所有可枚举的属性复制到目标对象(包括- Symbol属性)。注意点:- 浅拷贝
- 同名属性直接替换,不考虑嵌套
- 由于 Object.assign只能进行值复制,对于取值函数,则会先取值再复制
 
- Object.assign()常见用途:- 为对象添加属性:Object.assign(this, { x, y })
- 为对象添加方法:Object.assign(Foo.prototype, { barMethod() { ... } })
- 对象克隆:Object.assign(Object.create(originProto), origin)
- 属性提供默认值:Object.assign({}, DEFAULTS, options)
 
- 为对象添加属性:
Symbol
- ES6 引入 Symbol 的原因: - ES5 中,属性名是字符串,容易造成属性名冲突
- Symbol 可以保证每个属性独一无二
 
- Symbol是新增的原始类型,表示独一无二的值。用于对象的属性。使用- Symbol()函数构造
- Symbol接收一个字符串作为参数,表示对- Symbol实例的描述。如果是一个对象参数,则会调用- obj.toString()得到对象的字符串,再生成一个 Symbol 值
- 注意点:- 不可以直接与其它类型值运算,不会隐式转换为 string
- 可以显式转换为 string
 
- symbol.description可以获取 Symbol 的描述信息
- 作为对象的属性(只能是公开属性): - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- const name = Symbol('name') 
 let person = {}
 // 第一种定义方式
 person[name] = "Foo"
 // 获取的方式
 person[name]
 // 第二种定义方式
 let person2 = {
 [name]: 'Bar' // 必须要放在方括号中
 }
 // 第三种定义方式
 Object.defineProperty(a, mySymbol, { value: 'Hello!' });
- 可以作为常量,保证值不同 
- 可以类似我们在 Python 中那样定义 Enum:
| 1 | const userType = { | 
- 由于不会被常规的方法如 getOwnPropertyNames等遍历到,但其实又是公开。这种特性可以用来创建一些非私有、仅希望内部调用的方法
- Symbol.for和- Symbol.keyFor:复用相同参数构建的- Symbol,它会在全局注册参数,并在创建前进行搜索,如果存在则返回,否则新建。注意这个全局是全局环境,可以在不同的- iframe和- service worker中取到同样的值:- 1 
 2
 3
 4
 5
 6- let s1 = Symbol.for('s1') 
 Symbol.for('s1') === s1 // true
 Symbol.keyFor(s1) // 's1'
 let s2 = Symbol('s2')
 Symbol.keyFor(s2) // undefined
- 内置的 Symbol 值,指向语言内部使用的方法: - Symbol.hasInstance,每个对应可以自定义这个 Symbol 方法,从而让- instanceof运算符按照期望的表现
- Symbol.isConcatSpreadable属性,表示在- Array.prototype.concat()时,能否展开
- Symbol.species属性,指向一个构造函数。在创建衍生对象时,会使用该属性
- Symbol.iterator指向对象的默认遍历器
- Symbol.toPrimitive指向转换成原始类型的方法
 
Set 和 Map 数据结构
- Set构造函数可接受任意- Iterable的对象
- 来看下消除数组中重复元素的方法与 Python 的区别: - 1 
 2
 3
 4
 5
 6
 7
 8- // Python 中的写法 
 list(set([1, 1, 2]))
 // JS ES6 中的写法
 [...new Set([1, 1, 2])]
 // 或者
 Array.from(new Set([1, 1, 2)])
- WeakSet弱引用计数,只能存放对象。不可遍历
- Map的构造:- 1 
 2
 3
 4
 5
 6- // 先看看 Python 中一个类似的构造方式 
 dict(zip(['name', 'age'], ['chris', 22]))
 // 那么在 ES6 中的写法
 // 实际上任意 Iterable 的对象,且成员均为双元素的数组的数据结构都可以作为 Map 的构造函数
 new Map([['name', 'age'], ['chris', 22]])
- WeakMap
Proxy
- 属于元编程范畴,可以拦截一些操作,并进行重定义
- 构造函数:var proxy = new Proxy(target, handler)
- 要想让 - Proxy起作用,必须要针对其实例进行操作,而非目标对象- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18- let handler = { 
 get (target, name, receiver) {
 if (name === "name" || name == "age") {
 return Reflect.get(target, name, receiver)
 } else {
 throw ReferenceError(`property '${name}' not found`)
 }
 }
 }
 let user = {
 name: "Chris",
 age: 26,
 }
 let proxy = new Proxy(user, handler)
 console.log(proxy)
 console.log(proxy.age)
 console.log(proxy.name)
 console.log(proxy.foo)
- Proxy.revocable可以返回一个可取消的 Proxy 实例
Reflect
- Reflect也是为了操作对象而提供的新的 API,设计目标:- 将 Object对象中明显属于内部的方法剥离出来,放到Reflect对象上
- 修改某些 Object方法的返回结果,更加方便使用
- 将某些命令式的操作,变成函数,如:name in obj => Reflect.has(obj, name),delete obj[name] => Reflect.deleteProperty(obj, name)
- 与 Proxy中拦截方法一样,可以替代执行对象的默认行为
- Reflect.apply(Math.floor, undefined, [1])
 
- 将 
Promise
- 异步编程解决方案。所谓 Promise,就是一个简单的容器,保存着未来才会结束的事情(通常是一个异步结果)
- 特点: - 对象的状态不受外界影响(pending, fulfilled, rejected)
- 一旦状态改变,就不会再变,任何时候都可以得到结果
- 以更加同步的方式编程,易于提供统一的接口,便于维护
 
- 缺点: - 无法取消
- 若不设置回调,Promise 内部抛出错误不会反馈到外部
- pending 状态时,无法得知具体进展情况
 
- Promise.prototype.then()可接受两个回调,一个是- resolved时的 回调,还有一个是- rejected时的回调。第二个回调可选
- Promise.prototype.then()返回的是一個新的- Promise實例
- Promise.prototype.catch()是- .then(undefined, rejection)或- .then(null, rejection)的别名,用于指定发生错误时的回调函数
- Promise 对象的错误具有冒泡的性质,会一直向后传递,直到被捕获为止。错误总是会被下一个 catch捕获
- Promise.prototype.finally用于指定不管- Promise对象最终如何,都会执行的操作,ES2018 引入。无法在 finally 中获取 Promise 的状态,它的执行与状态无关
- Promise.all:- 参数必须是 Iterable 的对象,元素应当是 Promise 实例
- 状态由成员决定,只有都是 fulfilled时,整体的状态才会变成fulfilled
- 如果任何一个 rejcted,则整体整体就是rejected,并且返回第一个被reject的实例的返回值
 
- Promise.race,类似- Promise.any的感觉,就是只要有一个状态改变,整体状态就变化;率先变化的 Promise 实例返回值会传递给- p的回调函数
Iterator & for…of
- Iterator 遍历过程: - 创建一个指向可迭代对象的指针对象
- 调用 next,可得到第一个成员(返回结果包括两个属性:value+done,可分别省略)
- 不断调用 next直到消费完
 
- 实现了 - Iterator接口的数据结构,就是可遍历的(Iterable)。示例:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20- class RangeIterator { 
 constructor(start = 0, stop = 100) {
 this._value = start
 this._stop = stop
 }
 [Symbol.iterator]() {
 return this
 }
 next() {
 let val = this._value
 if (val < this._stop) {
 this._value++
 return {done: false, value: val}
 }
 return {done: true, value: undefined}
 }
 }
 for (const x of new RangeIterator()) {
 console.log(x)
 }
- 何时触发 Iterator (即 - Symbol.iterator)接口调用?- 解构赋值
- 扩展运算符
- yield*
- for...of
- Array.from()
- Map(),- Set(),- WeakMap(),- WeakSet()
- Promise.all()
- Promise.race()
 
- 可以使用 Generator 函数实现可迭代: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- class RangeIterator { 
 constructor(start = 0, stop = 10) {
 this._value = start
 this._stop = stop
 }
 *[Symbol.iterator]() {
 while (this._value < this._stop) {
 this._value++
 yield this._value
 }
 }
 }
 for (const x of new RangeIterator()) {
 console.log(x)
 }
- 遍历器对象除了有 - next方法外,还可以添加一个- return方法,用于处理- for...of提前退出(如出错,或者- break)时要做的事情。而- throw方法一般是配合 Generator 函数使用
Generator 函数
- 定义方式:function* funcName() { yield 1; return "end" }
- 调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数内部指针
- yield只能用于 Generator 函数中(这个和 Python 不同。在 Python 中,Generator 函数从表明上看和普通函数定义是一样的,只是实现不同)
- yield表达式本身无返回值,或者总返回- undefined。- next方法可以带参数,作为- yield表达式的返回值
- 第一次带参数调用 next方法时,参数是无效的。因为next方法的参数表示上一个yield表达式的返回值
- 语义上,第一次调用 next是启动遍历器对象
- Generator.prototype.throw()可以在函数体外向 Generator 函数抛错。如果生成器中没有- try...catch块,错误会传递到外部
- 如果一个 Generator 执行过程中出错,且没有内部捕获,则会认为它已经执行结束了
- Generator.prototype.return()可以终止 Generator,如果- return带参数,则会作为最后的返回值。如果 Generator 中含有- try...finally语句块,则会优先执行- finally,延迟返回
- yield*表达式可以参考 Python 中的- yield from
- 异步编程模式:- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
 
async 函数
- async是 Generator 语法糖,写异步执行的函数更加便捷
- 改进点: - 内置执行器,无需 co之类的模块控制 Generator 执行流程了
- 更好的语义
- 更广的适用性。await可以是 Promise 对象,如果是原始类型值,则会自动转换为 resolved Promise 对象
- 返回值是 Promise
 
- 内置执行器,无需 
- 定义方式: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- // 函数声明 
 async function foo() { }
 // 函数表达式
 const foo = async function () { }
 // 对象的方法
 let o = { async foo() { } }
 // 类中定义
 class Storage {
 constructor() {
 this.cachePromise = caches.open("avatars")
 }
 async getAvatar(name) {
 const cache = await this.cachePromise
 return cache.match(`/avatars/${name}.jpg`)
 }
 }
 const store = new Storage()
 store.getAvatar("jake").then().cach()
 // 箭头函数
 const foo = async () => {}
- 错误处理:如果 - await后面的异步操作出错,等同于- async函数返回的 Promise 对象被 reject。捕获错误的两种方式:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- async function foo() { 
 try {
 await doSomething()
 } catch (e) {
 console.error(e)
 }
 }
 // 另外一种
 async function bar() {
 await doSomething().catch(e => { console.error(e) })
 }
- 对于不相互依赖的独立操作,不建议分别 await,那样和同步有毛线区别。建议用下面的方式: - 1 
 2
 3
 4
 5
 6
 7- let [foo, bar] = await Promise.all([foo(), bar()]) 
 // 另外一种写法
 let fooPromise = foo()
 let bar Promise = bar()
 let fooResult = await fooPromise
 let barResult = await barPromise
- 实现原理:就是将 Generator 函数和自动执行器包装在一起: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- async function fn(args) { 
 // ...
 }
 // 等价于
 function fn(args) {
 return spaw(function *() {
 //...
 })
 }
Class
- ES6 中的 class 本质上还是 Function对象,所有定义的新方法都是在prototype上。但是,类内部定义的所有方法都是不可枚举的,这点和 ES5 中直接给prototype挂载方法不同
- 每个类都必要有 constructor,如果没有会有一个空的constructor存在
- 类必须使用 new调用,否则会报错
- this 指向: - 类的方法内部如果含有 this,默认指向类的实例。但是如果单独使用,可能出错
- 解决方法有两种,固定 this:- 采用 bind
- 使用箭头函数
 
- 采用 
 
- 类的方法内部如果含有 
- 静态方法: - 使用 static关键字修饰
- 不会被实例继承和调用(这点和 Python 不同,在 Python 中 static 方法可以被实例调用,而不需要通过类名)
- 如果静态方法中包含 this,其指向的不是实例,而是类
- 静态方法可以和非静态方法重名
- 父类的静态方法可以被子类继承
 
- 使用 
- 继承: - 使用 extends关键字
- 单继承
- 子类必须要在自定义的 constructor方法中调用super,触发父类构造函数调用后,才可以使用this
 
- 使用 
- 判断一个类是否继承自某个类实例: - Reflect.getPrototypeOf(ColoredPoint) === Point
- super关键字:- 当作函数使用,代表调用父类的构造函数(注意,此时 super调用后返回的是基类的实例),只能用于构造函数
- 当作对象,在普通方法中指向父类的原型对象;在静态方法中,指向父类
- ES6 规定,在子类普通方法中调用 super.xxx()时,方法中的this指向的是当前的子类实例
- 在子类静态方法中通过 super调用父类方法时,内部的this指向当前的子类,而非子类的实例
 
- 当作函数使用,代表调用父类的构造函数(注意,此时 
- ES5 原生构造函数无法继承(子类无法获得原生构造函数的内部属性)。具体原因是 ES5 中,是新建子类的实例对象 - this,再将父类的属性添加都子类上;而父类的内部属性无法获取,故无法继承:- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
 
- ES6 则允许继承上述原生构造函数了。原因是 ES6 中,是先新建父类的实例对象 - this,再用子类的构造函数修饰- this,使得父类的行为可以继承
模块
- 早期 JS 无模块管理机制,不利于开发大型项目
- 社区提供了 CommonJS 和 AMD 两种方案,分别用于服务端和浏览器端;二者都是运行时确定模块依赖
- ES6 模块的设计思想是尽可能静态化,编译时确定模块依赖关系及输入输出变量
- import使用注意:- import { foo } from 'mod'
- 导入的变量都是只读的
- 导入的路径既可以是相对路径,也可以是绝对路径
- .js后缀可省略
- 如果只是模块名称,无路径则需要配置文件告知 JS 引擎具体的模块位置
- 具有提升的效果
- 静态执行,无法使用表达式和变量
- Single 模式,有缓存机制
- 整体加载带别名:import * as mod from 'longNameMod'
 
- export default本质上是输出一个叫做- default的变量和方法,然后系统允许重名
- 在一条语句中同时输入默认方法和其它接口:import _, { each } from 'lodash'
- 复合写法:export { foo, bar } from 'my_mod',注意不会在当前模块导入这两个变量,而只是转发到外面了
模块加载规则
- 浏览器中可使用的方式: - <script src="path/to/foo.js" [async/defer]/>
- ES6 模块加载(默认就是 defer 模式):<script type="module" src="./mod.js"/>
- <script type="model">/可以正常写 import xx from xxx 这种,模块作用域/`
 
- ES6 与 CommonJS - 前者输出是值引用,后者输出的是值拷贝
- 前者是编译时输出接口,后者是运行时加载
- 前者是动态引用,后者会缓存值
- 在 Node 中 import是异步加载
- ES6 模块中,顶层 this指向undefined;后者则指向当前模块
 
二进制数组(类数组)
- 二进制数组组成对象: - ArrayBuffer对象:代表内存中的一段二进制数据,可通过「视图」操作
- TypedArray视图:可用于读写简单的二进制数据,包括 9 种类型视图:- Int8
- Uint8
- Uint8C:自动过滤溢出,- DataView视图不支持
- Int16
- Uint16
- Int32
- Uint32
- Float32
- Float64
 
- DateView视图:可自定义复合格式的视图,读写复杂类型的二进制数据