乐趣区

关于es6:9数组的扩展

扩大运算符

含意

扩大运算符(spread)是三个点(…), 能够当作 rest 参数的逆运算, 将数组转为用逗号分隔的参数序列
rest 是把传入的参数组合成一个数组
扩大运算符是把数组分解成参数

console.log(...[1,2,3])
//1,2,3
console.log(1,...[2,3,4],5)
//1,2,3,4,5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
  • 该运算符次要用于函数的调用, 将一个数组, 变为参数序列
function push(array,...items){array.push(...items)
}

function add(x,y){return x+y}
const numbers = [4,38]
add(...numbers) //42
  • … 能够与失常的参数联合应用
function f(v,w,x,y,z){console.log(v,w,x,y,z)  //-1 0 1 2 3
}
const args = [0,1]
f(-1,...args,2,...[3]);
  • … 前面还能够搁置表达式
var x = 1
const arr = [...(x>0?['a']:[]),
    'b',
]
console.log(arr) //['a','b']
  • 如果扩大运算符前面是一个空数组,则不产生任何成果。
[...[], 1]
// [1]
  • 只有函数调用时, 扩大运算符才能够放在圆括号中
(...[1, 2])
// Uncaught SyntaxError: Unexpected number

console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number

console.log(...[1, 2])
// 1 2
下面三种状况,扩大运算符都放在圆括号外面,然而前两种状况会报错,因为扩大运算符所在的括号不是函数调用。

代替 apply 办法

es5:
function f(x,y,z){console.log(x,y,z) // 0 1 2
}
var args = [0,1,2];
f.apply(null,args)
因为 f 接管的参数不能为数组,为了不便,能够用 apply 办法来实现用数组的参数来传递,这是很多时候使用的一个小技巧罢了。而 apply 办法第一个参数,是要代替的对象。没有要代替的,用 null, 也是很天然的
es6:
function f(x,y,z){console.log(x,y,z) // 0 1 2
}
var args = [0,1,2]
f(...args)
  • 利用:Math.max
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);
  • 将一个数组增加到另一个数组的尾部
es5:
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1,arr2)
console.log(arr1)//[0,1,2,3,4,5]
push 办法的参数不能是数组,所以只好通过 apply 办法变通应用 push 办法
es6:
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
console.log(arr1)//[0,1,2,3,4,5]
  • 另一个例子
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);

bind.apply:

扩大运算符的利用

复制数组

数组是复合数据类型, 所以间接复制的话是复制指向底层数据结构的指针, 不是一个新数组

var a1 = [1,2]
var a2 = a1
a2[0]= 2
a1 //[2,2]

所以 es5 的变通方法:

var a1 = [1, 2];
var a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

concat: 用于连贯 2 个或多个数组, 不会扭转现有数组, 会返回被连贯的数组的正本
       如果参数是数组, 那么增加的是数组中的元素

es6:

var a1 = [1, 2];
var a2 = [...a1]
或者
var [...a2] = a1

合并数组

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

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

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

a3 和 a4 是用两种不同办法合并而成的新数组,然而它们的成员都是对原数组成员的援用,这就是浅拷贝。
如果批改了援用指向的值,会同步反映到新数组。

var a1 = [{foo:1}]
var a2 = [{bar:2}]

var a3 = a1.concat(a2)
var a4 = [...a1,...a2]

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

与解构赋值联合

用于生成数组

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

rest // list[1]

slice(start,end): 截取数组中选定的元素并返回,
start 为负, 从数组尾部开始算起的地位
end, 可选, 不写则蕴含从 start 到数组完结的所有元素
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

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

const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

字符串

将字符串转为真正的数组

[...'hello']
// ["h", "e", "l", "l", "o"]

实现了 Iterator 接口的对象

)
)

Map,Set 构造,Generator 函数

let map = new Map([['1','one'],
    ['2','two'],
    ['3','three'],
]);
let arr = [...map.keys()]
  • Generator 函数运行后,返回一个遍历器对象,因而也能够应用扩大运算符。
const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]
  • 如果对没有 Iterator 接口的对象,应用扩大运算符,将会报错。
const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

Array.from(arrayLike[, mapFn[, thisArg]])

arrayLike
想要转换成数组的伪数组对象或可迭代对象。mapFn 可选
如果指定了该参数,新数组中的每个元素会执行该回调函数。thisArg 可选
可选参数,执行回调函数 mapFn 时 this 对象。* 用于将相似数组的对象和可遍历的对象转为真正的数组
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.from 将常见的相似数组的对象是 DOM 操作返回的 NodeList 汇合,以及函数外部的 arguments 对象转为数组
let ps = document.querySelectorAll('p')
Array.from(ps).filter(p=>{return p.textContent.length >100})

