ES6知识点(一)

一、声明变量 3

一、声明变量

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,还有两种声明变量的方法:import命令和class命令。
二、字符串

  1. 新增字符串遍历for...of

for (let codePoint of 'foo') {

console.log(codePoint)}

// "f"// "o"// "o"

  1. 模板字符 ``

$('#result').append(`

There are <b>${basket.count}</b> items

in your basket, <em>${basket.onSale}</em>

are on sale!

`);

  1. 确定一个字符串是否包含在另一个字符串中

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

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

let s = 'Hello world!';

s.startsWith('Hello') // true

s.endsWith('!') // true

s.includes('o') // true

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

let s = 'Hello world!';

s.startsWith('world', 6) // true

s.endsWith('Hello', 5) // true

s.includes('Hello', 6) // false

上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

  1. 复制

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

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

'hello'.repeat(2) // "hellohello"

'na'.repeat(0) // ""

参数如果是小数,会被取整。

'na'.repeat(2.9) // "nana"

如果repeat的参数是负数或者Infinity,会报错。

'na'.repeat(Infinity)

// RangeError

'na'.repeat(-1)

// RangeError

但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0。

'na'.repeat(-0.9) // ""

参数NaN等同于 0。

'na'.repeat(NaN) // ""

如果repeat的参数是字符串,则会先转换成数字。

'na'.repeat('na') // ""

'na'.repeat('3') // "nanana"

5、字符串补全功能
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

'x'.padEnd(4, 'ab') // 'xaba'

如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。

'abc'.padStart(10, '0123456789')

// '0123456abc'

如果省略第二个参数,默认使用空格补全长度。

'x'.padStart(4) // ' x''x'.padEnd(4) // 'x '

padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。

'1'.padStart(10, '0') // "0000000001"'12'.padStart(10, '0') // "0000000012"'123456'.padStart(10, '0') // "0000123456"

提示字符串格式。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"

'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

6、去空格

新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = ' abc ';


 

s.trim() // "abc"

s.trimStart() // "abc "

s.trimEnd() // " abc"

上面代码中,trimStart()只消除头部的空格,保留尾部的空格。trimEnd()也是类似行为。

除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。

浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名。

  1. 替换

历史上,字符串的实例方法replace()只能替换第一个匹配。

'aabbcc'.replace('b', '_')

// 'aa_bcc'

上面例子中,replace()只将第一个b替换成了下划线。

如果要替换所有的匹配,不得不使用正则表达式的g修饰符。

'aabbcc'.replace(/b/g, '_')

// 'aa__cc'

正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()方法,可以一次性替换所有匹配。

'aabbcc'.replaceAll('b', '_')

// 'aa__cc'

它的用法与replace()相同,返回一个新字符串,不会改变原字符串。

String.prototype.replaceAll(searchValue, replacement)

上面代码中,searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)。

如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。这一点跟replace()不同。

// 不报错'aabbcc'.replace(/b/, '_')

// 报错'aabbcc'.replaceAll(/b/, '_')

上面例子中,/b/不带有g修饰符,会导致replaceAll()报错。

replaceAll()的第二个参数replacement是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。

  • $&:匹配的子字符串。

  • $` :匹配结果前面的文本。

  • $':匹配结果后面的文本。

  • $n:匹配成功的第n组内容,n是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。

  • $$:指代美元符号$

下面是一些例子。

// $& 表示匹配的字符串,即`b`本身// 所以返回结果与原字符串一致'abbc'.replaceAll('b', '$&')

// 'abbc'

// $` 表示匹配结果之前的字符串// 对于第一个`b`,$` 指代`a`// 对于第二个`b`,$` 指代`ab`'abbc'.replaceAll('b', '$`')

// 'aaabc'

// $' 表示匹配结果之后的字符串// 对于第一个`b`,$' 指代`bc`// 对于第二个`b`,$' 指代`c`'abbc'.replaceAll('b', `$'`)

// 'abccc'

// $1 表示正则表达式的第一个组匹配,指代`ab`// $2 表示正则表达式的第二个组匹配,指代`bc`'abbc'.replaceAll(/(ab)(bc)/g, '$2$1')

