es6学习笔记

ECMAScript 6.0( 以下简称ES6) 是JavaScript语言的下一代标准。

ECMAScript和JavaScript的关系是, 前者是后者的规格, 后者是前者的一种实现( 另外的ECMAScript方言还有Jscript和ActionScript) 。 日常场合, 这两个词是可以互换的。

在前端工程化的现在,学习es6还是有必要的。
本文为个人根据阮老师的es6标准入门学习笔记。

ES6

let和const命令

let

  1. let用来声明变量。 它的用法类似于var, 但是所声明的变量, 只在let命令所在的代码块内有效
  2. 在循环中,如果变量i是var声明的, 在全局范围内都有效。 所以每一次循环,新的i值都会覆盖旧值,如果变量i是let声明的, 当前的i只在本轮循环有效, 所以每一次循环的i其实都是一个新的变量。
  3. let不像var那样会发生“变量提升”现象。 所以, 变量一定要在声明后使用, 否则报错
  4. let不允许在相同作用域内, 重复声明同一个变量
//所声明的变量, 只在let命令所在的代码块内有效
{
    let a = 10;
    var b = 1;
    console.log('a=' + a + '\nb=' + b);

}

console.log('let代码块外b=' + b);
// console.log('let代码块外b=' + a);

arr = [1, 2, 3, 4, 5, 6, 4];
for (let i = 0; i < arr.length; i++) {
    console.log(i);
}

/**
 * 变量i是var声明的, 在全局范围内都有效。 所以每一次循环,
 * 新的i值都会覆盖旧值, 导致最后输出的是最后一轮的i的值
 */
var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = i;
}
console.log(i);

/**
 * 变量i是let声明的, 当前的i只在本轮循环有效, 所以每一次循环的i其实都是一个新的变量, 所以最后输出的是6
 * @type {Array}
 */
var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = i;
}
console.log(a[6]);

/**
 * 只要块级作用域内存在let命令, 它所声明的变量就“绑定”( binding) 这个区域, 不再受外部的影响
 *存在全局变量tmp, 但是块级作用域内let又声明了一个局部变量tmp, 导致后者绑定这个块级作用域, 所以在let声明变量前, 对tmp赋
 值会报错。
 如果区块中存在let和const命令, 这个区块对这些命令声明的变量, 从一开始就形成了封闭作用域。 凡是在声明之前就使用这些变
 量, 就会报错。
 总之, 在代码块内, 使用let命令声明变量之前, 该变量都是不可用的
 暂时性死区”也意味着typeof不再是一个百分之百安全的操作
 * @type {number}
 */
var tmp = 123;
if (true) {
    //tmp = 'abc'; // ReferenceError
    let tmp;
}

/**
 *调用bar函数之所以报错
 *参数x默认值等于另一个参数y, 而此时y还没有声明
 * @param x
 * @param y
 * @returns {[null,null]}
 */
function bar(x = y, y = 2) {
    return [x, y];
}

//bar();  //报错

function bar(x = 2, y = x) {
    return [x, y];
}
bar();

/**
 * let不允许在相同作用域内, 重复声明同一个变量,都会报错
 */
// function () {
//     let a = 10;
//     var a = 1;
// }
//
// function () {
//     let a = 10;
//     let a = 1;
// }

结果为:


let结果

const

  1. const声明一个只读的常量。 一旦声明, 常量的值就不能改变。
  2. const声明的变量不得改变值, 这意味着, const一旦声明变量, 就必须立即初始化, 不能留到以后赋值。
  3. onst的作用域与let命令相同: 只在声明所在的块级作用域内有效。
  4. const命令声明的常量也是不提升, 同样存在暂时性死区, 只能在声明的位置后面使用
  5. const声明的常量, 也与let一样不可重复声明
const PI=3.1415;

console.log(PI);
/**
 * 量a是一个数组, 这个数组本身是可写的, 但是如果将另一个数组赋值给a, 就会报错
 * @type {Array}
 */
const a=[];
a.push('hello');
//a = ['Dave'];

全局对象的属性

  1. var命令和function命令声明的全局变量, 依旧是全局对象的属性
  2. let命令、 const命令、 class命令声明的全局变量, 不属于全局对象的属性
var a = 1;
// 如果在Node的REPL环境, 可以写成global.a
// 或者采用通用方法, 写成this.a
this.a // 1
let b = 1;
//window.b // undefined

