JavaScript 数组看这篇就够啦!

143 阅读7分钟

1.创建数组

// 1.通过构造函数
const people = new Array(); // []
const people1 = new Array(10); // [empty × 10]
const people2 = new Array('starry','alex'); //  ["starry", "alex"]

// 2.对象字面量(与对象一样,使用对象字面量创建数组不会调用数组的构造函数)
const people3 = []; // [] 
const people4 = ['starry','alex']; //  ["starry", "alex"]
const people5 = [,,,,];          // [,,,,]  
console.log(people5.length)      // 4 不过这些都是站位,值为undefined,执行的时候会跳过
                      
//3.通过转换
// Array 构造函数中 ES6 新增的方法:from() 和 of()
// from() 是将参数(类数组结构的数据,或有length属性和可索引元素的结构)转换成数组实例。
// of() 是将参数转换成数组实例
// 我的理解,区别就是 from 从本质上将数据变成数组,而of只是在最外层套一层[]?
// 参数是字符串
Array.from('formatt');   // ['f','o','r','m','a','t','t'];
Array.of('formatt');   // ['formatt'];
// set
// Map ==== 后面补

// =============================================================== 下面是详细介绍Array.from

// 如果是现有数组,是深拷贝
const a1 = [1,2,3];
const a2 = Array.from(a1);
a1 === a1 // false  引用地址不同
a1[0] = 9;
console.log(a1); // [9,2,3]
console.log(a2); // [1,2,3]

// from() 的第二个参数,是一个可选的映射函数,示例如下
const b1 = [1,2,3];
const b2 = Array.from(b1,el => el*10 );
console.log(b2)  // [10,20,30];

2.数组 length

数组的 length 不只是只读的,还可以通过修改 length 来增加或者删除数组最后一个元素,示例如下:

const arr = [1,2,3];
arr.length = 2 ;  // 通过修改数组的长度来删除最后一个元素
console.log(arr) // [1,2]

arr[arr.length] = 6;  // 往数组最后添加一个元素
console.log(arr)  // [1,2,6] 

arr[99] = 100; 
console.log(arr)  // [1,2,6,empty*96,100]

3.检测数组

判断一个对象是不是数组,通常的方法是通过 instanceof 判断是不是 Array 构造函数的实例。

// 判断 value 是不是数组类型
const result = value instanceof Array  // true or false

但是这中方法有个缺陷,如果只有一个全局执行上下文,不会有问题,因为只会出现一个版本的 Array 构造函数。但是如果存在多个全局上下文,每个版本的 Array 构造函数不一致,就会出现问题(这种情况我还没遇到过)。ECMAScript 提供了一个不用考虑这个问题的方法,目的就是确定一个值是不是数组的,示例如下:

// 判断 value 是不是数组类型
const result = Array.isArray(value);  // true or false

// ===========================
// Array.isArray() 的实现原理

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

// 而且
Array.isArray(Array.prototype);  // true

4.迭代器方法

Array 的原型中提供了三个检索内容的方法,keys()values()entries()

  • keys() 返回数组索引的迭代器。
const str = "starry"
const arr = Array.from(str);
console.log(arr.keys())     // Object [Array Iterator] {}  返回迭代器
  • values() 返回数组元素的迭代器。
const str = "starry"
const arr = Array.from(str);
console.log(arr.values())     // Object [Array Iterator] {}  返回迭代器
  • entries() 返回的是索引/值对的迭代器。
const str = "starry"
const arr = Array.from(str);
console.log(arr.entries())     // Object [Array Iterator] {}  返回迭代器

通过上面示例我们知道,这三个方法返回的都是迭代器,我们并不能直接看到,使用。

迭代器就是一个一次性的对象,用于迭代与其关联的可迭代对象。

我们可以上面提到的 Array.from() 方法将迭代器转换成数组。

Array.from(arr.keys())  // [0, 1, 2, 3, 4, 5]
Array.from(arr.values())  // ["s", "t", "a", "r", "r", "y"]
Array.from(arr.entries())(6) [[0, "s"],[1, "t"],[2, "a"],(2) [3, "r"],[4, "r"],[5, "y"]]

5.数组方法

改变原数组的方法:pop(),push(),shift(),unshift(),reverse(),splice()

不会改变原数组的方法:slice(),concat(),join(),flat()find()filter

  • reduce 按升序执行每一个元素,将结果累计返回。
reduce((累计值,currentValue,当前索引,源数组),initialValue)
// 注:当前索引表示正在处理的数据索引,如果设置 initialValue ,那么索引从 0 开始,如果没有设置,
//    索引默认从 1 开始
const initialValue = 0;
const numbers = [10,20,40,50,60,70,80]
const total = numbers.reduce((sum,current,index,originArray) => {
  return item + next
},initialValue);

reduce 应用场景:

  1. 将二维数组变成一维
const flattened = [[0, 1], [2, 3], [4, 5]]
const out = flattened.reduce((item,current) => item.concat(current),[]);
// [0, 1, 2, 3, 4, 5] 'out'
  1. 统计数组中元素出现的次数
