今天是周六,虽然没有什么事情,但是也来公司补(mō)班(yú)了。但是感觉一直没有进入状态,有点想睡午觉,果然还是因为工作不够饱和的缘故吗?这次索性把之前学的 ES6 新增 API 相关的知识在归纳一下,打打字,让自己补(mō)班(yú)不感觉那么无聊。

这次博客介绍的是在 ES6 中新增的一些 API,其中有一些可能都已经用得比较熟练了,都没有注意是不是 ES6 的。总得来说比较简单,大概就是对之前看的课程做一下回顾吧。

封面

Object.assign (分配、转让)

Object.assign方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回新的对象。通俗地讲就是将一个对象的值转移给另一个对象:

const a = { a1: 1, a2: 2 };
const b = { b1: 1, b2: 2 };
const c = Object.assign(a, b); // {a1: 1, a2: 2, b1: 1, b2: 2}
a === c // true

在这例子中,b中的属性被置于a中,a的值会被改变为返回的新的对象的值。此时如果b一些属性名与a中相同,b中的属性值会覆盖到a中。Object.assign只拷贝可枚举属性,如__proto__等属性不会被拷贝。Object.assign可接受多个参数,如Object.assign(a, b, c),此时c的数字也会被加入a中,而且c的属性高于b

Object.assign的拷贝是浅拷贝,赋值相当于复制内存中的有效内容给变量,但对象存储的是地址。对大多数语言来说,因为难以明确对象包含的内容(比如函数),导致难以实现完美的深拷贝。Object.assign是通过使用原对象的get和目标对象的set操作对象属性的,并不会跳过值为nullundefined的属性。

Array 的新增 API

Array.from

Array.from方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。简单地讲就是试图将不是数组的数据转为数组,如伪数组、字符串、集合、枚举等:

Array.from('foo');
// (3) ["f", "o", "o"]
Array.from({ '0': 0, '1': 1, '2': 2, 'length': 3 })
// (3) [0, 1, 2]

在 ES5,实际上已经有可以将数据转为数据的方式,但是使用起来更为繁琐。Array.prototype.splice()被用于替换数组内的值,Array.prototype.splice(0)即会得到原数组,若此时原数组的值并不是数组,便会试图将其转换为数组:

Array.prototype.slice.call('foo', 0)
// (3) ["f", "o", "o"]
Array.prototype.slice.call({ '0': 0, '1': 1, '2': 2, 'length': 3 }, 0)
// (3) [0, 1, 2]

Array.from可以接受函数为第二个参数,如果指定了该参数,新数组中的每个元素会执行该回调函数。让你可以在最后生成的数组上再执行一次map方法后再返回。也就是说Array.from(obj, mapFn)就相当于Array.from(obj).map(mapFn),除非创建的不是可用的中间数组。比如:

Array.from([1, 2, 3], x => x + x)
// (3) [2, 4, 6]
Array.from({ length: 5 }, (v, i) => i)
// (5) [0, 1, 2, 3, 4]

需要注意的是,通过Array.from创建的空值数组和用new Array()创建的空值数组是有区别的,new Array()创建的数组没有下标,没法使用map方法。不过在 ES5 中,也有创建有下标的空数组的方法,也是利用了apply方法的第二个参数会进行类型转换的特性:

Array.from({ length: 5 })
// (5) [undefined, undefined, undefined, undefined, undefined]
const arr = new Array(5)
// (5) [empty × 5]
arr.map((v, i) => i)
// (5) [empty × 5]
Array.apply(null, { length: 5 })
// (5) [undefined, undefined, undefined, undefined, undefined]

Array.of

Array.of()方法创建一个具有可变数量参数的新数组实例,Array 构造函数之间的区别在于不考虑参数的数量或类型。例如:

Array.of(7)
// [7]
Array.of(1, 2, 3)
// (3) [1, 2, 3]
Array.of(undefined)
// [undefined]
new Array(7)
// (7) [empty × 7]
new Array(1, 2, 3)
// (3) [1, 2, 3]
new Array(undefined)
// [undefined]

Array.prototype.find 和 Array.prototype.findIndex

find方法返回数组中满足提供的测试函数的第一个元素的值,findIndex则是返回元素的下标。否则返回undefined。与filter(过滤)的区别find只返回符合条件的第一个值,filter会将符合条件的所有值以数组的形式返回。

const array1 = [5, 12, 8, 130, 44];
const found1 = array1.find(element => element > 10);
const found2 = array1.find(element => element > 10);
const found3 = array1.findIndex(element => element > 10);
console.log(found1); // 12
console.log(found2); // (3) [12, 130, 44]
console.log(found3); // 1

Array.prototype.copyWithin

copyWithin方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

const array1 = ['a', 'b', 'c', 'd', 'e'];
array1.copyWithin(0, 3, 4);
// (5) ["d", "b", "c", "d", "e"]
array1.copyWithin(1, 3);
// (5) ["d", "d", "e", "d", "e"]

copyWithin的第一个参数代表需要数字需要被替换的地方开始的位置,第二个参数代表数组复制部分开始的位置(默认为0),第三个参数代表数组复制部分结束的位置(不包含在复制内容中,默认为arr.length)。此方***改变原数组

Array.prototype.entries

entries() 方法返回一个新的Array Iterator(迭代器)对象,该对象包含数组中每个索引的键/值对。例如:

const array1 = ['a', 'b', 'c'];
const iterator1 = array1.entries();
console.log(iterator1.next().value);
// (2) [0, "a"]
console.log(iterator1.next().value);
// (2) [1, "b"]
console.log(iterator1.next().value);
// (2) [2, "c"]
console.log(iterator1.next().value);
// undefined

String 的新增 API

String.prototype.includes

includes方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。在 ES5 中,同样有许多方式可以实现检测字符串的效果:

'Blue Whale'.includes('blue'); // returns false
'Blue Whale'.indexOf('blue') >= 0; // returns false(大于等于0时代表包含某段字符串)
'Blue Whale'.search(/blue/) >= 0; // returns false(使用正则搜索)
'Blue Whale'.match(/blue/); // returns null(使用正则匹配)

String.prototype.repeat

repeat可以将字符串重复多次,并返回新的字符串。接受的参数为字符串重复的次数,将会被自动转换成整数。例如:

"abc".repeat(2)
// "abcabc"
"abc".repeat(3.5)
// "abcabcabc"
"abc".repeat('3.5')
// "abcabcabc"

String.prototype.startsWith 和 String.prototype.endsWith

startsWithendsWith可以用来判断字符串是否以某段字符串开头或结尾。根据判断结果返回 true 或 false。startsWith等同于str.indexOf(searchString) === 0endsWith等同于str.lastIndexOf(searchString) === str.length - searchString.length

Number 的新增 API

Number.EPSILON

Number.EPSILON属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值。由于 JavaScript 中的浮点书运算可能会丢失精度,ES6 添加了Number.EPSILON这个常量用于表示 JavaScript可做到的最小误差。比如:

// 检测是否相等
const x = 0.2;
const y = 0.3;
const z = 0.1;
const equal = (Math.abs(x - y + z) < Number.EPSILON); // true

// 避免死循环
let i = 0;
while (Math.abs(i - 1) < Number.EPSILON) {
    i += 0.1;
}

Number.isInteger、Number.isFinite、Number.isNaN

Number.isInteger用来检测一个数值是否整数,如果被检测的值是整数,则返回 true,否则返回 false。注意 NaN 和正负 Infinity 不是整数。效果等同于data === parseInt(data, 10)。此外,Number.isSafeInteger则可以判断传入的参数值是否是一个「安全整数」,即可以使用 IEEE-754 直接表示的整数。

Number.isFinite用于检测一个数值是否有穷数。返回一个布尔值表示给定的值是否是一个有穷数。和全局的isFinite()函数相比,这个方法不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的(finite),才返回 true:

Number.isFinite(1.23); 
// true
Number.isFinite(Math.PI); 
// true
Number.isFinite('0'); 
// false
isFinite('0'); 
// true

Number.isNaN方法确定传递的值是否为 NaN,并且检查其类型是否为 Number。它是原来的全局isNaN()的更稳妥的版本。在 JavaScript 中,NaN 是数值中的一类值,代表不可表示的值。最特殊的地方就是,我们不能使用相等运算符(== 和 ===)来判断一个值是否是 NaN,因为NaN == NaNNaN === NaN都会返回 false。因此,必须要有一个判断值是否是 NaN 的方法。

和全局函数isNaN()相比,Number.isNaN()不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。

Number.isNaN(NaN);        // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0)       // true

// 下面这几个如果使用全局的 isNaN() 时,会返回 true。
Number.isNaN("NaN");      // false,字符串 "NaN" 不会被隐式转换成数字 NaN。
Number.isNaN(undefined);  // false
Number.isNaN({});         // false
Number.isNaN("blabla");   // false

Math 的新增 API

Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。Math 用于 Number 类型。它不支持 BigInt。在平时用得不多,这里大概把新增的 API 简单地过一遍:

  • Math.acosh():返回一个数字的反双曲余弦值;
  • Math.hypot():返回它的所有参数的平方和的平方根(和勾股定理有些相似);
  • Math.imul():返回类似 C 语言 32 位整数相乘的结果;
  • Math.sign():返回一个数字的符号, 指示数字是正数,负数还是零。此函数共有5种返回值, 分别是 1, -1, 0, -0, NaN. 代表的各是正数, 负数, 正零, 负零, NaN。传入该函数的参数会被隐式转换成数字类型;
  • Math.trunc():该方***将数字的小数部分去掉,只保留整数部分。传入该方法的参数会被隐式转换成数字类型。

需要注意的是,与parseInt()不同,Math.trunc()的执行逻辑很简单,仅仅是删除掉数字的小数部分和小数点,不管参数是正数还是负数;而parseInt()在参数数值以'0x'或'0'开头,就会转换为 16 进制或 8 进制。

使用parseInt()截断数字将产生意外结果,不如Math.trunc()稳妥。因为某些数字在其字符串表示形式中使用e字符(例如 6.022×23 表示 6.022e23),因此当对非常大或非常小的数字不能使用parseInt()。 例如:

Math.trunc(123123123123123123123123)
// 1.2312312312312312e+23
parseInt(123123123123123123123123) // 等同于"1.2312312312312312e+23"字符串
// 1

没想到内容有点多,虽然是两节课的内容,但是如果分成两篇博客来写那也没什么营养,因为这些知识可能是属于那种「没有什么用的偏僻知识」。我不是很相信以后的开发当中会用到这个反双曲余弦值的。当然也并非完全没有可取之处,毕竟这些 API 只要不是经常用的,就很容易忘记,偶尔来复习一下也是好的。

结语