字符串的扩展

ES6 加强了对 Unicode 的支持,并且扩展了 String 字符串对象,解决了 ES5 在字符串功能上的痛点。

最佳实践:静态字符串一律使用单引号,不使用双引号。动态字符串使用模板字符串。

模板字符串

模板字符串第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 ``和\${}来界定。这使得事情变得更简单,代码更容易阅读。你可以在花括号内放置任何东西:变量、方程式或函数调用。

1
2
3
4
5
6
7
// es5
var name1 = "bai";
console.log('hello ' + name1);

// es6
const name2 = "ming";
console.log(`hello ${name2}`);

模板字符串第二个用途:在 ES5 时我们通过反斜杠来做多行字符串拼接。ES6 模板字符串直接搞定。

1
2
3
4
5
6
7
8
// es5
var msg = "Hi \
man!";

// es6
const template = `<div>
<span>hello world</span>
</div>`;

模板字符串之中还能调用函数:

1
2
3
4
5
function fn() {
return "Hello World";
}

console.log(`输出值为:${fn()}`) // "输出值为:Hello World"

字符串扩展方法

传统上,JavaScript 只有 indexOf 方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,判断数组、字符串是否包含某个元素。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

includes() 可以代替 indexOf(),endsWith() 可以用来检测文件后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// includes: 判断是否包含然后返回布尔值
'hahah'.includes('y') // false
['1','y'].includes('y') // true

// ES5 indexOf
let arr = ['react', 'angular', 'vue'];

if (arr.indexOf('react') !== -1) {
console.log('React存在');
}

// ES6 includes
let arr = ['react', 'angular', 'vue'];

if (arr.includes('react')) {
console.log('React存在');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// repeat: 字符串重复n次
console.log('he'.repeat(3)); // 'hehehe'

// 开始字符
console.log('abcd'.startsWith('aa')); // false

// 结束字符
console.log('abcd'.endsWith('cd')); // true

// padStart、padEnd 长度不足就补
let date = new Date();
let month = date.getMonth() + 1;
console.log(month.toString().padStart(2, '0')); // 06

console.log('123'.padStart(11, '0')); // 00000000123
console.log('123'.padEnd(11, '0')); // 12300000000

// padStart ES5实现
padStart(num, n = 2, str = '0') {
let len = num.toString().length
while (len < n) {
num = str + num
len++
}
return num
}

正则的扩展

y 修饰符

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

1
2
3
4
5
let s= 'bbb_bb_b';
let reg1 = /b+/g;
let reg2 = /b+/y;
console.log(reg1.exec(s), reg2.exec(s)); // ["bbb", index: 0, input: "bbb_bb_b"] ["bbb", index: 0, input: "bbb_bb_b"]
console.log(reg1.exec(s), reg2.exec(s)); // ["bb", index: 4, input: "bbb_bb_b"] null

u 修饰符

含义为“Unicode 模式”,用来正确处理大于\uFFFF 的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

1
2
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true

s 修饰符

dotAll 模式

数值的扩展

Number 新增数值方法

注意 Number.isFinite() 和 Number.isNaN() 与传统的全局方法 isFinite() 和 isNaN() 的区别。

1
2
3
4
5
6
7
8
9
// isFinite: 判断一个数值是否是有尽的
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100 / 0)); // false
console.log(Number.isFinite(NaN)); // false

// isNaN: 判断一个数值是否是一个非数值
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(100)); // false
console.log(Number.isNaN(100 / 0)); // false
  • Number.parseInt(), Number.parseFloat()

ES6 将全局方法 parseInt() 和 parseFloat(),移植到 Number 对象上面,行为完全保持不变。

  • Number.isInteger():用来判断一个数值是否为整数
1
2
3
4
// isInteger: 判断一个数值是否是一个整数
console.log(Number.isInteger(100)); // true
console.log(Number.isInteger(100.0)); // true
console.log(Number.isInteger(100.5)); // false

Math 新增数学方法

ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。

  • Math.trunc 方法用于去除一个数的小数部分,返回整数部分。
  • Math.sign 方法用来判断一个数到底是正数、负数、还是零。
  • Math.cbrt 方法用于计算一个数的立方根。
  • Math.hypot 方法返回所有参数的平方和的平方根。
  • ES6 新增了 4 个对数相关方法。
  • ES6 新增了 6 个双曲函数方法。
1
2
3
4
5
6
7
8
9
10
// trunc: 返回整数部分
console.log(Math.trunc(4)); // 4
console.log(Math.trunc(4.1)); // 4
console.log(Math.trunc(-1.9)); // -1

// 返回数值符号
console.log(Math.sign(-5)); // -1
console.log(Math.sign(0)); // 0
console.log(Math.sign(5)); // 1
console.log(Math.sign('abc')); // NaN

指数运算符

ES2016 新增了一个指数运算符(**),类似 Math.pow

1
2
3
4
5
// ES5
Math.pow(2, 3) // 8

// ES6
2 ** 3 // 8

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

1
2
3
4
5
6
7
let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