function foo(){var args = Array.from(arguments)
}
  • 只有是部署了 Iterator 接口的数据结构,Array.from 都能将其转为数组。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
  • 如果参数是一个真正的数组,Array.from 会返回一个截然不同的新数组
var arr1 = [1,2,3]
var arr2 = Array.from(arr1)
arr2 === arr1  //false
  • … 也能够将某些数据结构转为数组
function foo(){var args = [...arguments]
}

[...document.querySelectorAll('div')]

扩大运算符背地调用的是遍历器接口, 如果这个对象没有部署这个接口, 就无奈转换
Array.from 能够转化有 length 属性的相似数组的对象, 而扩大运算符就无奈转换

Array.from({length:3})
// [undefined, undefined, undefined]

下面代码中,Array.from 返回了一个具备三个成员的数组,每个地位的值都是 undefined。
扩大运算符转换不了这个对象。

  • Array.from 的第二个参数, 相似 map, 对每一个元素进行解决
Array.from(arr,x=>x*x)
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

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

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

let names1 = Array.prototype.map.call(spans,s=>s.textContent);
.map(): 返回一个新数组, 原始数组调用函数解决后的值

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']

Array.from 的第一个参数指定了第二个参数运行的次数。这种个性能够让该办法的用法变得非常灵活。

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

将字符串转为数组, 返回字符串的长度

function countSymbols(string){return Array.from(String).length
}

Array.from 办法会将数组的空位,转为 undefined,也就是说,这个办法不会疏忽空位。

Array.from(['a',,'b'])
// ["a", undefined, "b"]

Array.of()

将一组值转换为数组, 次要是补救 Array()的有余

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

Array 办法没有参数、一个参数、三个参数时,返回后果都不一样。
只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。
参数个数只有一个时,实际上是指定数组的长度。

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

let arr = Array.of(1, 2, 3, 4)
console.log(arr) // [1, 2, 3, 4]
console.log(arr.length) // 4

Array.of 能够代替 Array()或者 new Array()

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

Array.of 办法能够用上面的代码模仿实现

function ArrayOf(){return [].slice.call(arguments)
}

数组实例的 copyWithin()

在以后数组中, 将指定地位的成员复制到其余地位(笼罩原有成员), 而后返回以后被扭转的数组

Array.prototype.copyWithin(target,start,end)

target: 从这个地位开始替换数据, 如果数值为负, 示意倒数
start(可选): 从这个地位开始读取数据, 默认为 0, 如果数值为负, 示意从开端开始计算
end(可选): 到该地位前进行读取数据, 默认为数组的长度, 如果数值为负, 示意从开端开始计算

例子:

[1,2,3,4,5].copyWithin(0,3)   //[4,5,3,4,5]
从 0 开始替换, 替换数字是第 3 位到数组完结, 所以是 4,5, 替换掉了 1,2

[1,2,3,4,5].copyWithin(0,3,4)  //[4,2,3,4,5]
从 0 开始替换, 替换数字是第 3 位到第 4 位完结, 所以是 4, 替换掉了 1

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

![Int32Array 和 call 的问题](9- 数组的扩大_files/4.jpg)

find()和 findIndex()

find: 返回找出的第一个符合条件 (返回值为 true) 的数组成员, 如果没有, 返回 undefined
findIndex: 返回第一个符合条件的数组成员的地位, 如果没有, 返回 -1

find(callback(value,index,arr),thisArg)
findIndex(callback(value,index,arr),thisArg)
value: 以后的值
index: 以后的地位
arr: 原数组
thisArg: 绑定回调函数的 this 对象

例子:

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

[1, 5, 10, 15].find(function(value, index, arr) {return value > 9;}) // 10

[1,5,10,15].findIndex(function(value,index,arr){return value >9}) //2

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

另外,这两个办法都能够发现 NaN,补救了数组的 indexOf 办法的有余。

[NaN].indexOf(NaN)
// -1

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

下面代码中,indexOf 办法无奈辨认数组的 NaN 成员,然而 findIndex 办法能够借助 Object.is 办法做到。

Object.is(): 判断 2 个值是否是同一个值, 不进行强制转换, 须要满足以下条件:
* 都是 undefined
* 都是 null
* 都是 true 或 false
* 都是长度雷同的字符串且字符排列程序雷同
* 都是雷同对象(意味着同一个援用)
* 都是数字, 都是 +0, 都是 -0, 都是 NaN, 或都是非零而且非 NaN 且为同一个值

留神:+0 != -0 

