ECMAScript 6.0( 以下简称ES6) 是JavaScript语言的下一代标准。
ECMAScript和JavaScript的关系是, 前者是后者的规格, 后者是前者的一种实现( 另外的ECMAScript方言还有Jscript和ActionScript) 。 日常场合, 这两个词是可以互换的。
在前端工程化的现在,学习es6还是有必要的。
本文为个人根据阮老师的es6标准入门学习笔记。
ES6
let和const命令
let
- let用来声明变量。 它的用法类似于var, 但是所声明的变量, 只在let命令所在的代码块内有效
- 在循环中,如果变量i是var声明的, 在全局范围内都有效。 所以每一次循环,新的i值都会覆盖旧值,如果变量i是let声明的, 当前的i只在本轮循环有效, 所以每一次循环的i其实都是一个新的变量。
- let不像var那样会发生“变量提升”现象。 所以, 变量一定要在声明后使用, 否则报错
- 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;
// }
结果为:
const
- const声明一个只读的常量。 一旦声明, 常量的值就不能改变。
- const声明的变量不得改变值, 这意味着, const一旦声明变量, 就必须立即初始化, 不能留到以后赋值。
- onst的作用域与let命令相同: 只在声明所在的块级作用域内有效。
- const命令声明的常量也是不提升, 同样存在暂时性死区, 只能在声明的位置后面使用
- const声明的常量, 也与let一样不可重复声明
const PI=3.1415;
console.log(PI);
/**
* 量a是一个数组, 这个数组本身是可写的, 但是如果将另一个数组赋值给a, 就会报错
* @type {Array}
*/
const a=[];
a.push('hello');
//a = ['Dave'];
全局对象的属性
- var命令和function命令声明的全局变量, 依旧是全局对象的属性
- let命令、 const命令、 class命令声明的全局变量, 不属于全局对象的属性
var a = 1;
// 如果在Node的REPL环境, 可以写成global.a
// 或者采用通用方法, 写成this.a
this.a // 1
let b = 1;
//window.b // undefined
变量的解构赋值
数组的解构赋值
- 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, 默认值是不会生效的
对象的解构赋值
解构不仅可以用于数组, 还可以用于对象
- 对象的解构与数组有一个重要的不同。 数组的元素是按次序排列的, 变量的取值由它的位置决定; 而对象的属性没有次序, 变量必须与属性同名, 才
能取到正确的值。 - 对象的解构也可以指定默认值。默认值生效的条件是, 对象的属性值严格等于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];
}
圆括号问题
只要有可能导致解构的歧义, 就不得使用圆括号
不能使用圆括号的情况
- 变量声明语句中, 不能带有圆括号
- 函数参数中, 模式不能带有圆括号。
- 赋值语句中, 不能将整个模式, 或嵌套模式中的一层, 放在圆括号之中。
// 全部报错
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实例的成员总数。
方法
- 操作方法:
- 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
- 遍历方法
- 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方法的行为完全一致。
- 应用
//扩展运算符( ...) 内部使用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与其他数据结构的互相转换:
- Map转为数组
Map转为数组最方便的方法, 就是使用扩展运算符( ...) 。
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
- 数组转为Map
将数组转入Map构造函数, 就可以转为Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
- 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"]]]'
- 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