数组的扩展

扩展运算符

扩展运算符是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

==rest 参数可以理解为聚拢,扩展运算符可以理解为“展开”==。

1
2
3
4
5
function foo(...args) {
console.log(...args);

}
foo(1, 2, 3); // 1, 2, 3

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

1
const [first, ...rest] = [1, 2, 3, 4, 5];

用途:数组合并的新写法

1
2
3
4
5
6
7
8
9
const arr1 = ['a', 'b']
const arr2 = ['c']
const arr3 = ['d', 'e']

// ES5 的合并数组
arr1.concat(arr2, arr3) // [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
;[...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]

用途:复制数组的新写法

1
2
3
4
5
6
7
8
9
10
11
const a1 = [1, 2];

// 写法一
const a2 = [...a1];
a2[0] = 2;
a1 // [1, 2]

// 写法二
const [...a2] = a1;
a2[0] = 2;
a1 // [1, 2]

用途:获取数组除了某几项的其他项

1
2
3
let num = [1, 3, 5, 7, 9];
let [first, second, ...rest] = num;
rest; // [5, 7, 9]

用途:函数调用

1
2
3
4
5
6
function add(x, y) {
return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

数组扩展方法

Array.from 方法用于将两类对象转为真正的数组:

  1. 类似数组的对象(如 String、NodeList、arguments),所谓类似数组的对象,本质特征只有一点,即必须有 length 属性。
  2. 可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
1
2
3
4
5
6
7
8
9
10
11
12
let arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
}

// ES5的写法
var arr1 = [].slice.call(arrayLike) // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike) // ['a', 'b', 'c']

Array.of 方法用于将一组值,转换为数组。Array.of 基本上可以用来替代 Array()或 new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。

1
2
console.log(Array.of()); // []
console.log(Array.of(1, 3, 5, 7, 9)); // [1, 3, 5, 7, 9]

数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined

数组实例的 findIndex 方法的用法与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

1
2
console.log([1, 4, -5, -1, 10].find((n) => n < 0)); // -5
console.log([1, 4, -5, -1, 10].findIndex((n) => n < 0)); // 2

fill 方法使用给定值,填充一个数组。常用于空数组的初始化。

1
2
console.log(['a', 'b', 'c'].fill(7)); // [7, 7, 7]
console.log(new Array(3).fill(7)); // [7, 7, 7]

entries(),keys()和 values()——用于遍历数组,它们都返回一个遍历器对象。keys()是对键名(索引)的遍历、values()是对键值的遍历,entries()是对键值对的遍历。可以配合 for…of 循环进行遍历。

1
2
3
4
5
6
7
8
9
10
11
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"
}

Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。

1
2
3
console.log([1, 2, 3].includes(2)); // true
console.log([1, 2, 3].includes(4)); // false
console.log([1, 2, NaN].includes(NaN)); // true

Array.prototype.flat 方法用于将嵌套的数组“拉平”,变成一维的数组。

对象的扩展

对象属性的简洁表示法

ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ES5
var o = 1,
k = 2;
var obj1 = {
o: o,
k: k,
name: 'bxm',
getName: function() {
console.log(this.name);
}
};

// ES6
let c = 1,
n = 2;
let obj2 = {
c, // 等同于 c: c
n,
name: 'bai',
getName() { // 等同于 getName: function() {}
console.log(this.name);
}
};

用途:

  • 用于函数的返回值
  • CommonJS 模块、ES6 模块输出一组变量
1
2
3
4
5
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
1
module.exports = { getItem, setItem, clear };

属性表达式

现在,对象的属性可以是一个变量或表达式

1
2
3
4
5
6
7
8
9
10
11
for (let i = 0; i < 5; i++) {
let obj = {
[i]: 'value',
}
console.log(obj)
}
// Object {0: "value"}
// Object {1: "value"}
// Object {2: "value"}
// Object {3: "value"}
// Object {4: "value"}

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
const obj = {
id: 5,
name: 'San Francisco',
}
obj[getKey('enabled')] = true

// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
}

对象遍历

Object.keys(),Object.values(),Object.entries()

Object.assign()

Object.assign() 又叫做==浅拷贝==,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign() 的第一个参数是目标对象,后面的参数都是源对象。

1
Object.assign({ a: '1' }, { b: '2' }) // Object {a: "1", b: "2"}

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

1
2
3
4
5
6
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }

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

最佳实践:在实际项目中,我们为了不改变源对象,一般会把目标对象传为{}

1
2
3
4
const obj = Object.assign({}, objA, objB)

// 给对象添加属性
this.seller = Object.assign({}, this.seller, response.data)

小技巧:扩展运算符也可以实现 Object.assign() 的功能:

1
2
3
4
5
6
7
let aClone = { ...a }
// 等同于
let aClone = Object.assign({}, a)

let ab = { ...a, ...b }
// 等同于
let ab = Object.assign({}, a, b)

用途:

  • ==为属性指定默认值,然后用来覆盖默认的配置选项(config)==
  • 为对象添加属性和方法
  • 合并多个对象
  • 对象浅克隆
