ES6 学习笔记(1)

本文是在 《ES6标准入门》(第二版) 之后的精简知识点,主要是学完之后的学习笔记。相对于原书主要在于精简。不过还是希望大家支持原作者,购买正版书籍。本人是购买的 kindle 版本。

let & const

用于变量声明,相对于原有 var 的区别是:

  1. 支持块级作用域。
  2. 不存在变量提升。
  3. 暂时性死区:在代码块内,使用 let 声明变量之前,该变量是不可用的。
  4. 不允许重复声明。
  5. const 用来声明常量,保证变量名指向的地址不变,并不保证该地址的数据不变。
  6. varfunction 声明的全局变量依旧是全局对象的属性,letconstclass 声明的全局变量不属于全局对象的属性。

变量的解构赋值

ES6 允许按照一定的模式,从数组和对象中提取值,对变量进行赋值。内部使用严格相等运算符(===)判断一个位置是否有值。

数组

两个变量互相交换值的语句:[b, a] = [a, b]

对象

下面语句中只声明了两个变量 c 和 d,其中 d 是默认值 0,a 和 b 均是模式匹配。而默认值如果是一个表达式,则这个表达式是惰性求值的,即只有在用到时才会求值。

1
const {a: { b: c, d: 0 } } = {a: { b: 3 } };

字符串的扩展

  1. includes(str[, startIndex]) 返回布尔值,表示是否找到了参数字符串。
  2. startsWith(str[, startIndex]) 返回布尔值,表示参数字符串是否在原字符串的头部。
  3. endsWith(str[, startIndex]) 返回布尔值,表示参数字符串是否在原字符串的尾部。
  4. repeat(n) 返回一个新字符串,表示将原字符串重复 n 次。
  5. 模板字符串
    a = `${}`;```
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    6. `String.raw()` 用来获取一个模板字符串的原始字面量值.
    #### 标签模板
    模板字符串形式可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为 "标签模板" 功能。是函数调用的一种特殊形式,紧跟在后面的模板字符串就是它的参数。
    ```javascript
    const a = 5;
    const b = 10;
    tag`Hello ${a + b} world ${a * b}`

tag 函数第一个参数是一个数组,数组成员是模板字符串中那些没有变量替换的部分。上述代码中,模板字符串含有两个变量,因此 tag 会接收到 value1 和 value2 两个参数。tag 函数的参数值如下:

  • 第一个参数: ['Hello', 'world']
  • 第二个参数:15
  • 第三个参数:50

可以用来处理用户输入的内容或者支持国际化。

ES7

  1. padStart(len, str) 原字符串未达到指定长度 len,会在头部补全 str。
  2. padEnd(len, str) 原字符串未达到指定长度 len,会在尾部补全 str。

正则的扩展

  1. 允许 RegExp 函数接受正则表达式作为参数,此时会返回一个原有正则表达式的拷贝。第二个参数的修饰符会更新原有的修饰符。
  2. 字符串对象中的 4 个可以使用正则表达式的方法:match、replace、search 和 split。ES6 将该4个方法在语言内部全部调用 RegExp 的实例方法,从而做到所有与正则相关的方法都定义在 RegExp 对象上。

    1
    2
    3
    4
    String.prototype.match // 调用 RegExp.prototype[Symbol.match]
    String.prototype.replace // 调用 RegExp.prototype[Symbol.replace]
    String.prototype.search // 调用 RegExp.prototype[Symbol.search]
    String.prototype.split // 调用 RegExp.prototype[Symbol.split]
  3. u修饰符,定义为 ‘Unicode模式’,用来正确处理大于 \uFFFF 的 Unicode 字符。使用 u 修饰符后,所有量词都会正确识别码点大于 0xFFFF 的 Unicode 字符。

  4. Unicode字符表示法:使用大括号表示 Unicode 字符的表示法,但在正则表达式中必须加上 u 修饰符才能识别。

    1
    /\u{61}/.test('a') // true
  5. 预定义模式:\S 匹配所有不是空格的字符。加了 u 修饰符,能正确匹配码点大于 0xFFFF 的 Unicode 字符。

  6. y修饰符,与 g 类似,全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置存在匹配就行,而 y 修饰符会确保必须从剩余的第一个位置开始匹配。与 y 修饰符想匹配,正则对象多了 sticky 属性,表示是否设置了 y 修饰符。
  7. ES6 新增了 flags 属性,返回正则表达式的修饰符。