es5 替换:
if (!Object.is) {Object.is = function(x, y) {
    // SameValue algorithm
    if (x === y) {return x !== 0 || 1 / x === 1 / y;} else {return x !== x && y !== y;}
  };
}

数组实例的 fill()

应用指定值, 填充 (批改) 一个数组, 其实就是用默认内容初始化数组
fill(value,start,end)

value: 填充值
start: 填充起始地位
end:填充完结地位,能够省略,理论完结地位是 end-1

例子:

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

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

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

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({name: "Mike"},0,1);
arr[0].name = "Ben";
arr  // [{name: "Ben"},empty*2] =====>length:3

let arr = new Array(3).fill([]);
arr[0].push(5);
arr  // [[5], [5], [5]]

数组实例的 entries(),keys(),values()

用于遍历数组, 返回一个遍历器对象, 能够用 for…of 循环进行遍历
enteries(): 键值对的遍历
keys(): 键名的遍历
values(): 键值的遍历

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"

数组实例的 includes()

  • 查看数组中是否蕴含给定的值
includes(value,start)
value: 查找的內容
strat: 搜寻的起始地位, 默认为 0, 如果正数, 从后开始, 如果大于数组长度, 从头开始
  • 例子
[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

相较于 indexOf(), 不须要比拟是否等于 -1,NaN 也能够检测

[NaN].indexOf(NaN)
// -1

[NaN].includes(NaN)
// true

es5:

var contains = (()=>
    Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)()
contains(['foo', 'bar'], 'baz'); // => false

数组实例的 flat(),flatMap()

flat

  • flat(): 将嵌套数组变成一维数组, 取出数组, 放在原来的地位, 返回新数组
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
  • flat(n): 默认拉平一层, 为 1, 如果想要“拉平”多层, 将 flat()的参数写成一个想要拉平层数的整数,

如果不确定层数, 能够用 Infinity

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

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

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
  • 如果原数组有空位,flat()办法会跳过空位。
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]

flatMap()

对 arr 的每一个成员执行函数, 而后对返回值组成的数组执行 flat(), 返回新数组, 只能开展一层
arr.flatMap(function callback(currentValue[, index[, array]]) {
// …
}[, thisArg])
callback: 回调函数: 三个参数:
currentValue: 以后参数
index: 以后参数的地位
array: 原数组
thisArg: 绑定回调函数里的 this

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

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

数组的空位

数组的空位指,数组的某一个地位没有任何值。比方,Array 构造函数返回的数组都是空位。

Array(3) // [, , ,] 返回一个具备 3 个空位的数组

空位不等于 undefined,一个地位的值等于 undefined,仍然是有值的。空位是没有任何值,in 运算符能够阐明这一点

第一个数组的 0 号地位是有值的,第二个数组的 0 号地位没有值:
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

ES5 和 ES6 对于空位的解决

ES5:
forEach(),filter(),reduce(),every(),some(), 会跳过空位
map(), 跳过空位, 但保留值
join(),toString(), 将空位视为 undefined,undefined 和 null 被解决成空字符串
)

ES6: 将空位转为 undefined

Array.from(['a',,'b'])
// ["a", undefined, "b"]

[...['a',,'b']]
// ["a", undefined, "b"]

[,'a','b',,].copyWithin(2,0) 
// [,"a",,"a"]

new Array(3).fill('a') 
// ["a","a","a"]

let arr = [, ,];
for (let i of arr) {console.log(1);
}
// 1
// 1

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0

Array.prototype.sort 的排序稳定性

  • 排序稳固
const arr = [
  'peach',
  'straw',
  'apple',
  'spork'
];

const stableSorting = (s1, s2) => {if (s1[0] < s2[0]) return -1;
  return 1;
};

arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]

下面代码对数组 arr 依照首字母进行排序。
排序后果中,straw 在 spork 的后面,跟原始程序统一,所以排序算法 stableSorting 是稳固排序。

  • 排序不稳固
const unstableSorting = (s1, s2) => {if (s1[0] <= s2[0]) return -1;
  return 1;
};

arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]

下面代码中,排序后果是 spork 在 straw 后面,跟原始程序相同,所以排序算法 unstableSorting 是不稳固的。
常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳固的,堆排序、疾速排序等是不稳固的。
不稳固排序的次要毛病是,多重排序时可能会产生问题。
假如有一个姓和名的列表,要求依照“姓氏为次要关键字,名字为主要关键字”进行排序。
开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳固的,这样就能够达到“先姓氏,后名字”的排序成果。如果是不稳固的,就不行。

ES2019 明确规定,Array.prototype.sort()的默认排序算法必须稳固。
这个规定曾经做到了,当初 JavaScript 各个次要实现的默认排序算法都是稳固的。

退出移动版