// 'bcab'

// $$ 指代 $'abc'.replaceAll('b', '$$')

// 'a$c'

replaceAll()的第二个参数replacement除了为字符串,也可以是一个函数,该函数的返回值将替换掉第一个参数searchValue匹配的文本。

'aabbcc'.replaceAll('b', () => '_')

// 'aa__cc'

上面例子中,replaceAll()的第二个参数是一个函数,该函数的返回值会替换掉所有b的匹配。

这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。

const str = '123abc456';

const regex = /(\d+)([a-z]+)(\d+)/g;

function replacer(match, p1, p2, p3, offset, string) {

return [p1, p2, p3].join(' - ');}


 

str.replaceAll(regex, replacer)

// 123 - abc - 456

上面例子中,正则表达式有三个组匹配,所以replacer()函数的第一个参数match是捕捉到的匹配内容(即字符串123abc456),后面三个参数p1p2p3则依次为三个组匹配。

  1. Math对象的扩展

  1. 取整

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

对于非数值,Math.trunc内部使用Number方法将其先转为数值。

Math.trunc('123.456') // 123

Math.trunc(true) //1

Math.trunc(false) // 0

Math.trunc(null) // 0

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

Math.trunc(NaN); // NaN

Math.trunc('foo'); // NaN

Math.trunc(); // NaN

Math.trunc(undefined) // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.trunc = Math.trunc || function(x) {

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

};

2、判断一个数到底是正数、负数、还是零

Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

它会返回五种值。

  • 参数为正数,返回+1

  • 参数为负数,返回-1

  • 参数为 0,返回0

  • 参数为-0,返回-0;

  • 其他值,返回NaN

Math.sign('') // 0

Math.sign(true) // +1

Math.sign(false) // 0

Math.sign(null) // 0

Math.sign('9') // +1

Math.sign('foo') // NaN

Math.sign() // NaN

Math.sign(undefined) // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.sign = Math.sign || function(x) {

x = +x; // convert to a number

if (x === 0 || isNaN(x)) {

return x;

}

return x > 0 ? 1 : -1;

};

3、计算一个数的立方根。

Math.cbrt(-1) // -1

Math.cbrt(0) // 0

Math.cbrt(1) // 1

Math.cbrt(2) // 1.2599210498948732

Math.cbrt('8') // 2

Math.cbrt('hello') // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.cbrt = Math.cbrt || function(x) {

var y = Math.pow(Math.abs(x), 1/3);

return x < 0 ? -y : y;

};

4Math.imul

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

Math.imul(2, 4) // 8Math.imul(-1, 8) // -8Math.imul(-2, -2) // 4

如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)a * b的结果是相同的,即该方法等同于(a * b)|0的效果(超过 32 位的部分溢出)。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

(0x7fffffff * 0x7fffffff)|0 // 0

上面这个乘法算式,返回结果为 0。但是由于这两个二进制数的最低位都是 1,所以这个结果肯定是不正确的,因为根据二进制乘法,计算结果的二进制最低位应该也是 1。这个错误就是因为它们的乘积超过了 2 的 53 次方,JavaScript 无法保存额外的精度,就把低位的值都变成了 0。Math.imul方法可以返回正确的值 1。

Math.imul(0x7fffffff, 0x7fffffff) // 1

  1. 一个数的32位单精度浮点数形式

Math.fround方法返回一个数的32位单精度浮点数形式。

对于32位单精度格式来说,数值精度是24个二进制位(1 位隐藏位与 23 位有效位),所以对于 -224 224 之间的整数(不含两个端点),返回结果与参数本身一致。

Math.fround(0) // 0Math.fround(1) // 1Math.fround(2 ** 24 - 1) // 16777215

如果参数的绝对值大于 224,返回的结果便开始丢失精度。

Math.fround(2 ** 24) // 16777216Math.fround(2 ** 24 + 1) // 16777216

Math.fround方法的主要作用,是将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致)。

// 未丢失有效精度Math.fround(1.125) // 1.125Math.fround(7.25) // 7.25

// 丢失精度Math.fround(0.3) // 0.30000001192092896Math.fround(0.7) // 0.699999988079071Math.fround(1.0000000123) // 1

