es6学习笔记

2017-08-06  本文已影响32人  EarthChen

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()

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方法使用给定值, 填充一个数组

数组实例的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实例的属性和方法

属性
方法
  1. 操作方法:
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. 遍历方法
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

遍历

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要区分两种情况:
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
} 
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
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函数是一个普通函数, 但是有两个特征。

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语句不能用在普通函数中, 否则会报错

Promise对象

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

Promise对象有以下两个特点:

//创造了一个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
上一篇下一篇

猜你喜欢

热点阅读