变量的解构赋值

数组的解构赋值

  1. ES6允许按照一定模式, 从数组和对象中提取值, 对变量进行赋值, 这被称为解构( Destructuring)
/**
 * 同时给abc赋值可以用一下方式
 */
var [a, b, c] = [1, 2, 3];
console.log('a='+a+'  b='+b+'  c='+c);

/**
 * 这种写法属于“模式匹配”, 只要等号两边的模式相同, 左边的变量就会被赋予对应的值
 */
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log('foo='+foo+'  bar='+bar+'  baz='+baz);

let [ , , third] = ["foo", "bar", "baz"];
console.log('third='+third);

let [x, , y] = [1, 2, 3];
console.log('x='+x+'  y='+y);

let [head, ...tail] = [1, 2, 3, 4];
console.log('head='+head+' tail='+tail);

/**
 *另一种情况是不完全解构, 即等号左边的模式, 只匹配一部分的等号右边的数组
 */
let [x2, y2] = [1, 2, 3];
console.log('x2='+x2+'  y2='+y2);

let [a2, [b2], c2] = [1, [2, 3], 4];
console.log('a2='+a2+'  b='+b2+'  c2='+c2);

结果为:


数组的解构赋值

注:

  • 只要某种数据结构具有Iterator接口, 都可以采用数组形式的解构赋值
  • 解构赋值允许指定默认值,ES6内部使用严格相等运算符( ===) , 判断一个位置是否有值。 所以, 如果一个数组成员不严格等于undefined, 默认值是不会生效的

对象的解构赋值

解构不仅可以用于数组, 还可以用于对象

  1. 对象的解构与数组有一个重要的不同。 数组的元素是按次序排列的, 变量的取值由它的位置决定; 而对象的属性没有次序, 变量必须与属性同名, 才
    能取到正确的值。
  2. 对象的解构也可以指定默认值。默认值生效的条件是, 对象的属性值严格等于undefined。
var {foo, bar} = {foo: "aaa", bar: "bbb"};
console.log('foo=' + foo + ' bar=' + bar);

var {foo2: foo2, bar2: bar2} = {foo2: "aaa", bar2: "bbb"};
console.log('foo2=' + foo2 + ' bar2=' + bar2);
/**
 * 真正被赋值的是变量baz, 而不是模式foo。
 */
var {foo: baz} = {foo: "aaa", bar: "bbb"};
console.log(baz);

/**
 * 和数组一样, 解构也可以用于嵌套结构的对象
 * 这时p是模式, 不是变量, 因此不会被赋值
 * @type {{p: [string,null]}}
 */
var obj = {
    p: [
        'Hello',
        {y: 'World'}
    ]
};
var {p: [x, {y}]} = obj;
console.log('x=' + x + ' y=' + y);
/**
 * line和column是变量, loc和start都是模式, 不会被赋值
 * @type {{loc: {start: {line: number, column: number}}}}
 */
var node = {
    loc: {
        start: {
            line: 1,
            column: 5
        }
    }
};
var {loc: {start: {line, column}}} = node;
console.log('line=' + line + ' column=' + column);

/**
 * 嵌套赋值
 *  let命令下面一行的圆括号是必须的, 否则会报错。 因为解析器会将起首的大括号, 理解成一个代码块, 而不是赋值语句。
 * @type {{}}
 */
let obj2 = {};
let arr = [];
({foo: obj2.prop, bar: arr[0]} = {foo: 123, bar: true});
console.log('obj2.prop='+obj2.prop+' arr='+arr);

/**
 * 对象的解构赋值, 可以很方便地将现有对象的方法, 赋值到某个变量
 * 将Math对象的对数、 正弦、 余弦三个方法, 赋值到对应的变量上
 */
let { log, sin, cos } = Math;

结果为:


对象解析赋值

字符串的解构赋值

字符串也可以解构赋值。 这是因为此时, 字符串被转换成了一个类似数组的对象

注:类似数组的对象都有一个length属性, 因此还可以对这个属性解构赋值

let [a, b, c, d, e] = 'hello';
let {length : len} = 'hello';
console.log(a+b+c+d+e+'  length='+len);

结果:

hello  length=5

函数参数的解构赋值

函数的参数也可以使用解构赋值。

