ES6

[TOC]

声明变量

let

let所声明的变量只在let命令所在的代码块内有效,特别是在for循环内部,设置循环变量的那部分是一个父作用域,而循环内部是一个单独的子作用域。

1
2
3
4
for (let i = 0; i < 3 ; i++){
let i = 'abc';
console.log(i);
} //结果输出三次abc

不存在变量死区

var命令会发生变量提升,即变量可以在声明之前使用,值为undefinded。而let所声明的变量一定要在声明之后使用。

暂时性死区

如果在区块中let和const命令,则这个区块对这个命令声明的变量,从一开始就形成封闭作用域。只要在声明之前就使用这些变量,就会报错。在代码块,使用let命令声明变量之前,该变量都是不可用的。这在语法上称为“暂时性死区”。暂时性死区的本质就是,只要进入了当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

不允许重复声明

let不允许在相同作用域内声明同一个变量,因此,不能在函数内部重新声明参数。

块级作用域

内层作用域可以定义外层作用域的同名变量。外层作用域无法读取内层作用域的变量。

避免在块级作用域内声明函数,如果确实需要,可以写成函数表达式的形式 let f = function(){ return a; };而不是函数声明语句 function f(){ return a; };

do表达式

块级作用域不返回值,除非其中的变量是全局变量,为了使其可以返回一个值,可以将其变成do表达式,以下代码中变量x会得到整个块级作用域的返回值。

1
2
3
4
let x = do{
let t = f();
t*t+1;
}

const

const一旦声明常量,就必须立即初始化,不能留到以后赋值,同样也只在声明所在的块级作用域内有效。不会提升,不可重复声明。

const实际上保证的并不是变量的值不得改变,而是变量指向的那个内存地址不得改动,所以依然可以为其添加新属性。

如果真的想将对象冻结,应该使用Object.freeze方法,此时添加新属性时不起作用,严格模式时还会报错。

一个将对象彻底冻结的函数:

1
2
3
4
5
6
7
8
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if(typeof obj[key] === 'object'){
constantize( obj[key] );
}
});
};

ES6声明变量的6种方法:var function let const import class

变量的解构赋值

ES6:允许按照一定模式从数组和对象中提取值,然后对变量进行赋值。

数组的解构赋值

let [a,b,c] = [1,2,3];

只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。

解构赋值允许指定默认值:

let[foo = true] = [];

foo // true

let [x,y = 'b'] = ['a',undefined]; // x='a',y='b';

注意:null不严格等于undefined,所以当一个数组成员是null时,默认值就不会生效

对象的解构赋值

数组的元素时按照次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。

let { foo: baz} = { foo: "aaa", bar: "bbb" };

baz // "aaa"

foo // error: foo is not defined

foo是匹配的模式,baz才是变量,真正被赋值的是变量baz

对象的解构赋值的内部机制是先找到同名的属性,然后赋值给对应的变量。真正被赋值的是后者而不是前者

对象的解构也可以指定默认值,同样需要严格等于undefined

对数组进行对象属性的解构:

数组的本质是特殊的对象

