ES6知识点(一)

旧城等待, 2022-09-07 12:22 372阅读 0赞

一、声明变量 3

1、 新增字符串遍历for…of 3

2、 模板字符 `` 3

3、 确定一个字符串是否包含在另一个字符串中 3

4、 复制 4

5、字符串补全功能 5

6、去空格 6

7、 替换 7

三、 Math对象的扩展 10

1、 取整 10

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

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

4、Math.imul 12

5、 一个数的32位单精度浮点数形式 13

6、 求所有参数的平方和的平方根 14

7、 对数 14

(1) Math.expm1() 14

(2)Math.log1p() 15

(3)Math.log10() 15

(4)Math.log2() 15

8、 双曲函数 16

9、指数运算符 ** 16

四、函数 17

1、 函数参数默认值 17

2、rest函数 … 17

3、箭头函数 => 18

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

不适用场合 21

嵌套箭头函数 22

递归函数 23

五、数组 23

1、扩展运算符 … 23

(1) 复制数组 24

(2)合并数组 25

(3)与解构赋值组合 25

(4) 字符串 25

(5)实现了 Iterator 接口的对象 25

(6)Map 和 Set 结构,Generator 函数 26

2、 Array.from() 27

3、 Array.of() 29

4、复制 copyWithin() 30

5、查找数组成员 find() 和 findIndex() 31

6、数组填充 fill() 32

7、遍历数组 entries(),keys() 和 values() 33

8、包含 includes() 34

9、拉平多维数组 flat(),flatMap() 35

一、声明变量

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 ${ basket.count} items

in your basket,${ basket.onSale}

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 之间的小数,取整以后等于-0,repeat视同为 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;

functionreplacer(match, p1, p2, p3, offset, string){

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

str.replaceAll(regex, replacer)

// 123 - abc - 456

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

  1. Math**对象的扩展**

  2. 取整

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;

};

4****Math.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){

returnnewFloat32Array([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. 对数

    (**1**) Math.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. 函数参数默认值

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

functionPoint(x =0, y =0){

this.x = x;

this.y = y;

}

const p =newPoint();

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

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

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

y =’World’;

}

2**rest函数 …**

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

functionadd(…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(){ return5};

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;

// 等同于

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

递归函数

functionfactorial(n, total =1){

if(n ===1)return total;

returnfactorial(n -1, n * total);

}

factorial(5) // 120

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

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

五、数组

1**、扩展运算符 …**

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

functionpush(array,…items){

array.push(…items);}

functionadd(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

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

(**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 =newMap([

[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 将大于\uFFFF的 Unicode 字符,算作两个字符的 bug。

functioncountSymbols(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 =newInt32Array([1,2,3,4,5]);

i32a.copyWithin(0,2);

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

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

[].copyWithin.call(newInt32Array([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对象。

functionf(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]newArray(3).fill(7)

// [7, 7, 7]

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

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

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

// [‘a’, 7, ‘c’]

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

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

let arr =newArray(3).fill({ name:”Mike”});

arr[0].name =”Ben”;

arr

// [{name: “Ben”}, {name: “Ben”}, {name: “Ben”}]let arr =newArray(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(functioncallback(currentValue[, index[, array]]){

// …}[, thisArg])

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

参考文档:ES6入门

发表评论

表情:
评论列表 (有 0 条评论,372人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ES6知识总结

    声明变量 let 不能重复声明 块级作用域 可修改let变量的值 , const 不能重复声明 块级作用域 不能修改const 变量的值 2. 箭头函数

    相关 es6模块化基础知识

      1、为何需要模块化:     因为随着前端的代码复杂度越来越高,代码之间会容易有这三个问题(全局变量冲突、函数命名冲突、依赖关系混乱) 2、模块化规范的进阶历史  

    相关 ES6知识汇总(全)

    ES6知识点汇总(全) > 近期在整理ES6相关开发文档,针对ES6新的知识点,以问答形式整理一个全面知识和问题汇总。 一、问:ES6是什么,为什么要学习它,不学习E

    相关 ES6知识总结

    面向对象编程介绍 面向过程和面向对象的对比  面向过程 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 缺点:没有面