函数add的参数表面上是一个数组, 但在传入参数的那一刻, 数组参数就被解构成变量x和y

function add([x, y]) {
    return x + y;
}

console.log(add([1, 2]));

/**
 * 使用默认值
 * @param x
 * @param y
 * @returns {[null,null]}
 */
function move({x = 0, y = 0} = {}) {
    return [x, y];
}

圆括号问题

只要有可能导致解构的歧义, 就不得使用圆括号

不能使用圆括号的情况

  1. 变量声明语句中, 不能带有圆括号
  2. 函数参数中, 模式不能带有圆括号。
  3. 赋值语句中, 不能将整个模式, 或嵌套模式中的一层, 放在圆括号之中。
// 全部报错
var [(a)] = [1];
var {x: (c)} = {};
var ({x: c}) = {};
var {(x: c)} = {};
var {(x): c} = {};
var { o: ({ p: p }) } = { o: { p: 2 } };

// 报错
function f([(z)]) { return z; }

// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];

可以使用圆括号的情况

赋值语句的非模式部分, 可以使用圆括号。

[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确

变量的解构赋值用途

交换变量的值

[x, y] = [y, x];

从函数返回多个值

// 返回一个数组
function example() {
    return [1, 2, 3];
}

var [a, b, c] = example();

// 返回一个对象
function example() {
    return {
        foo: 1,
        bar: 2
    };
}

var {foo, bar} = example();

提取JSON数据

var jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
};
let {id, status, data: number} = jsonData;

遍历Map结构

任何部署了Iterator接口的对象, 都可以用for...of循环遍历。 Map结构原生支持Iterator接口, 配合变量的解构赋值, 获取键名和键值就非常方便。

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
    console.log(key + " is " + value);
}

如果只想获取键名, 或者只想获取键值, 可以写成下面这样

// 获取键名
for (let [key] of map) {
    // ...
} 
// 获取键值
for (let [,value] of map) {
    // ...
}

字符串

字符串的遍历器接口

字符串可以被for...of循环遍历

for (let codePoint of 'hello') {
    console.log(codePoint)
}

常用的新方法

includes(), startsWith(), endsWith()

  • includes(): 返回布尔值, 表示是否找到了参数字符串
  • startsWith(): 返回布尔值, 表示参数字符串是否在源字符串的头部
  • endsWith(): 返回布尔值, 表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
  • 三个方法都支持第二个参数, 表示开始搜索的位置
var s = 'Hello world!';
s.startsWith('world', 6) // true 
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

repeat()

repeat 方法返回一个新字符串, 表示将原字符串重复n 次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

模板字符串

