JavaScript ES6 学习笔记

引言

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);而 undefinednull 无法转换为对象,所以会报错
  • 函数的参数也支持解构赋值

字符串

  • "\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.isFiniteNumber.isNaN 方法,但这两种方法只对数值有效,其它一律为 false。注意也有一对全局的函数 isFiniteisNaN,它们则会尝试先将输入的参数转换为 Number 后再进行判断,这是最重要的区别
  • ES6 将 parseIntparseFloat 挪到 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
      8
      function 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
  • flatMapflat 类似,但是会对每个元素执行一次回调函数,但它只能展开一层

对象

  • 属性或者方法都可以简写啦:

    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
2
3
4
5
const userType = {
guest: Symbol(),
org: Symbol(),
member: Symbol(),
}
  • 由于不会被常规的方法如 getOwnPropertyNames 等遍历到,但其实又是公开。这种特性可以用来创建一些非私有、仅希望内部调用的方法
  • Symbol.forSymbol.keyFor:复用相同参数构建的 Symbol,它会在全局注册参数,并在创建前进行搜索,如果存在则返回,否则新建。注意这个全局是全局环境,可以在不同的 iframeservice 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 表达式本身无返回值,或者总返回 undefinednext 方法可以带参数,作为 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 视图:可自定义复合格式的视图,读写复杂类型的二进制数据

参考

0%