对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。

Math.fround(NaN) // NaNMath.fround(Infinity) // Infinity

Math.fround('5') // 5Math.fround(true) // 1Math.fround(null) // 0Math.fround([]) // 0Math.fround({}) // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.fround = Math.fround || function (x) {

return new Float32Array([x])[0];};

  1. 求所有参数的平方和的平方根

Math.hypot方法返回所有参数的平方和的平方根。

Math.hypot(3, 4); // 5Math.hypot(3, 4, 5); // 7.0710678118654755Math.hypot(); // 0Math.hypot(NaN); // NaNMath.hypot(3, 4, 'foo'); // NaNMath.hypot(3, 4, '5'); // 7.0710678118654755Math.hypot(-3); // 3

上面代码中,3 的平方加上 4 的平方,等于 5 的平方。

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

  1. 对数

1Math.expm1()

Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1

Math.expm1(-1) // -0.6321205588285577Math.expm1(0) // 0Math.expm1(1) // 1.718281828459045

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.expm1 = Math.expm1 || function(x) {

return Math.exp(x) - 1;};

2Math.log1p()

Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN

Math.log1p(1) // 0.6931471805599453Math.log1p(0) // 0Math.log1p(-1) // -InfinityMath.log1p(-2) // NaN

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log1p = Math.log1p || function(x) {

return Math.log(1 + x);};

3Math.log10()

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

Math.log10(2) // 0.3010299956639812Math.log10(1) // 0Math.log10(0) // -InfinityMath.log10(-2) // NaNMath.log10(100000) // 5

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log10 = Math.log10 || function(x) {

return Math.log(x) / Math.LN10;};

4Math.log2()

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

Math.log2(3) // 1.584962500721156Math.log2(2) // 1Math.log2(1) // 0Math.log2(0) // -InfinityMath.log2(-2) // NaNMath.log2(1024) // 10Math.log2(1 << 29) // 29

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.log2 = Math.log2 || function(x) {

return Math.log(x) / Math.LN2;};

  1. 双曲函数

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)

9、指数运算符 **

这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)2 ** 3 ** 2

// 512

上面代码中,首先计算的是第二个指数运算符,而不是第一个。

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

let a = 1.5;

a **= 2;

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

b **= 3;

// 等同于 b = b * b * b;

四、函数

  1. 函数参数默认值

允许为函数的参数设置默认值,即直接写在参数定义的后面。参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。需写在函数末尾

function Point(x = 0, y = 0) {

this.x = x;

this.y = y;

}

const p = new Point();

p // { x: 0, y: 0 }

可以判断参数y是否被赋值,如果没有,再等于默认值。

if (typeof y === 'undefined') {

y = 'World';

}

2rest函数 ...

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

function add(...values) {

let sum = 0;

for (var val of values) {

sum += val;

}

return sum;

}

add(2, 5, 3) // 10


 

const sortNumbers = (...numbers) => numbers.sort();


 

3、箭头函数 =>

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;

// 等同于var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;

// 等同于var sum = function(num1, num2) {

return num1 + num2;};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错let getTempItem = id => { id: id, name: "Temp" };

// 不报错let getTempItem = id => ({ id: id, name: "Temp" });

下面是一种特殊情况,虽然可以运行,但会得到错误的结果。

let foo = () => { a: 1 };foo() // undefined

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于

function full(person) {

return person.first + ' ' + person.last;

}

箭头函数使得表达更加简洁。

const isEven = n => n % 2 === 0;

const square = n => n * n;

上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

箭头函数的一个用处是简化回调函数。

// 正常函数写法

[1,2,3].map(function (x) {

return x * x;}

);

// 箭头函数写法[1,2,3].map(x => x * x);

另一个例子是

// 正常函数写法

var result = values.sort(function (a, b) {

return a - b;

});

// 箭头函数写法var result = values.sort((a, b) => a - b);

下面是 rest 参数与箭头函数结合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)

// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)

// [1,[2,3,4,5]]

箭头函数有几个使用注意点。

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

2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

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

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。

