[TOC]
参考阮一峰的ECMAScript 6 入门
参考深入浅出ES6
let和const
- let和const都是用来声明变量的,用法和var相似
- 用let或const定义的变量只能在let或const命令所在的代码块(块级作用域)中有效
- es6中新增了块级作用域
- ES6 允许块级作用域的任意嵌套,如例2。
- 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了,如例3.
//例2
{{{{{let insane = 'Hello World'}}}}};
//外层作用域无法读取内层作用域的变量。
//内层作用域可以定义外层作用域的同名变量。
//例3
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
- 必须先声明才能用,没有预解释,只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
未声明就使用变量报错的几种情况
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面的代码会报错,因为代码块中用let声明了一个变量tmp,这个tmp就会跟代码块进行绑定,而es6中规定,变量没有声明就使用就会报错
// 报错
let x = x;
// ReferenceError: x is not defined
变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
让x=y,但是这时候y并没有声明就使用所以报错
- let不允许在相同作用域内,重复声明同一个变量。
// 报错
function () {
let a = 10;
let a = 1;
}
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
//不允许在函数内部重新声明形参
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
- let用于定义变量,const用于定义常量,const给一个变量赋值之后就不能给这个变量重新赋值,会报错。
- 对于const来说,只声明不赋值,就会报错。
- const本质上是保证变量所指向的内存地址不改变,对于基本数据类型就是常量,对于引用数据类型,就是保证变量所对应的内存地址的指针不改变,但是里面的内容无法保证;如果想让对象的内容也不能改变,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
//除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
- 在ES6中var和function声明的全局变量,依旧是window的属性;let、const、class声明的全局变量,不属于window的属性。
- 在for循环中使用let声明变量有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。
do表达式
{
let t = f();
t = t * t + 1;
}
上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。
要想得到块级作用域中的返回值,可以在块级作用域之前加上do,使它变成do表达式。
let x = do {
let t = f();
t * t + 1;
};
上面代码中,变量x会得到整个块级作用域的返回值。
关于ES6中的顶层对象
ES5中顶层对象不统一,浏览器下是window,node中是global
ES6中
- 全局环境中,this会返回顶层对象。但是,Node模块和ES6模块中,this返回的是当前模块。
- 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
- 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
变量的解构赋值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象
数组的解构赋值
只要等号两边的模式相同,左边的变量就会被赋予对应的值,如果解构不成功,变量的值就等于undefined。
let [a, b, c] = [1, 2, 3];
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
- 解构赋值允许指定默认值,默认值生效的条件是,解构的值是undefined。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
- ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构赋值是下面形式的简写,也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者,前者只是要匹配的模式,后者才是变量。
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
使用对象的解构赋值时,变量的声明和赋值是一体的,对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
- 对象的解构也可以指定默认值,默认值生效的条件是,对象的属性值严格等于undefined。
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
字符串的解构赋值
字符串解构赋值的时候会被转成类数组
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
Number和Boolean的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
null和undefined的解构赋值
由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
解构赋值的用途
- 变量交换
let x = 1;
let y = 2;
[x, y] = [y, x];
- 取函数的返回值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
- 提取JSON数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
- 函数参数的默认值,不用再写
var foo = config.foo || 'default foo'
;这样的语句。
字符串的扩展
includes、startsWith和endsWith
传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。
- 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,第二个参数表示从索引n开始
s.endsWith('Hello', 5) // true,第二个参数表示前n个字符
s.includes('Hello', 6) // false,第二个参数表示从索引n开始
fo-of循环
for-of循环的功能
- for-of循环可以用来遍历数组,for-in循环用来遍历对象属性。
for (var value of myArray) {
console.log(value);
}
- 这是最简洁、最直接的遍历数组元素的语法
- 这个方法避开了for-in循环的所有缺陷
- 与forEach()不同的是,它可以正确响应break、continue和return语句
- for-of循环不仅支持数组,还支持大多数类数组对象,例如DOM NodeList对象。
- for-of循环也支持字符串遍历,它将字符串视为一系列的Unicode字符来进行遍历:
for (var chr of "") {
alert(chr);
}
repeat方法
repeat(n) n
是一个数字,表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3) // "xxx"
参数如果是小数,会被取整,如果repeat的参数是负数或者Infinity,会报错。
'na'.repeat(2.9) // "nana"
如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0,repeat视同为0。
'na'.repeat(-0.9) // ""
'na'.repeat(NaN) // ""
如果repeat的参数是字符串,则会先转换成数字。
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
padStart(),padEnd()
padStart()用于头部补全,padEnd()用于尾部补全。一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(4, 'ab') // 'xaba'
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789')// '0123456abc'
模板字符串
模板字符串(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"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3
- 模板字符串之中还能调用函数。
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
如果大括号中的值不是字符串,将默认调用toString方法转换成字符串,如果模板字符串中的变量没有声明,将报错。
- 标签模板
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
alert`123`
// 等同于
alert(123)
如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
tag函数的第一个参数是一个数组,数组项是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组各个项之间。
tag函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此tag会接受到两个参数。
tag函数所有参数的实际值如上面的代码。
- String.raw函数
String.raw
方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。如果原字符串的斜杠已经转义,那么String.raw不会做任何处理。
String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"
String.raw`Hi\u000A!`;
// 'Hi\\u000A!'
String.raw`Hi\\n`
// "Hi\\n"
正则表达式的扩展
关于ES6中RegExp构造函数
在ES6中RegExp构造函数的第一个参数如果是一个正则表达式,可以使用第二个参数指定正则的的修饰符,在ES5中不可以,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
new RegExp(/abc/ig, 'i')// /abc/i
关于字符串的正则方法
字符串对象共有4个方法,可以使用正则表达式:match()、replace()、search()和split()。
新增u修饰符
ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。
Number的扩展
二进制和八进制的新的写法
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
**Number.isFinite(), Number.isNaN() **
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
Number.isFinite()用来检查一个数值是否为有限的(finite)。
Number.isNaN()用来检查一个值是否为NaN。
这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
Number.parseInt(), Number.parseFloat()
ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
Number.isInteger()
Number.isInteger()用来判断一个值是否为整数。
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.EPSILON
js中浮点数计算是不精确的(0.1+0.2)。ES6在Number对象上面,新增一个极小的常量Number.EPSILON,Number.EPSILON的实质是一个可以接受的误差范围。为浮点数计算,设置一个误差范围.如果这个误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。
安全整数和Number.isSafeInteger()
JavaScript能够准确表示的整数范围在-2^53(-2的53次方)
到2^53(2的53次方)
之间(不含两个端点),超过这个范围,无法精确表示这个值。
ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。
Math对象的扩展
ES6在Math对象上新增了17个与数学相关的方法。所有这些方法都是静态方法,只能在Math对象上调用。
-
Math.trunc
方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值。对于空值和无法截取整数的值,返回NaN。
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc('123.456')// 123
Math.trunc(NaN); // NaN
Math.trunc('foo'); // NaN
Math.trunc(); // NaN
-
Math.sign
方法用来判断一个数到底是正数、负数、还是零。它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
-
Math.cbrt
方法用于计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
Math.clz32
方法返回一个数的32位无符号整数形式有多少个前导0。Math.imul
方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。Math.fround
方法返回一个数的单精度浮点数形式。Math.hypot
方法返回所有参数的平方和的平方根。Math.expm1(x)
返回ex - 1,即Math.exp(x) - 1。ath.log1p(x)
方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。Math.log10(x)
返回以10为底的x的对数。如果x小于0,则返回NaN。Math.log2(x)
返回以2为底的x的对数。如果x小于0,则返回NaN。Math.sinh(x)
返回x的双曲正弦(hyperbolic sine)Math.cosh(x)
返回x的双曲余弦(hyperbolic cosine)Math.tanh(x)
返回x的双曲正切(hyperbolic tangent)Math.asinh(x)
返回x的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)
返回x的反双曲正切(inverse hyperbolic tangent)Math.signbit()
方法判断一个数的符号位是否设置了
如果参数是NaN,返回false
如果参数是-0,返回true
如果参数是负值,返回true
其他情况返回falseES2016 新增了一个指数运算符(**)。指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
2 ** 2 // 4
2 ** 3 // 8
a **= 2;// 等同于 a = a * a;
数组的扩展
-
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
-
Array.of
方法用于将一组值,转换为数组。如果没有参数,就返回一个空数组。
Array.of(3, 11, 8) // [3,11,8]
- 数组实例的
copyWithin()
方法,将数组指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
它接受三个参数。
target(必需):从该位置开始替换数据。
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
- 数组实例的find()和findIndex()
数组实例的find方法,用于找出第一个
符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
- fill方法使用给定值,填充一个数组。用于初始化空数组,第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
-
keys()
是对键名的遍历,可以用for...of循环进行遍历 -
values()
是对键值的遍历,可以用for...of循环进行遍历 -
entries()
是对键值对的遍历,可以用for...of循环进行遍历 -
Array.prototype.includes
方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于ES7,但Babel转码器已经支持。
函数的扩展
参数的默认值
- 可以直接在函数的实参中设置函数的默认值
function log(x, y = 'World') {
console.log(x, y);
}
- 参数变量是默认声明的,所以不能用let或const再次声明。
- 使用参数默认值时,函数不能有同名参数。
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
- 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
- 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。rest参数(可变参数)也不会计入length属性。
- 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
- 把参数的默认值设成undefined表明这个参数是可以省略的
作用域
一旦设置了参数的默认值,函数执行时,参数会形成一个单独的作用域。等到执行完,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
rest参数
ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数是一个数组,该变量将多余的参数放入数组中。rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
扩展运算符
- 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
该运算符主要用于函数调用。
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
- 扩展运算符还可以将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
- 函数的name属性,返回该函数的函数名。如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
箭头函数
- 基本用法
var f = () => 5;
var f = v => v;
var sum = (num1, num2) => num1 + num2;
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => {
console.log(num1 + num2);
return num1 + num2;
}
- 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getTempItem = id => ({ id: id, name: "Temp" });
箭头函数的注意点
- 箭头函数的this是他的父级的this,因为是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
- 箭头函数不存在arguments、super、new.target这三个变量。
- 箭头函数不能用call()、apply()、bind()这些方法去改变this的指向。
函数绑定运算符(::)
这是一个ES7的提案,但是babel已经支持
双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
// 等同于
bar.bind(foo);
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
对象的扩展
属性的简洁写法
ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁,简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果。
var obj = {
class () {}
};
// 等同于
var obj = {
'class': function() {}
};
var birth = '2000/01/01';
var Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
- ES6可以用下面这种方式定义对象
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
对象新增的方法
-
bject.is
是用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
- Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。第一个参数是目标对象,后面的参数都是源对象。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该参数不是对象,则会先转成对象,然后返回。如果只有一个参数,Object.assign会直接返回该参数。由于undefined和null无法转成对象,所以如果它们作为参数并且是目标对象(即第一个参数),就会报错。
未完待续。。。
Symbol数据类型
ES5的对象属性名都是字符串,这容易造成属性名的冲突。ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第八种数据类型。
let s = Symbol();
typeof s// "symbol"
Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
- Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false
// 有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');
s1 === s2 // false
- 如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。
- Symbol值不能与其他类型的值进行运算,会报错。
- Symbol值能转换成字符串和布尔值,但是不能转换成数字。
- Symbol值可以用作对象的属性名。Symbol值作为对象属性名时,不能用点运算符。
var mySymbol = Symbol();
var a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!
- 在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);
上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个Symbol值
- Symbol值作为属性名时,该属性是公有属性,不是私有属性。
- Symbol作为属性名不可以被遍历,可以使用Object.getOwnPropertySymbols方法获得属性名。
Symbol相关方法
- Symbol.for()
Symbol.for接受一个字符串作为参数,搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。Symbol.for会被登记在全局环境中供搜索
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
s1 === s2 // true
- Symbol.keyFor() 方法返回一个已登记的 Symbol 类型值的key。
就是前面已经执行过Symbol.for的
Symbol的内置属性
ES6提供了11个内置的Symbol属性,指向语言内部使用的方法。
-
Symbol.hasInstance
属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。 -
Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。 -
Symbol.species
属性,指向当前对象的构造函数。创造实例时,默认会调用这个方法,即使用这个属性返回的函数当作构造函数,来创造新的实例对象 -
Symbol.match
属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。 -
Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,第二个参数是替换后的值 -
Symbol.search
属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。 -
Symbol.split
属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。 -
Symbol.iterator
属性,指向该对象的默认遍历器方法。 -
Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 -
Symbol.toStringTag
属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。 -
Symbol.unscopables
属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。
Set和Map数据结构
Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
数组去重的新增2种写法
// 数组去重
[...new Set(array)]
//数组去重
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
Set相关的方法
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set实例的成员总数。
增删改查
- add(value):添加某个值,返回Set结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
遍历
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
WeakSet
WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。
- WeakSet的成员只能是对象,而不能是其他类型的值。
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
WeakSet相关方法
- WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。
- WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。
Map
Map数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。
只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心。
var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
Map相关属性和方法
-
size
属性返回Map结构的成员总数。 -
set
方法设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。可以采用链式写法。 -
get
方法读取key对应的键值,如果找不到key,返回undefined。 -
has
方法返回一个布尔值,表示某个键是否在Map数据结构中。 -
delete
方法删除某个键,返回true。如果删除失败,返回false。 -
clear
方法清除所有成员,没有返回值。 -
keys()
返回键名的遍历器。 -
values()
返回键值的遍历器。 -
entries()
返回所有成员的遍历器。 -
forEach()
遍历Map的所有成员。
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转为对象
如果所有Map的键都是字符串,它可以转为对象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
- 对象转为Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// [ [ 'yes', true ], [ 'no', false ] ]
- 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
JSON转为Map,正常情况下,所有键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}
有一种特殊情况,整个JSON就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为JSON的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
WeakMap
WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。
WeakMap只有四个方法可用:get()、set()、has()、delete()。
Proxy
待续……
Reflect
待续……
Promise 对象
Promise 的含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象的特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise的缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise的基本用法
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
,它们是两个函数。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
-
resolve
的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; -
reject
的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise相关方法
-
then
方法可以分别指定当promise的状态是成功和失败的时候的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法
promise.then(function(value) {
// 状态成功的时候执行的函数
}, function(error) {
// 状态为失败的时候执行的函数,这个函数可以省略
});
-
catch
相当于.then(null, rejection),相当于then方法的第二个参数,一般用catch方法处理错误,不用then方法。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
-
Promise.all
方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
-
Promise.race
方法同样是将多个Promise实例,包装成一个新的Promise实例,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
ES6的构造函数和继承
通过class来创建一个类,构造函数写在constructor里面
创建父类
class 类名{
constructor(param1,param2){
//私有的属性和方法
}
getName(){
//公有的属性和方法
}
static getAge(){
//类的静态方法,也就是类的私有方法,实例不能使用
}
}
创建子类并继承
class S extend F{
constructor(name,age,color){
super(name,age)//这句话必须要写,写了就是把父类的私有公有都继承了过来
this.color=color//这样给子类添加私有的
}
getSex(){
//这是子类公有的
}
}
Module的介绍
语法
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载3个方法,其他方法不加载。
严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
模块命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。import和export命令只能在模块的顶层,不能在代码块之中,类似于全局作用域
export命令
export命令用于将模块内部的变量输出到外面。
写法
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
优先采用下面这种写法,export命令后面跟一个对象,对象里面是变量名
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() { ... }
export {
v1 as streamV1
};
ES6中不输出任何接口的写法
export {};//这个命令并不是输出一个空对象,而是不输出任何接口的 ES6 标准写法。
import命令
import命令用于加载模块
用法
import后面跟大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。from
后面跟模块的路径,可以省略.js,如果不跟路径,只写模块名,那么就必须要有配置文件来指定路径
import {firstName, lastName, year} from './profile';
import命令也可以用as关键字重命名加载的变量;
import命令具有提升效果
,会提升到整个模块的头部,首先执行。
import命令会执行所加载的模块;
import 'lodash';//只是执行了模块
整体加载模块使用*
import * as circle from './circle';
export default 命令
export default 命令命令是默认输出的变量或函数,当其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。注意,这时import命令后面,不使用大括号。
export 与 import 的复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
import函数
import函数是动态加载模块,类似于node的require,区别主要是前者是异步加载,后者是同步加载。
import()返回一个 Promise 对象。后面可以跟then或catch方法
Module的加载实现
脚本的异步加载
在<script>标签中添加defer或async属性,脚本就会异步加载。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
defer与async的区别
是:前者要等到整个页面正常渲染结束,才会执行;后者一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
ES6的编程规则
let和const之间应该优先使用const,所有的函数都应该设置为常量
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
使用数组成员对变量赋值时,优先使用解构赋值。
函数的参数如果是对象的成员,优先使用解构赋值。
如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
var ref = 'some value';
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
使用扩展运算符(...)拷贝数组。
使用Array.from方法,将类似数组的对象转为数组。
立即执行函数可以写成箭头函数的形式。
(() => {
console.log('Welcome to the Internet.');
})();
那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。
简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。
所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
使用rest运算符(...)代替arguments。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。
使用默认值语法设置函数参数的默认值。
注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。
用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。
Module语法是JavaScript模块的标准写法,坚持使用Module语法。使用import取代require。
使用export取代module.exports。
如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,不要export default与普通的export同时使用。
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
如果模块默认输出一个函数,函数名的首字母应该小写。
如果模块默认输出一个对象,对象名的首字母应该大写。
-
使用ESLint检查语法规则和代码风格,保证写出语法正确、风格统一的代码。
[ESLint的使用方式](http://es6.ruanyifeng.com/#docs/style)