数值的扩展

  1. 支持二进制和八进制表示法,分别用前缀 0b(或0B) 和 0o(或0O)表示。
  2. Number.isFinite() 和 Number.isNaN(),只对数值有效,非数值一律返回false。
  3. Number.parseInt() 和 Number.parseFloat() 移到 Number 对象上。
  4. Number.isSafeInteger() 判断一个数是否在 -2^(53)(Number.MIN_SAFE_INTEGER 表示) 到 2^(53)(Number.MAX_SAFE_INTEGER 表示) 之间(不含两个端点)。

Math 对象的扩展

  1. Math.trunc() 去除一个数的小数部分,返回整数部分。
  2. Math.hypot() 返回所有参数的平方和的平方根。Math.hypot(3, 4) //5

ES7

新增了一个指数运算符()。`2 3 //8`。

数组的扩展

  1. Array.from() 将两类对象转为真正的数组:类似数组的对象和可遍历对象(包括新增的 Set 和 Map)。可选的第二个参数,类似数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
  2. Array.of() 用于将一组值转换为数组。如果没有参数,就返回一个空数组。
  3. Array.prototype.copyWithin(target[, start, end]) 在当前数组内部将制定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。参数均是数字。

    1
    2
    3
    4
    5
    6
    target: 从该位置开始替换数据
    start: 从该位置开始读取数据,默认为0。负值表示倒数
    end: 从该位置前停止读取数据,默认等于数组长度。负值表示倒数。
    // 从3号位置直到数组结束的成员复制到从0号位置开始的位置。
    [1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
  4. Array.prototype.find(fn) 与 Array.prototype.findIndex(fn) 找出第一个符合条件的数组成员(位置)。

  5. Array.prototype.fill() 使用给定值填充数组。
  6. 数组实例的 entries()、keys() 和 values() 分别返回对键名、键值和键值对的遍历。

ES7

  1. Array.prototype.includes(val[, start]) 返回一个布尔值,表示某个数组是否包含给定的值。
  2. 数组推导
    1
    2
    const nums = [1, 2, 3, 4];
    [for (num of nums) if (num > 2) num]; // [3, 4]

数组的空位

数组的空位指的某一个位置没有任何值。例如 Array 构造函数返回的数组都是空位。空位不是 undefined,一个位置的值等于 undefined 依然是有值的。0 in [, , ,] // false。Array.from、扩展运算符(…)、entries()、keys()、values()、find() 和 findIndex() 会将数组的空位转为 undefined。copyWithin 会连空位一起复制。fill 会将空位视为正常的数组位置。

1
2
3
[...['a',,'b']] // ['a', undefined, 'b']
[, 'a'].find(x => true) // undefined
[, 'a'].findIndex(x => true) // 0

函数的扩展

  1. 参数的默认值 function test({x, y} = { x: 0, y: 0 })
  2. 指定了默认值以后,函数的 length 属性将返回没有指定默认值的参数个数。因为 length 的含义是,该函数预期传入的参数个数。同理,rest 参数也不会计入 length 属性。
  3. 如果参数默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则是一样的,即先当前函数的作用域,然后才是全局作用域。

    1
    2
    3
    4
    5
    var x = 1;
    function f(x, y=x) {
    console.log(y);
    }
    f(2); // 2
  4. ES6 引入了 rest 参数(...),用于获取函数的多余参数。rest 参数搭配的变量是一个数组,该变量将多余的参数放入其中。注意,rest 参数之后不能再有其它参数,即只能是最后一个参数,否则会报错。

扩展运算符

扩展运算符(...)相当于 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列(已经可以将一个对象转成所有 key-value 的形式)。扩展运算符内部调用的是数据结构的 Iterator 接口。如果没有 iterator 接口的对象使用扩展运算符,将会报错。

对象中的扩展运算符是浅复制,即如果一个键的值是一个对象的引用,那么扩展运算符复制的是这个值的引用,而不是这个值的副本。也不会复制继承自原型对象的属性。扩展运算符的参数对象中,如果有取值函数get,那么这个函数是会执行的。

箭头函数

ES6 允许使用 => 定义函数。const f = v => v

  1. 如果箭头函数的代码块部分多余一条语句,则使用大括号将其括起来,并使用 return 语句返回。
  2. 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
  3. 箭头函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象。
  4. 不可以当做构造函数,即不可以使用 new 命令,否则会报错。
  5. 不可以使用 arguments 对象,该对象在函数体内不存在。
  6. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

ES7

函数绑定运算符(::),左边是一个对象,右边是一个函数。该函数会自动将左边的对象作为上下文环境(即 this 对象)绑定到右边的函数上。如果双冒号左边为空,右边是一个对象的方法,则等于将该对象方法绑定在该对象上。

1
2
3
let method = obj:: obj.foo;
// 等同于
let method = :: obj.foo;

由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

1
2
3
4
5
let { find, html } = jake;
document.querySelector('div.test')
:: find('p')
:: html('qingguoing');

对象的扩展

  1. Object.is(a, b) 用来比较两个值是否严格相等,与严格相等运算符(===)的行为基本一致。不同之处:一是 +0 不等于 -0,而是 NaN 等于自身。
  2. Object.assign(target, source1 [, source2 ...]) 将源对象的所有可枚举属性复制到目标对象上。同名属性后面的会覆盖前面的属性。
    1
    2
    3
    4
    // 对于嵌套的对象,会替换而不是添加
    const target = {a : {b: '111', c: '222' }};
    const source = {a: {b": 'hello' }};
    Object.assign(target, source); // {a: {b: 'hello'}}

Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,它是 JavaScript 语言的第7种数据类型,前6种分别是:undefined、null、布尔值、字符串、数值和对象。Symbol 值通过 Symbol 函数生成。也就是说,对象的属性名现在可以有两种类型:一种是原来就有的字符串,另一种是新增的 Symbol 类型。只要属性名属于 Symbol 类型,就是独一无二的,可以保证不会与其他属性名产生冲突。

1
2
let s = Symbol();
typeof s // "symbol"

Symbol 函数前不能使用 new 命令,否则会报错。因为生成的 Symbol 是一个原始类型的值,不是对象。Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。主要是为了在控制台显示或转为字符串时比较容易区分,所以相同参数的 Symbol 函数的返回值是不相等的。Symbol 值不能与其他类型的值进行运算,否则会报错,但是可以显示转为字符串和布尔值,不能转为数值。

1
2
3
4
5
6
const sym = Symbol('qingguoing');
String(sym); // 'Symbol(qingguoing)'
sym.toString(sym); // 'Symbol(qingguoing)'
Boolean(sym); // true
!sym // false

Symbol 作为属性名,不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames() 返回。但它也不是私有属性,Object.getOwnPropertySymbols() 可以获取指定对象的所有 Symbol 属性名。Reflect.ownKeys(object) 方法可以返回所有类型的键名,包括常规键名和 Symbole 键名。

1
2
3
4
5
6
let obj = {
[Symbol('key')]: 1,
enum: 2,
test: 3,
};
Reflect.ownKeys(obj); // [Symbol(key), 'enum', 'test']
  1. Symbol.for(str) 搜索有没有以 str 为名称的 Symbol 值,有就返回这个 Symbol 值,否则就新建并返回一个已该字符串为名称的 Symbol 值。Symbol.for 为 Symbol 值登记的名字是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。
  2. Symbol.keyFor(symbol) 返回一个已登记的 Symbol 类型值的 key。

内置的 Symbol 值

ES6 提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

  1. Symbol.hasInstance 对象使用 instanceof 运算符时会调用这个方法,判断该对象是否为某个构造函数的实例。
  2. Symbol.isConcatSpreadable 布尔值,默认 true,表示该对象使用 Array.prototype.concat() 时是否可以展开。

    1
    2
    3
    4
    let arr1 = ['a', 'b'];
    let arr2 = ['c', 'd'];
    arr2[Symbol.isConcatSpreadable] = false;
    ['a', 'b'].concat(arr2, 'e'); // ['a', 'b', ['c', 'd'], 'e']
  3. Symbol.species 指向一个方法,对象作为构造函数构造实例时会调用这个方法。即如果 this.constructor[Symbol.species] 存在,就会使用这个属性作为构造函数来创造新的实例对象。

    1
    2
    3
    4
    // 默认读取器
    static get [Symbol.species]() {
    return this;
    }
  4. Symbol.match 函数,当执行 str.match(myObject) 时,如果该属性存在,会调用它返回该方法的返回值。

    1
    2
    3
    String.prototype.match(regexp);
    // 等同于
    regexp[Symbol.match](this);
  5. Symbol.replace 当对象被 String.prototype.replace 方法调用时会返回该方法的返回值。

  6. Symbol.search 当对象被 String.prototype.search 方法调用时会返回该方法的返回值。
  7. Symbol.split 当对象被 String.prototype.split 方法调用时会返回该方法的返回值。
  8. Symbol.iterator 对象的该属性指向其默认遍历器方法,即对象在进行 for…of 循环时会调用这个方法,返回该对象的默认遍历器。
  9. Symbol.toPrimitive 对象被转为原始类型值时会调用这个方法,返回该对象对应的原始类型值。Symbol.toPrimitive 被调用时会接受一个字符串参数,表示当前运算的模式。一共有三种模式。
  • Number 转成数值。
  • String 转成字符串。
  • Default 可以转成数值,也可以转成字符串。
  1. Symbol.toStringTag 在对象上调用 Object.prototype.toString 方法时,如果这个属性存在,其返回值会出现在 toString 方法返回的字符串中,表示对象的类型。即这个属性可用于定制 [object Object][object Array] 中 object 后面的字符串。

    1
    ({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]"
  2. Symbol.unscopables 指向一个对象,指定了使用 with 关键字时哪些属性会被 with 环境排除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyClass {
    foo() { return 1; }
    get [Symbol.unscopables] {
    return { foo: true };
    }
    }
    const foo = () => 2;
    with(MyClass.prototype) {
    foo(); // 2
    }

Proxy 和 Reflect

Proxy

Proxy 用于修改某些操作的默认行为,可以理解成在目标对象前架设一”拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

1
let proxy = new Proxy(target, handler);

Proxy 对象的所有用法都是上面这种形式,不同的只是 handler 参数的写法。其中,new Proxy() 表示生成一个 Proxy 实例,target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。注意,要使 Proxy 起作用,必须针对 Proxy 实例进行操作,而不是针对目标对象。如果 handler 没有任何拦截,那就等同于直接通向原对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
let proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time; // 35
proxy.name; // 35
proxy.title; // 35
// Proxy 实例也可以作为其他对象的原型对象
let obj = Object.create(proxy);
obj.time; // 35

同一个拦截器函数可以设置拦截多个操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const hanler = {
get(target, name) {
if (name === 'prototype') return Object.prototype;
return `hello ${name}`;
},
apply(target, thisBinding, args) {
return args[0];
},
construct(target, args) {
return args[1];
},
};
const fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(1, 2); // 11
new fproxy(1, 2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'hello foo'

Proxy 支持的拦截操作:

  1. get(target, propKey[, receiver]) 拦截对象属性的读取,返回类型不限。当 target 对象设置了 propKey 属性的 get 函数时,receiver 对象会绑定 get 函数的 this 对象。
  2. set(target, propKey, value, receiver) 拦截对象属性的设置,返回一个布尔值。返回 false 时,严格模式下的赋值操作会报错。
  3. has(target, proKey) 拦截 propKey in proxy 的操作,返回一个布尔值。如果原对象 target 不可配置或禁止扩展,has 拦截会报错。
  4. deleteProperty(target, propKey) 拦截 delete proxy[propKey] 的操作,返回一个布尔值。
  5. enumerate(target) 拦截 for(var x in proxy),返回一个遍历器。返回的不是一个对象,就会报错。
  6. hasOwn(target, propKey) 拦截 proxy.hasOwnProperty(‘foo’),返回一个布尔值。
  7. ownKeys(target) 拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。
  8. getOwnPropertyDescriptor(target, propKey) 拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  9. defineProperty(target, propKey, propDesc) 拦截 Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDesc),返回一个布尔值。
  10. getPrototypeOf(target) 拦截 Object.getPrototypeOf(proxy),返回一个对象。
  11. isExtensible(target) 拦截 Object.isExtensible(proxy),返回一个布尔值。若 Object.isExtensible(proxy) === Object.isExtensible(target) 条件不满足,就会报错。
  12. preventExtensions(target) 拦截 Object.preventExtensions(proxy),返回一个布尔值。只有当 Object.isExtensible(proxy) 为 false 时,proxy.preventExtensions 才能返回true,否则报错。为了防止出错,通常需要在 proxy.preventExtensions 方法中调用一次 Object.preventExtensions。

    1
    2
    3
    4
    5
    6
    let p = new Proxy({}, {
    preventExtensions(target) {
    Object.preventExtensions(target);
    return true;
    }
    });
  13. setPrototypeOf(target, proto) 拦截 Object.setPrototypeOf(proxy, proto),返回一个布尔值。

如果目标对象是函数,另外提供以下两种操作可以拦截:

  1. apply(target, object, args) 拦截 proxy 作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
  2. construct(target, args, proxy) 拦截 proxy 作为构造函数调用的操作。若返回的不是对象,就会抛出错误。

另外,Proxy.revocable() 返回一个可取消的 Proxy 实例。该方法返回一个对象,其中 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例。

1
2
3
4
5
6
7
let target = {};
let handler = {};
let { proy, revoke } = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked

Reflect

ES6 为了操作对象而提供的新 API。Reflect 对象的设计目的有以下几个:

  1. 将 Object 对象的一些明显属于语言层面的放到 Reflect 对象上。
  2. 修改某些 Object 方法的返回结果,让其变得更合理。
  3. 让 Object 操作都变成函数行为。
  4. Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法完成默认行为,作为修改行为的基础。