es6

96
郭子web
2017.12.13 17:22 字数 11121

[TOC]

参考阮一峰的ECMAScript 6 入门
参考深入浅出ES6

let和const

  • let和const都是用来声明变量的,用法和var相似
  • 用let或const定义的变量只能在let或const命令所在的代码块(块级作用域)中有效
  1. es6中新增了块级作用域
  2. ES6 允许块级作用域的任意嵌套,如例2。
  3. 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(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给一个变量赋值之后就不能给这个变量重新赋值,会报错。
  1. 对于const来说,只声明不赋值,就会报错。
  2. 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循环的功能

  1. for-of循环可以用来遍历数组,for-in循环用来遍历对象属性。
for (var value of myArray) {
  console.log(value);
}
  • 这是最简洁、最直接的遍历数组元素的语法
  • 这个方法避开了for-in循环的所有缺陷
  • 与forEach()不同的是,它可以正确响应break、continue和return语句

  1. for-of循环不仅支持数组,还支持大多数类数组对象,例如DOM NodeList对象。

  1. 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
    其他情况返回false

  • ES2016 新增了一个指数运算符(**)。指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

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" });

箭头函数的注意点

  1. 箭头函数的this是他的父级的this,因为是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数。
  5. 箭头函数不存在arguments、super、new.target这三个变量。
  6. 箭头函数不能用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有两个区别。

  1. WeakSet的成员只能是对象,而不能是其他类型的值。
  2. 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构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject,它们是两个函数。

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]);
  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  2. 只要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
es6