var handler = {

id: '123456',


 

init: function() {

document.addEventListener('click',

event => this.doSomething(event.type), false);

},


 

doSomething: function(type) {

console.log('Handling ' + type + ' for ' + this.id);

}};

上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

不适用场合

由于箭头函数使得this从“动态”变成“静态”,下面两个场合不应该使用箭头函数。

第一个场合是定义对象的方法,且该方法内部包括this

const cat = {

lives: 9,

jumps: () => {

this.lives--;

}}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');

button.addEventListener('click', () => {

this.classList.toggle('on');});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

另外,如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。

嵌套箭头函数

const plus1 = a => a + 1;

const mult2 = a => a * 2;

mult2(plus1(5))

// 12

箭头函数还有一个功能,就是可以很方便地改写 λ 演算。

// λ演算的写法fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

// ES6的写法var fix = f => (x => f(v => x(x)(v)))

(x => f(v => x(x)(v)));

递归函数

function factorial(n, total = 1) {

if (n === 1) return total;

return factorial(n - 1, n * total);

}

factorial(5) // 120

上面代码中,参数total有默认值1,所以调用时不用提供这个值。

总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

五、数组

1、扩展运算符 ...

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。

function push(array, ...items) {

array.push(...items);}

function add(x, y) {

return x + y;

}

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

  1. 复制数组

数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

const a1 = [1, 2];

const a2 = a1;


 

a2[0] = 2;

a1 // [2, 2]

上面代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。

ES5 只能用变通方法来复制数组。

const a1 = [1, 2];

const a2 = a1.concat();


 

a2[0] = 2;

a1 // [1, 2]

上面代码中,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。

扩展运算符提供了复制数组的简便写法。

const a1 = [1, 2];

// 写法一const a2 = [...a1];

// 写法二const [...a2] = a1;

上面的两种写法,a2都是a1的克隆。

2)合并数组

const a1 = [{ foo: 1 }];

const a2 = [{ bar: 2 }];

const a3 = a1.concat(a2); //es5写法

const a4 = [...a1, ...a2]; //es6写法


 

a3[0] === a1[0] // true

a4[0] === a1[0] // true

两种方法都是浅拷贝,使用的时候需要注意。上面代码中,a3a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。

3)与解构赋值组合

与解构赋值结合起来,用于生成数组。

// ES5

a = list[0], rest = list.slice(1)

// ES6

[a, ...rest] = list

  1. 字符串

将字符串转化为数组

[...'hello']

// [ "h", "e", "l", "l", "o" ]

5实现了 Iterator 接口的对象

Number.prototype[Symbol.iterator] = function*() {

let i = 0;

let num = this.valueOf();

while (i < num) {

yield i++;

}}


 

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

上面代码中,先定义了Number对象的遍历器接口,扩展运算符将5自动转成Number实例以后,就会调用这个接口,就会返回自定义的结果。

对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

let arrayLike = {

'0': 'a',

'1': 'b',

'2': 'c',

length: 3};

// TypeError: Cannot spread non-iterable object.let arr = [...arrayLike];

上面代码中,arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。

6Map Set 结构,Generator 函数

扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([

[1, 'one'],

[2, 'two'],

[3, 'three'],]);

let arr = [...map.keys()]; // [1, 2, 3]

Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

const go = function*(){

yield 1;

yield 2;

yield 3;};

[...go()] // [1, 2, 3]

上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。

如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。

const obj = {a: 1, b: 2};let arr = [...obj]; // TypeError: Cannot spread non-iterable object

  1. Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

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.prototype.slice方法替代。

const toArray = (() =>

Array.from ? Array.from : obj => [].slice.call(obj))();

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);

// 等同于

Array.from(arrayLike).map(x => x * x);


 

Array.from([1, 2, 3], (x) => x * x)

// [1, 4, 9]

下面的例子是取出一组 DOM 节点的文本内容。

let spans = document.querySelectorAll('span.name');

// map()

let names1 = Array.prototype.map.call(spans, s => s.textContent);

// Array.from()

let names2 = Array.from(spans, s => s.textContent)

下面的例子将数组中布尔值为false的成员转为0

Array.from([1, , 2, , 3], (n) => n || 0)

// [1, 0, 2, 0, 3]