1
options = Object.assign({}, defaultConfig, options)

Object.getOwnPropertyDescriptors()

ES2017

返回指定对象的某个指定属性的属性描述符,该属性必须是对象自己定义而不是继承自原型链

Object.getOwnPropertyDescriptors()相当于Object.getOwnPropertyDescriptor()的复数形式,可以获取对象的所有自身属性的描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[object Object] {
configurable:当该值为true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable:当且仅当该属性的 enumerable 为 true时,该属性才能够出现在对象的枚举属性中。默认为 false。
value
writable
get
set
}

[object Object] {
configurable: true,
enumerable: true,
value: ["React Quickly"],
writable:
}

函数的扩展

函数参数的默认值

ES5 给函数定义参数默认值,使用的是短路运算,有一定缺陷和冗余。

1
2
3
4
5
6
7
8
9
10
// ES5
function foo(num) {
num = num || 200;
return num;
}

// ES6
function foo(num = 200) {
return num;
}

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

1
2
3
4
5
function foo({x, y = 5} = {}) {
console.log(x, y);
}

foo() // undefined 5

通常情况下,默认值后面不能再有没有默认值的参数,即定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

另外,可以将参数默认值设为 undefined,表明这个参数是可以省略的。

1
function foo(optional = undefined) { ··· }

rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

1
2
3
4
function rest(...args) { console.log(args); }
rest(); // []
rest(1); // [1]
rest(1,2); // [1, 2]
1
2
3
4
function foo(x, y, ...rest) {
return ((x + y) * rest.length);
}
foo(1, 2, 'hello', true, 7); // 9

最佳实践:不要在函数体内使用 arguments 变量,使用 rest 运算符(…)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。

1
2
3
4
5
6
7
8
9
10
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}

// good
function concatenateAll(...args) {
return args.join('');
}

箭头函数

ES6 允许使用“箭头”(=>)定义函数,箭头函数是函数的快捷写法,使得表达更加简洁,有以下特性。

  • 不需要 function 关键字来创建函数
  • 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分;
  • 而当你的函数有且仅有一个参数的时候,是可以省略掉括号()
  • 当你函数中有且仅有一个表达式的时候,可以省略return语句和大括号{}
  • 而如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ES5
var arr1 = [1, 2, 3];
var newArr1 = arr1.map(function(x) { return x + 1; });

// ES6
let arr2 = [1, 2, 3];
let newArr2 = arr2.map((x) => { x + 1 });

// 箭头函数使得表达更加简洁
let isEven = n => n % 2 == 0;
let square = n => n * n;
let f = () => 5; // 等同于 let f = function () { return 5 };
let f = v => v; // 等同于 let f = function (v) { return v; };
let sum = (num1, num2) => num1 + num2; // 等同于 let sum = function(num1, num2) { return num1 + num2; };
[1,2,3].map(x => x * x); // 等同于 [1,2,3].map(function (x) { return x * x; });

立即执行函数可以写成箭头函数的形式。

1
2
3
(() => {
console.log('Welcome to the Internet.');
})();

箭头函数中的 this 绑定

箭头函数让复杂的 this 变得清晰:

  • ==箭头函数内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。即,this 对象的指向是可变的,但是在箭头函数中,它是固定的。==
  • 箭头函数中的 this 继承当前上下文的 this 关键字,即箭头函数中 this 的作用域和外面是一样的,==并不产生新的作用域==。
  • 本质上,箭头函数里面根本没有自己的 this,而是引用外层的 this。
  • 箭头函数可以绑定 this 对象,大大减少了显式绑定 this 对象的写法(call、apply、bind)。
  • 箭头函数取代 Function.prototype.bind,不应再用 self/_this/that 绑定 this。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 。运行下面的代码会报错,这是因为setTimeout中的this指向的是全局对象。
class Animal {
constructor() {
this.type = 'animal';
}
says(say) {
setTimeout(function() {
console.log(this.type + ' says ' + say);
}, 1000);
}
}

var animal = new Animal();
animal.says('hi'); //undefined says hi

// 解决办法:

// 传统方法1: 将this传给self,再用self来指代this
says(say) {
var self = this;
setTimeout(function() {
console.log(self.type + ' says ' + say);
}, 1000);
}

// 传统方法2: 用bind(this),即
says(say) {
setTimeout(function() {
console.log(this.type + ' says ' + say);
}.bind(this), 1000);
}

// ES6: 箭头函数
says(say) {
setTimeout(() => {
console.log(this.type + ' says ' + say);
}, 1000);
}

函数参数的尾逗号

现在,函数参数与数组和对象的尾逗号规则,保持一致了。函数定义和调用时,尾部允许有一个逗号。这样可以避免一些不必要的报错。

1
2
3
4
5
6
7
8
9
function clownsEverywhere(
param1,
param2
) { /* ... */ }

clownsEverywhere(
'foo',
'bar'
);

编程风格:单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾,方便扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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,
}

尾调用

优化递归性能