const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
const out = names.reduce((allNames,current) => {
  if(current in allNames){
    allNames[current] ++ ;
  }else {
    allNames[current]  = 1
  }
  return allNames;
},{});

// 注意初始值设置成 {}
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
  1. 按顺序执行 promise
const p1 = num => new Promise((resolve, reject) => {
  resolve(num * 10)
})

const p2 = num => new Promise((resolve, reject) => {
  resolve(num * 20)
})

const p3 = num => new Promise((resolve, reject) => {
  resolve(num * 30)
})

const p4 = num => new Promise((resolve, reject) => {
  resolve(num * 40)
})

const promiseArr = [p1,p2,p3,p4];

// 此时第一参数表示的就是上一个 promise 了 
const out = promiseArr.reduce((beforePromise,current) =>  {
  console.log(beforePromise,'beforePromise');
  return beforePromise.then(current);
},Promise.resolve(10));

out.then(console.log)
  • splice 删除,替换或者添加元素,返回的是被修改的内容,此方法会改变原数组。

splice 使用场景:

// 函数签名
splice(start,deleteCount,要替换的内容)
  • slice 截取字符串,返回一个新的数组,不会改变原数组。

slice 使用场景:

// 函数签名
slice(start,end)   // 包含 start 不包含 end

// 另外一个用法,用来把类数组转换成数组,作用同 Array.from()
// arguments 为例
const arr = Array.prototype.slice.call(arguments)

find 使用场景:

返回数组中满足测试函数的第一个元素的值,否则返回 undefined

所以可以用来查找

arr.find(函数) 

filter 使用场景:

filter() 方法创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。

const files = [ 'foo.txt ', '.bar', '   ', 'baz.foo' ];
const filePaths = files
  .map(file => file.trim())
  .filter(Boolean)
  .map(fileName => `~/cool_app/${fileName}`);

// filePaths = [ '~/cool_app/foo.txt', '~/cool_app/.bar', '~/cool_app/baz.foo']

// filter(Boolean)
//  等价于 filter((x)=>Boolean(x))

flat 使用场景:

用来扁平化数组,返回一个新的数组。

flat()
// 提取嵌套数组的深度,默认是 1 ,
flat(depth)
// 可以提取嵌套任意深度的数组
flat(Infinity)

6.数组方法分类

1.会改变原数组的方法

push()
pop()
unshift() //往数组前面添加数据
shift() //往数组前面弹出数据
reverse()
sort()
splice()

2.不会改变原数组方法

concat()
slice()
reduce()
join()
map()
filter

ES6 结构赋值
const colors = ['green', 'red', 'pink']
const newColors = [...colors]
colors[0] = 'black'
// colors:['black', 'red', 'pink']
// newColors:['green', 'red', 'pink']

7.Array.of() 和 Array.from()

Array 构造函数中 ES6 新增的方法:from() 和 of()

from() 是将参数(类数组结构的数据,或有length属性和可索引元素的结构)转换成数组实例。前提是类数组

of() 是将参数转换成数组实例,解决 new Array() 行为不统一的问题

// 解决 new Array() 行为不统一的问题
new Array(2)  //   [empty × 2]
new Array('abc') // ['abc']
Array.of(2)   // [2]
Array.of('abc') // ['abc']

8.数组的存储方式

从 Chorme 的源码来看,js 中的数组 JSArray 是继承自 JSObject 的,所以可以把数组看作是特殊的对象,内部也是以 key-value 的方式进行存储的,所以可以存储不同类型的数据。因此,数组的存储方式分为两种:快数组和慢数组

快数组(fast):存储结构是 FixedArray数组长度<= elements.length,push 或者 pop可能会引起扩容和减容。

慢数组(slow):存储结构是 HashTable,数组下标作为 key。

1.快数组

FixedArray 是 V8 中实现的一个类似数组的类,表示一段连续的内存,可以使用索引直接定位。新创建的空数组默认就是快数组。当执行 push 操作时,如果数组满的时候会 JSArray 会进行动态扩容。是以时间换空间。

动态扩容

Chorme 源码中 push 的操作是使用 c++ 内嵌汇编实现的。实现过程如下:

  1. 执行 push 时,发现内存不足
  2. 申请内存,申请内存大小的计算规则 new_capacity = old_capacity /2 + old_capacity + 16(原内存大小的1.5倍+16) .
  3. 将数组复制到新的内存中。
  4. 将新元素放到 length + 1的位置
  5. length + 1
  6. 返回length

动态减容

c++实现,步骤如下:

  1. 执行 pop,获取数组长度,获取 length+1 的元素,length-1
  2. 数组容量是否大于等于 length - 1 的2倍
  3. 是的话,计算释放空间的大小,做好标记等待 gc 回收
  4. 不是的话用 holes 对象填充
  5. 返回被删除的元素

2.慢数组

慢数组是以哈希表的形式保存在内存中,它不需要连续的存储空间,节省了内存,但是需要维护一个哈希表。与快数组相比,性能较差。是以时间换空间。