1
2
3
4
let arr = [1,2,3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

字符串的解构赋值

const [a,b,c,d,e] = 'hello';

a // "h"

b // "e"

c // "l"

d // "l"

e // "o"

类似数组的对象都有一个length的属性:

let {length: len} = 'hello';

len // 5

数值和布尔值的解构赋值

先将数值和布尔值转化为对象

let {toString: s} = 123;

s === Number.prototype.toString //true

解构赋值的规则是,如果等号右边不是对象或者数组,就先将其转化为对象。由于undefined和null无法转化为对象,所以对他们进行解构赋值的都会报错

函数参数的解构赋值

function add({x, y}){

return x+y;

}

传进去的参数是一个数组,但是无形之中已经被解构了!可以很方便的拿到数组中的某一个值。

函数参数的解构也可以使用默认值

圆括号

尽可能不要再模式中放置圆括号

不能使用圆括号的情况:

  1. 变量声明语句
  2. 函数参数(也属于变量声明)
  3. 赋值语句var {p:a} = {p:42};

只有在赋值而非声明语句中,可以用圆括号

字符串的扩展

codePoinAt()

codePointAt()方法是判断一个字符是由2个字节还是4个字节组成的最简单的方法

1
2
3
function is32Bit(c){
return c.codePointAt(0) > 0xFFFF;
}

4个字节则返回true

2个字节则返回false

String.fromCodePoint()

String.fromCodePonit(0x20BB7) //"𠮷"

就是和codePointAt()发过来的

for…of

1
2
3
4
5
6
for (let codePonit of 'foo'){
console.log(codePoint);
}
//"f"
//"o"
//"o"

for循环和for…of的区别:for不能识别大于0xFFFF的码点,for…of可以识别大于0xFFFF的码点

includes(), startsWith(), endsWith()

includes():返回布尔值,表示是否找到了参数字符串

startsWith():返回布尔值,表示参数字符串是否在源字符串的头部

endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部

以上三种方法都支持第二个参数,表示开始搜索的位置

使用第二个参数n的时候,includes()startsWith()针对从第n个位置到字符串结束位置之间的字符,而 endsWith()针对前n个字符

repeat()

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

'x'.repeat(3) // "xxx"

参数如果是小数,则会被取整2.9=>2

如果参数是0到-1之间的小数,则等同于0,参数NAN等同于0.如果参数是字符串,则会先转化为数字

padStart(), padEnd()

padStart()用于头部补全

padEnd()用于尾部补全

如果源字符串的长度等于或大于指定的最大长度,则返回原字符串。如果用来补全的字符串与原字符串的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

'abc'.padStart(10,'0123456789); //'0123456abc'

如果省略第二个参数,则会用空格补全

作用:

  1. 为数值补全指定位数
  2. 提示字符串格式

正则的扩展

RegExp构造函数

如果 RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,值使用新指定的修饰符。

字符串对象共有4个方法可以使用正则表达式:

match() replace() search() split()

U修饰符

用来处理大于\uFFFF的UniCode字符

i修饰符

识别非规范的字符

y修饰符

y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就行,而y修饰符会确保匹配必须从剩余的第一个位置开始,所谓“粘连”

1
2
3
4
5
6
7
8
9
var s = "aaa_aa_a";
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"];
r2.exec(s) // ["aaa"];

r1.exec(s) // ["aa"];
r2.exec(s) // null;

lastIndex属性指定每一次搜索开始的位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。y修饰符同样可以使用这个属性,但是要求必须在lastIndex指定的位置发现匹配。

sticky属性

(/hello\d/y).sticky // true

表示是否设置了y修饰符

flags属性

返回正则表达式的修饰符

/abc/ig.flags // 'gi'

s修饰符:dotAll模式

正则表达式中,点(.)是一个特殊的字符,代表任意的单个字符,但是行终止符除外。

行终止符:

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符
  • U+2029 段分隔符

引入/s修饰符,使得.可以匹配任意单个字符,这称为dotAll模式。

1
2
3
4
5
6
const re = /foo.bar/s;
//另一种写法
//const re = new RegExp('foo.bar','s');

//dotAll属性,返回一个布尔值,判断该正则表达式是否处在dotAll模式下
re.dotAll // true

具名组匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//数字方式
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const a = RE_DATE.exce('1999-12-31');
const year = a[1]; // 1999
const month = a[2]; // 12
const day = a[3]; // 31

//具名组匹配:在模式的头部添加“问号 + 尖括号 + 组名”(?<year>),然后就可以在exce方法返回结果的groups属性上引用该组名。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const a = RE_DATE.exce('1999-12-31');
const year = a.groups.year; // 1999
const month = a.groups.month; // 12
const day = a.groups.day; // 31

字符串替换时,使用$<组名>引用具名组:

1
2
3
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

'2015-01-02'.replace(re, '$<day>/$<month>/$<year>'); // '02/01/2015'

引用:

如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>,数字引用依然有效

1
2
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc'); // true

数值的扩展

Number()

前缀 0b(0B)表示二进制,0o(0O)表示八进制

Number()方法可以将二进制和八进制的数转化为十进制数

Number('0b111'); // 7

Number.isFinite()

检查一个数值是否为有限的

只对数值有效,对于非数值一律返回false

1
2
3
4
5
Number.isFinite(0.8);	//	true
Number.isFinite(NAN); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

Number.isNAN()

检查一个数值是否为NAN

只有对NAN才返回true

1
2
3
4
5
Number.isNAN('15');		//	false
Number.isNAN(true); // false
Number.isNAN(9/NAN) // true
Number.isNAN('true'/0); // true
Number.isNAN('true'/'true'); // true

Number.parseInt(), Number.parseFloat()

将原本parseInt(), parseFloat()方法移植到了Number对象上面。

1
2
3
4
5
//ES5
parseInt('12.34');

//ES6
Number.parseInt('12.34');

Number.isInteger()

判断一个数是否为整数

3和3.0被视为同一个数

1
2
Number.isInteger("15");	//	false
Number.isInteger(true); // false

Number.EPSILON

ES6在Number对象上面新增一个极小的常量——Number.EPSILON

引入一个这么小的量,目的在于为浮点数设置一个误差范围,如果浮点数计算能够小于Number.EPSILON,则我们认为得到了正确的结果。

Number.isSafeInteger()

JS能够精确表示的整数范围在 -Math.pow(2,53)Math.pow(2,53)之间,超过这个范围就无法精确表示

Math.pow(2,53) === Math.pow(2,53) + 1

ES6引入Number.MAX_SAFT_INTEGER和Number.MIN_SAFT_INTEGER两个常量用来表示这个范围的上下限。

Math.trunc()

用于去除一个数的小数部分,返回整数部分

对于非数值,内部使用Number方法将其转化为数值

对于空值和和无法截取整数的值,返回NAN

x < 0 ? Math.ceil(x) : Math.floor(x);

Math.sign()

用来判断一个数到底是正数,负数还是零。对于非数值,会先将其转化为数值。

  • 参数为正数,返回+1
  • 参数为负数,返回-1
  • 参数为0,返回0
  • 参数为-0,返回-0
  • 其他值,返回NAN

Math.cbrt()

用于计算一个数的立方根,对于非数值,内部先转化为数值

Math.clz32()

JS的整数使用32位二进制形式表示,该方法返回一个数的32位无符号整数形式有多少个前导0

0的二进制形式全为0,所以有32个前导0

1的二进制形式是0b1,只有1位,所以32位之中有31个前导0

左移运算符(<<)

1
2
3
Math.clz32(1);		//31
Math.clz32(1 << 1); //30
Math.clz32(1 << 2); //29

对于小数,该方法只考虑整数部分

对于空值和其他类型的值,该方法会在内部先将它们转化为数值

1
2
3
4
5
6
7
8
Math.clz32();	// 32
Math.clz32(NAN); // 32
Math.clz32(Infinity); // 32
Math.clz32(null); // 32
Math.clz32('foo'); // 32
Math.clz32([]); // 32
Math.clz32({}); // 32
Math.clz32(true); // 31

Math.imul()

该方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。

Math.imul(2,4); // 8

Math.fround

该方法返回一个数的单精度浮点数的形式

Math.hypot()

返回所有参数的平方和的平方根

Math.hypot(3,4); //5

如果参数不是数值,Math.hypot方法将会转化为数值,只要有一个参数无法转化为数值,就会返回NAN

对数方法

  1. Math.expm1(x)返回e的x次方再减1,即Math.exp(x)-1

  2. Math.log1p(x)返回ln(1+x),即 Math.log(1+x),如果x小于-1,则返回NAN

    Math.log1p(-1); // -Infinity

  3. Math.log10(),返回以10为底的x的对数,如果x小于0,则返回NAN

  4. Math.log2(),返回以2为底的对数,如果x小于0,则返回NAN

双曲函数方法

1
2
3
4
5
6
Math.sinh(x);	//双曲正弦
Math.cosh(x); //双曲余弦
Math.tanh(x); //双曲正切
Math.asinh(x); //反双曲正弦
Math.acosh(x); //反双曲余弦
Math.atanh(x); //反双曲正切

Math.signbit()

1
2
3
4
5
Math.signbit(NAN);	//	false
Math.signbit(-0); // true
Math.signbit(负值); // true

//其他情况返回false

指数运算符(**)

1
2
3
4
2 ** 2;	//	4

a **= 2;
//等同于 a = a * a;

函数的扩展

函数参数的默认值

1
2
3
4
5
6
7
//ES6允许为函数的参数设置默认值,即直接写在参数定义的后面
function log(x, y = 'world'){
console.log(x,y);
}

log('Hello'); // Hello World
log('Hello', 'China'); // Hello China

注意:

  • 函数参数是默认声明的,所以不能用let或者const在函数内部再次声明
  • 使用参数默认值时,函数不能有同名参数。(即使不是有默认值的那个参数,其他的参数也是不能出现两次的
  • 参数默认值不是传值,而是每次都重新计算默认值表达式的值,即参数默认值是惰性求值的

参数默认值可以与解构赋值的默认值结合起来使用

1
2
3
4
5
6
7
function fetch(url, { method = 'GET'} = {}){
console.log(method);
}

fetch('httP://example.com'); // "GET"

//没有传入第二个参数则默认传入一个空对象,空对象没有method属性于是使用默认值。

通常情况下,默认值的参数应该是函数的尾参数,如果非尾部的参数设置默认值,实际上这个参数是无法省略的。

1
2
3
4
5
6
7
8
function f(x=1, y){
return [x,y];
}

f(,1); //报错
f(undefined,1); //[1,1]

//可以看出,当有默认值的参数不是尾参数,这时无法省略该参数而不省略其后的参数,除非显式输入unfined。如果传入undefined,将触发该参数等于默认值,null则没有该效果

应用:

  • 利用函数默认值可以指定某一个参数不得省略,如果省略就抛出一个错误
  • 可以将参数默认值设为undefined,表示这个参数是可以被省略的

函数的length属性

length属性 = 参数个数 - 指定默认值参数的个数 - rest参数

注意:如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数

函数作用域

1
2
3
4
5
6
7
8
9
var x = 1;

function foo(x = x){
//....
}

foo(); // 报错

//参数x = x形成一个单独的作用域,实际执行的是let x = x.由于暂时性死区,执行上述代码会产生“定义”错误

rest参数

1
2
3
4
5
6
7
8
9
10
11
function add(...values){
let sum = 0;

for (var val of values){
sum += val;
}

return sum;
}

//rest参数(形式为"...变量名"),用于获取函数的多余参数,rest参数搭配的变量是一个数组,该变量将多余的参数放入其中。

注意:rest参数之后不能再有其他参数,否则会报错

name属性

函数的name参数返回该函数的函数名

const bar = function baz(){};

bar.name; //baz

bind返回的函数,name属性值会加上bound前缀

foo.bind({}).name; // "bound foo"

箭头函数

1
2
3
var f = () => 5;
//等同于
var f = function(){return 5};

注意:

函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象

不可以当作构造函数,即不可以使用new命令

不可以使用arguments对象,可以用rest进行代替

不可以使用yield命令,因此箭头函数不能用作Generator函数

数组的扩展

扩展运算符

扩展运算符(…),rest参数的逆运算。可以将一个数组变成参数序列

如果将扩展运算符用于数组赋值,则只能将其放在参数的最后一位

应用:

数组的合并: [...arr1, ...arr2, ...arr3];

将字符串转为真正的数组:[...'hello'] // ["h", "e", "l", "l", "o"](可以用于返回正确字符串长度)

Array.from()

将类似数组的对象,即有length属性的对象,都可以通过这个方法转化为数组。

1
2
3
Array.from([1,2,3],(x) => x * x)	//	[1,4,9]
//还可以接受第二个参数,对每个元素进行处理,将处理之后的值放入返回数组
//也可以传入第三个参数,用来绑定this

Array.of()

将一组值转化为数组

Array.of(3,11,8) // [3,11,8]

copyWithin()

1
2
3
4
5
6
7
8
Array.prototype.copyWithin(target, start, end);
//三个参数
//target:开始替换的位置
//start:从该位置开始读取数据
//end:停止读取数据的位置

[1,2,3,4,5].copyWithin(0,3,4) //[4,2,3,4,5]

find() findIndex()

找出第一符合条件的数组成员

[1,4,-5,10].find((n) => n < 0) // -5

find的回调函数可以接受三个参数,依次是当前的值,当前的位置和原数组

fill()

使用定值填充一个数组

['a','b','c'].fill(7,1,2) // ['a', 7, 'c']

参数: 填充的定值,起始位置,结束位置

entries() keys() values()

用于遍历数组

keys()键名

values()键名

entries()键值对

对象的扩展

ES6中允许在对象中只写属性名,不写属性值。这时,属性值等于属性名所代表的变量

属性名表达式与简洁表示法不能同时使用,否则会报错

属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]

方法也有name属性,返回方法名

Object.is()

判断值是否相等

Object.is(NAN,NAN) // true

Object.assign()

1
2
3
4
5
6
7
8
9
var target = { a : 1 };
var source1 = { b : 2 };
var aource2 = { c : 3 };

Object.assign(target,source1,source2);
target // {a:1, b:2, c:3}

//如果目标对象和源对象有同名属性,则后面的会覆盖前面
//浅复制,得到的是这个对象的引用

属性的可枚举性

Object.getOwnPropertyDescriptor(对象,属性)获取这个属性的可描述对象

属性的遍历

  • for...in
  • Object.keys(obj)
  • Object.getOwnPropertyDescriptor(obj)
  • Object.getOwnPropertySymbols(obj)
  • Reflect.ownKeys(obj)

原型

Object.getPrototype读取一个对象的prototype

Object.setPrototype设置一个对象的prototype

对象的遍历

Object.keys()

Object.values()

Object.entries()

Iterator

原生具备Iterator接口的数据解构:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象

而没有Iterator接口的数据结构,比如对象,需要我们在Symbol.iterator属性上部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RangeIterator{
constructor(start,stop){
this.value = start;
this.stop = stop;
}

[Symbol.iterator]() { return this;}
next(){
var value = this.value;
if(value < this.stop){
this.value++;
return{done: false, value: value};
}
return {done: true, value: undefined};
}
}

function range(start,stop){
return new RangeIterator(atart,stop);
}

for(var value of range (0,3)){
console.log(value); //0,1,2
}

Generator函数

像一个状态机,封装了多个函数,,使用yield语句定义不同的内部状态。一直执行同步代码,直到遇到yield语句为止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* helloWorld(){
yield 'hello';
yield 'world';
return 'ending'; // generator函数的return语句必须返回一个对象
}

var hw = helloWorld();

//调用next()才会开始执行

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语句的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* f(){
for(var i = 0; true; i++){ //第二个参数是true可以无限运行噢
var reset = yield i;
if(reset){
i=-1;
}
}
}

var g = f();

g.next() // {value: 0, done: false}
g.next() // {value: 1, done: false}
g.next(true) // {value: 0, done: false} // reset等于true然后i就从-1开始递增啦~

async函数

  • 与generator函数相比,async内置执行器
  • async函数的返回值是Promise对象,可以使用then方法指定下一步操作

正常情况下,await命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象,如果async函数内部抛出错误会导致返回的Promise对象变成reject对象。如果有一个await语句后面的Promise变成reject,那么整个async函数都会中断执行,把await放进try…catch结构里面,可以避免这种情况,即使try..catch;里面的await失败,外面的await依旧会执行。

1
2
3
4
5
6
7
8
9
async function f(){
try {
await Promise.reject('出错了');
}catch(e){
}
return await Promise.resolve('hello,world');
}

f().then(v => console.log(v)) // hello world

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let methodName = 'getArea';

class Point{//方法之间不需要逗号分隔
constructor{
//...
}

toString{
//...
}

toValue{
//...
}

[methodName](){//类属性名可以采用表达式,通过表达式获得方法名getArea
//...
}
}

类不存在变量提升

私有属性,私有方法:在属性名之前,使用#来表示

类的方法中如果含有this,它将默认指向类的实例

类相当于实例的原型,所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过调用,称为“静态方法”。