模板字符串( template string) 是增强版的字符串, 用反引号( `) 标识。 它可以当作普通字符串使用, 也可以用来定义多行字符串, 或者在字符串中
嵌入变量

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

//可以放入任意的JavaScript表达式, 可以进行运算
var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

//调用函数
function fn() {
    return "Hello World";
} 
`foo ${fn()} bar`

注:

  • 如果在模板字符串中需要使用反引号, 则前面要用反斜杠转义
  • 如果使用模板字符串表示多行字符串, 所有的空格和缩进都会被保留在输出之中
  • 模板字符串中嵌入变量, 需要将变量名写在${}之中
  • 大括号内部可以放入任意的JavaScript表达式, 可以进行运算, 以及引用对象属性
  • 模板字符串之中还能调用函数

数值扩展

Math对象的扩展

Math.trunc()

Math.trunc方法用于去除一个数的小数部分, 返回整数部分

Math.sign()

Math.sign方法用来判断一个数到底是正数、 负数、 还是零

Math.cbrt()

Math.cbrt方法用于计算一个数的立方根

数组的扩展

Array.from()

Array.from方法用于将两类对象转为真正的数组: 类似数组的对象( array-like object) 和可遍历( iterable) 的对象( 包括ES6新增的数据结构Set和
Map) 。

//常见的类似数组的对象是DOM操作返回的NodeList集合
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
    console.log(p);
});

//只要是部署了Iterator接口的数据结构, Array.from都能将其转为数组
Array.from('hello')
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

数组实例的find()和findIndex()

数组实例的find方法, 用于找出第一个符合条件的数组成员。 它的参数是一个回调函数, 所有数组成员依次执行该回调函数, 直到找出第一个返回值
为true的成员, 然后返回该成员。 如果没有符合条件的成员, 则返回undefined。

数组实例的fill()

fill方法使用给定值, 填充一个数组

  • fill方法还可以接受第二个和第三个参数, 用于指定填充的起始位置和结束位置

数组实例的entries(), keys()和values()

可以用for...of循环进行遍历, 唯一的区别是keys()是对键名的遍历、 values()是对键值的遍历, entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
    console.log(index);
} 
// 0
// 1
for (let elem of ['a', 'b'].values()) {
    console.log(elem);
} 
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
    console.log(index, elem);
} 
// 0 "a"
// 1 "b"

//如果不使用for...of循环, 可以手动调用遍历器对象的next方法, 进行遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

数组实例的includes()

表示某个数组是否包含给定的值, 与字符串的includes方法类似

[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true

该方法的第二个参数表示搜索的起始位置, 默认为0。 如果第二个参数为负数, 则表示倒数的位置, 如果这时它大于数组长度( 比如第二个参数为-4,
但数组长度为3) , 则会重置为从0开始

[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

函数的扩展

函数参数的默认值

ES6允许为函数的参数设置默认值, 即直接写在参数定义的后面

function log(x, y = 'World') {
    console.log(x, y);
} 
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

rest参数

ES6引入rest参数( 形式为“...变量名”) , 用于获取函数的多余参数, 这样就不需要使用arguments对象了。 rest参数搭配的变量是一个数组, 该变量将多余的参数放入数组中

//利用rest参数, 可以向该函数传入任意数目的参数
function add(...values) {
    let sum = 0;
    for (var val of values) {
        sum += val;
    } 
    return sum;
} 

add(2, 5, 3) // 10

//rest参数中的变量代表一个数组, 所以数组特有的方法都可以用于这个变量
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
        console.log(item);
    });
}
var a = [];
push(a, 1, 2, 3)

注:rest参数之后不能再有其他参数( 即只能是最后一个参数)

扩展运算符

扩展运算符( spread) 是三个点( ...) 。 它好比rest参数的逆运算, 将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

//合并数组
[1, 2, ...more]
[...arr1, ...arr2, ...arr3]

//转为真正的数组。
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

//map转数组
let map = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]

箭头函数

使用“箭头”( =>) 定义函数

var f = v => v;
//等同于
// var f = function (v) {
//     return v;
// };

//如果箭头函数不需要参数或需要多个参数, 就使用一个圆括号代表参数部分
var f2 = () => 5;
// 等同于
// var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
// var sum = function(num1, num2) {
//     return num1 + num2;
// };

//如果箭头函数的代码块部分多于一条语句, 就要使用大括号将它们括起来, 并且使用return语句返回
var sum = (num1, num2) => {
    return num1 + num2;
}

//由于大括号被解释为代码块, 所以如果箭头函数直接返回一个对象, 必须在对象外面加上括号
var getTempItem = id => ({id: id, name: "Temp"});
console.log(getTempItem(5));

const full = ({ first, last }) => first + ' ' + last;
// 等同于
// function full(person) {
//     return person.first + ' ' + person.last;
// }

console.log([1,2,3].map(x => x * x));

注:

  • 函数体内的this对象, 就是定义时所在的对象, 而不是使用时所在的对象。
  • 不可以当作构造函数, 也就是说, 不可以使用new命令, 否则会抛出一个错误

函数绑定

箭头函数可以绑定this对象, 大大减少了显式绑定this对象的写法( call、 apply、 bind) 。

函数绑定运算符是并排的两个双冒号( ::) , 双冒号左边是一个对象, 右边是一个函数。 该运算符会自动将左边的对象, 作为上下文环境( 即this对象) , 绑定到右边的函数上面。

foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

//如果双冒号左边为空, 右边是一个对象的方法, 则等于将该方法绑定在该对象上面。
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);

尾调用优化

尾调用

某个函数的最后一步是调用另一个函数

function f(x){
    return g(x);
}
//函数f的最后一步是调用函数g, 这就叫尾调用

function f(x) {
    if (x > 0) {
        return m(x)
    } 
    return n(x);
}
//函数m和n都属于尾调用, 因为它们都是函数f的最后一步操作

Set和Map数据结构

Set

提供了新的数据结构Set。 它类似于数组, 但是成员的值都是唯一的, 没有重复的值

//Set本身是一个构造函数, 用来生成Set数据结构
let s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
    console.log(i);
}

//Set函数接受数组作为参数
//Set函数可以接受一个数组( 或类似数组的对象) 作为参数, 用来初始化
var set = new Set([1, 2, 3, 4, 4]);

// 例二
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(items.size) // 5

//接受类似数组的对象作为参数
// 例三
function divs() {
    return [...document.querySelectorAll('div')];
}

var set2 = new Set(divs());
console.log(set2.size); // 56
// 类似于
divs().forEach(div => set.add(div));
console.log(set2.size); // 56

Set实例的属性和方法

属性
  • Set.prototype.constructor: 构造函数, 默认就是Set函数
  • Set.prototype.size: 返回Set实例的成员总数。
方法
  1. 操作方法:
  • add(value): 添加某个值, 返回Set结构本身
  • delete(value): 删除某个值, 返回一个布尔值, 表示删除是否成功
  • has(value): 返回一个布尔值, 表示该值是否为Set的成员
  • clear(): 清除所有成员, 没有返回值
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
  1. 遍历方法
  • keys(): 返回键名的遍历器
  • values(): 返回键值的遍历器
  • entries(): 返回键值对的遍历器
  • forEach(): 使用回调函数遍历每个成员
let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
    console.log(item);
} 
// red
// green
// blue

for (let item of set.values()) {
    console.log(item);
} 
// red
// green
// blue

for (let item of set.entries()) {
    console.log(item);
} 
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2) )
// 2
// 4
// 6

注:由于Set结构没有键名, 只有键值( 或者说键名和键值是同一个值) , 所以key方法和value方法的行为完全一致。

  1. 应用
//扩展运算符( ...) 内部使用for...of循环, 所以也可以用于Set结构
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']

//去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]

//数组的map和filter方法也可以用于Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构: {2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构: {2, 4}

//并集( Union) 、 交集( Intersect) 和差集( Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

Map

Map结构提供了“值—值”的对应, 是一种更完善的Hash结构实现。 如果你需要“键值对”的
数据结构,请使用Map

var m = new Map();
var o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false

// Map也可以接受一个数组作为参数。 该数组的成员是一个个表示键值对的数组
var map = new Map([
    ['name', '张三'],
    ['title', 'Author']
]);

console.log(map); //Map { 'name' => '张三', 'title' => 'Author' }
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

//如果对同一个键多次赋值, 后面的值将覆盖前面的值
let map = new Map();
map
    .set(1, 'aaa')
    .set(1, 'bbb');
map.get(1) // "bbb"

属性和操作方法

size属性

size属性返回Map结构的成员总数。

let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
set(key, value)

set方法设置key所对应的键值, 然后返回整个Map结构。 如果key已经有值, 则键值会被更新, 否则就新生成该键

var m = new Map();
m.set("edition", 6) // 键是字符串
m.set(262, "standard") // 键是数值
m.set(undefined, "nah") // 键是undefined
get(key)

get方法读取key对应的键值, 如果找不到key, 返回undefined

var m = new Map();
var hello = function() {console.log("hello");}
m.set(hello, "Hello ES6!") // 键是函数
m.get(hello) // Hello ES6!
has(key)

has方法返回一个布尔值, 表示某个键是否在Map数据结构中

var m = new Map();
m.set("edition", 6);
m.set(262, "standard");
m.set(undefined, "nah");
m.has("edition") // true
m.has("years") // false
m.has(262) // true
m.has(undefined) // true
delete(key)

delete方法删除某个键, 返回true。 如果删除失败, 返回false。

var m = new Map();
m.set(undefined, "nah");
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
clear()

clear方法清除所有成员, 没有返回值

let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0

遍历

  • keys(): 返回键名的遍历器。
  • values(): 返回键值的遍历器
  • entries(): 返回所有成员的遍历器。
  • forEach(): 遍历Map的所有成员
let map = new Map([
    ['F', 'no'],
    ['T', 'yes'],
]);
for (let key of map.keys()) {
    console.log(key);
} 
// "F"
// "T"
for (let value of map.values()) {
    console.log(value);
} 
// "no"
// "yes"
for (let item of map.entries()) {
    console.log(item[0], item[1]);
} 
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
    console.log(key, value);
} 
// 等同于使用map.entries()
for (let [key, value] of map) {
    console.log(key, value);
}

map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});

结合数组的map方法、 filter方法, 可以实现Map的遍历和过滤( Map本身没有map和filter方法)

let map0 = new Map()
    .set(1, 'a')
    .set(2, 'b')
    .set(3, 'c');
let map1 = new Map(
    [...map0].filter(([k, v]) => k < 3)
);
// 产生Map结构 {1 => 'a', 2 => 'b'}
let map2 = new Map(
    [...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}

map与其他数据结构的互相转换:

  1. Map转为数组
    Map转为数组最方便的方法, 就是使用扩展运算符( ...) 。
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  1. 数组转为Map
    将数组转入Map构造函数, 就可以转为Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
  1. Map转为JSON
    Map转为JSON要区分两种情况:
  • Map的键名都是字符串, 这时可以选择转为对象JSON
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
} 
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
  • Map的键名有非字符串, 这时可以选择转为数组JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]);
} 
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
  1. JSON转为Map
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
} 
jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}

Generator 函数

Generator函数是ES6提供的一种异步编程解决方案, 语法行为与传统函数完全不同。

执行Generator函数会返回一个遍历器对象, 也就是说, Generator函数除了状态机,还是一个遍历器对象生成函数。 返回的遍历器对象, 可以依次遍历Generator函数内部的每一个状态。

形式上, Generator函数是一个普通函数, 但是有两个特征。

  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield语句, 定义不同的内部状态( yield语句在英语里的意思就是“产出”) 。

Generator函数的调用方法与普通函数一样, 也是在函数名后面加上一对圆括号。 不同的是, 调用Generator函数后, 该函数并不执行, 返回的也不是函数运行结果, 而是一个指向内部状态的指针对象, 也就是上一章介绍的遍历器对象( Iterator Object)

必须调用遍历器对象的next方法, 使得指针移向下一个状态。 也就是说, 每次调用next方法, 内部指针就从函数头部或上一次停下来的地方开始执行, 直到遇到下一个yield语句( 或return语句) 为止。 换言之, Generator函数是分段执行的,yield语句是暂停执行的标记, 而next方法可
以恢复执行。

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
} 
var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

yield语句

遍历器对象的next方法的运行逻辑如下

  • 遇到yield语句, 就暂停执行后面的操作, 并将紧跟在yield后面的那个表达式的值, 作为返回的对象的value属性值
  • 下一次调用next方法时, 再继续往下执行, 直到遇到下一个yield语句。
  • 如果没有再遇到新的yield语句, 就一直运行到函数结束, 直到return语句为止, 并将return语句后面的表达式的值, 作为返回的对象的value属性值。
  • 如果该函数没有return语句, 则返回的对象的value属性值为undefined

注: yield语句不能用在普通函数中, 否则会报错

Promise对象

所谓Promise, 简单说就是一个容器, 里面保存着某个未来才会结束的事件( 通常是一个异步操作) 的结果。 从语法上说, Promise是一个对象, 从它可以获取异步操作的消息

Promise对象有以下两个特点:

  • 对象的状态不受外界影响。 Promise对象代表一个异步操作, 有三种状态: Pending( 进行中) 、 Resolved( 已完成, 又称Fulfilled)和Rejected( 已失败) 。 只有异步操作的结果, 可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态。 这也是Promise这个名字的由来, 它的英语意思就是“承诺”, 表示其他手段无法改变
  • 一旦状态改变, 就不会再变, 任何时候都可以得到这个结果。 Promise对象的状态改变, 只有两种可能: 从Pending变为Resolved和从Pending变为Rejected。 只要这两种情况发生, 状态就凝固了, 不会再变了, 会一直保持这个结果。 就算改变已经发生了, 你再对Promise对象添加回调函数, 也会立即得到这个结果。 这与事件( Event) 完全不同, 事件的特点是, 如果你错过了它, 再去监听, 是得不到结果的。
//创造了一个Promise实例。
var promise = new Promise(function(resolve, reject) {
    // ... some code
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});

//可以用then方法分别指定Resolved状态和Reject状态的回调函数
promise.then(function(value) {
    // success
}, function(error) {
    // failure
});

//用Promise对象实现的Ajax操作的例子
var getJSON = function (url) {
    var promise = new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if(this.status === 200)
            {
                resolve(this.response);
            }
        else
            {
                reject(new Error(this.statusText));
            }
        };
    });
    return promise;
};
getJSON("/posts.json").then(function (json) {
    console.log('Contents: ' + json);
}, function (error) {
    console.error('出错了', error);
});

async函数

async函数就是Generator函数的语法糖。

async函数就是将Generator函数的星号( *) 替换成async, 将yield替换成await, 仅此而已

//async函数返回一个Promise对象, 可以使用then方法添加回调函数。 当函数执行的时候, 一旦遇到await就会先返回, 等到触发的异步操作完成, 再接着执行函数体内后面的语句。
async function getStockPriceByName(name) {
    var symbol = await getStockSymbol(name);
    var stockPrice = await getStockPrice(symbol);
    return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
    console.log(result);
});

//指定多少毫秒后输出一个值
function timeout(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

async function asyncPrint(value, ms) {
    await timeout(ms);
    console.log(value)
}

asyncPrint('hello world', 50);

//Async函数有多种使用形式。
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
// 箭头函数
const foo = async () => {};

Class

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `x=${this.x}    y=${this.y}`;
    }
}

let point = new Point(1, 2);
console.log(point.toString()); //x=1    y=2

注:定义“类”的方法的时候, 前面不需要加上function这个关键字, 直接把函数定义放进去了就可以了。 另外, 方法之间不需要逗号分隔, 加了会报错。

constructor方法

constructor方法是类的默认方法, 通过new命令生成对象实例时, 自动调用该方法。 一个类必须有constructor方法, 如果没有显式定义, 一个空的constructor方法会被默认添加

class Foo {
    constructor() {
        return Object.create(null);
    }
} 
new Foo() instanceof Foo

Class的继承

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
    }

    toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
    }
}

注:

  • 子类必须在constructor方法中调用super方法, 否则新建实例时会报错。 这是因为子类没有自己的this对象, 而是继承父类的this对象, 然后对其进行加工
  • 在子类的构造函数中, 只有调用super之后, 才可以使用this关键字, 否则会报错

Module

模块功能主要由两个命令构成: export和import。 export命令用于规定模块的对外接口, import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。 该文件内部的所有变量, 外部无法获取。 如果你希望外部能够读取模块内部的某个变量, 就必须使用export关键字输出该变量

export

//用export命令对外部输出了三个变量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

//另外一种写法
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};

//export命令除了输出变量, 还可以输出函数或类( class) 。
export function multiply(x, y) {
    return x * y;
};

//export输出的变量就是本来的名字, 但是可以使用as关键字重命名。
function v1() { ... }
function v2() { ... }
export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion
};

//export命令规定的是对外的接口, 必须与模块内部的变量建立一一对应关系
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};

import

使用export命令定义了模块的对外接口以后, 其他JS文件就可以通过import命令加载这个模块( 文件)
import命令接受一个对象( 用大括号表示) , 里面指定要从其他模块导入的变量名。 大括号里面的变量名, 必须与被导入模块( profile.js) 对外接口的名称相同。

//从profile中导入firstName, lastName, year
import {firstName, lastName, year} from './profile';
function setName(element) {
    element.textContent = firstName + ' ' + lastName;
}

//import命令要使用as关键字, 将输入的变量重命名
import { lastName as surname } from './profile';

模块的整体加载

除了指定加载某个输出值, 还可以使用整体加载, 即用星号( *) 指定一个对象, 所有输出值都加载在这个对象上面

// circle.js
export function area(radius) {
    return Math.PI * radius * radius;
}
export function circumference(radius) {
    return 2 * Math.PI * radius;
}

//整体加载
import * as circle from './circle';
console.log('圆面积: ' + circle.area(4));
console.log('圆周长: ' + circle.circumference(14));

export default命令

为模块指定默认输出

// export-default.js
//默认输出是一个函数。
export default function () {
    console.log('foo');
}

// import-default.js
//import命令可以为该匿名函数指定任意名字。
import customName from './export-default';
customName(); // 'foo'

注:一个模块只能有一个默认输出, 因此export deault命令只能使用一次。 所以, import命令后面才不用加大括号, 因为只可能对应一个方法。

跨模块常量

const声明的常量只在当前代码块有效。 如果想设置跨模块的常量( 即跨多个文件) , 可以采用下面的写法

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

推荐阅读更多精彩内容