另一个例子是返回各种数据的类型。

function typesOf () {

return Array.from(arguments, value => typeof value)

}typesOf(null, [], NaN)

// ['object', 'object', 'number']

如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this

Array.from()可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。

Array.from({ length: 2 }, () => 'jack')

// ['jack', 'jack']

上面代码中,Array.from的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。

Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于\uFFFFUnicode 字符,算作两个字符的 bug。

function countSymbols(string) {

return Array.from(string).length;

}

  1. Array.of()

Array.of()方法用于将一组值,转换为数组。

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

Array.of(3) // [3]

Array.of(3).length // 1

4、复制 copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三个参数。

  • target(必需):从该位置开始替换数据。如果为负值,表示倒数。

  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。

  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。

这三个参数都应该是数值,如果不是,会自动转为数值。

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

// [4, 5, 3, 4, 5]

上面代码表示将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2。

// 将3号位复制到0号位

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

// [4, 2, 3, 4, 5]

// -2相当于3号位,-1相当于4号位

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

// [4, 2, 3, 4, 5]

// 将3号位复制到0号位

[].copyWithin.call({length: 5, 3: 1}, 0, 3)

// {0: 1, 3: 1, length: 5}

// 将2号位到数组结束,复制到0号位

let i32a = new Int32Array([1, 2, 3, 4, 5]);

i32a.copyWithin(0, 2);

// Int32Array [3, 4, 5, 4, 5]

// 对于没有部署 TypedArray 的 copyWithin 方法的平台// 需要采用下面的写法

[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);

// Int32Array [4, 2, 3, 4, 5]

5查找数组成员 find() findIndex()

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

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

// -5

上面代码找出数组中第一个小于 0 的成员。

[1, 5, 10, 15].find(function(value, index, arr) {

return value > 9;}) // 10

上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

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

[1, 5, 10, 15].findIndex(function(value, index, arr) {

return value > 9;}) // 2

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

function f(v){

return v > this.age;}let person = {name: 'John', age: 20};[10, 12, 26, 15].find(f, person); // 26

上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。

另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

[NaN].indexOf(NaN)

// -1[NaN].findIndex(y => Object.is(NaN, y))

// 0

上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。

6数组填充 fill()

fill方法使用给定值,填充一个数组。

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

// [7, 7, 7]new Array(3).fill(7)

// [7, 7, 7]

上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。

fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

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

// ['a', 7, 'c']

上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。

注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

let arr = new Array(3).fill({name: "Mike"});

arr[0].name = "Ben";

arr

// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]let arr = new Array(3).fill([]);

arr[0].push(5);

arr

// [[5], [5], [5]]

7遍历数组 entries()keys() values()

ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

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

}

8、包含 includes()

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

[1, 2, 3].includes(2) // true

[1, 2, 3].includes(4) // false

[1, 2, NaN].includes(NaN) // true

Map 和 Set 数据结构有一个has方法,需要注意与includes区分。

  • Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target, propertyKey)

  • Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

9拉平多维数组 flat()flatMap()

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, 4]].flat()

// [1, 2, 3, 4]

上面代码中,原数组的成员里面有一个数组,flat()方法将子数组的成员取出来,添加在原来的位置。

flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

[1, 2, [3, [4, 5]]].flat()

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

// [1, 2, 3, 4, 5]

上面代码中,flat()的参数为2,表示要“拉平”两层的嵌套数组。

如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

[1, [2, [3]]].flat(Infinity)

// [1, 2, 3]

如果原数组有空位,flat()方法会跳过空位。

[1, 2, , 4, 5].flat()

// [1, 2, 4, 5]

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

// 相当于 [[2, 4], [3, 6], [4, 8]].flat()[2, 3, 4].flatMap((x) => [x, x * 2])

// [2, 4, 3, 6, 4, 8]

flatMap()只能展开一层数组。

// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()[1, 2, 3, 4].flatMap(x => [[x * 2]])

// [[2], [4], [6], [8]]

上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。

flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。

arr.flatMap(function callback(currentValue[, index[, array]]) {

// ...}[, thisArg])

flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this
 

参考文档:ES6入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值