JavaScript系列 — Array 详解

前言

在前端开发中,跟数组日常打交道,所以研究 Array 和 Array.prototype 的属性和方法对于做前端开发的我们非常有必要:

我们可以借助Object.getOwnPropertyNames()方法来看看ArrayArray.prototype都有哪些属性和方法:

image.png

其中是 ES5 新增的有:

  • reduce、filter、sort、forEach、map、some、every、indexOf

其中是 ES6 新增的有:

  • Array.from()、Array.of()
  • find、findIndex、fill、includes、entries、keys、values、flat、flatMap

讲解下面的这些方法的时候我使用 4 个模块去讲解:

  • 基本使用(基础操作)
  • 注意事项(bug 预防)
  • API 实现(考虑性能)
  • 应用拓展(API 拓展)

在此之前我们先了解一下创建数组的方式和其实例属性:

创建数组的方法

创建数组据我了解有下面四种方式:

1. 直接使用字面量[]创建
let arr = [0, 1, 2];

2. 使用 Array 构造函数创建
let arr = new Array(0, 1, 2);

3. 调用 Array 内置方法创建
let arr = Array(0, 1, 2);

4. 调用 Array of 内置方法创建
let arr = Array of(0, 1, 2);
复制代码

第 3 种方式创建与其它方式的区别在于:

let arr = [7]     // [7]
Array.of(7);       // [7]

Array(7);          // [ , , , , , , ]
复制代码

实例属性

constructor

指向构造出该数组的构造函数

length

length 最大的妙用,就是直接改变 length 属性可以直接删除元素增加元素

let arr = [0, 1, 2];
arr.length = 2; // [0, 1]
arr.length = 5; // [0, 1, empty × 3]
复制代码

直接清空数组,还可实现复用不会产生内存垃圾

let arr = [0, 1, 2];
arr.length = 0; // []
复制代码

值得注意的是:在 ECMAScript6 的文档中,明确规定了 empty 就是等于 undefined ,在任何情况下都应该这样对待 empty

Array 的方法

Array.from( obj ):转化为数组

基本使用

将对象(广义上的)转化为数组(包括:字符串数组SetMap、函数里的Arguments、Generator 函数的Generator对象)

Array.from("foo") // ["f","o","o"]
Array.from([1,2,3]) // [1,2,3]  注意:第一层是深拷贝,第二层及以上是浅拷贝(即数组元素为数组/对象)
复制代码
const set = new Set(['foo', 'bar', 'baz']);
Array.from(set); // ["foo", "bar", "baz"]
复制代码
const map = new Map([['1', 'a'], ['2', 'b']]);
Array.from(map); // [['1', 'a'], ['2', 'b']]
Array.from(map.keys()); // ['1' , '2']
Array.from(map.values()); // ['a', 'b'];
复制代码
function f() {
  console.log(arguments); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  console.log(Array.from(arguments)); // [1,2,3]
}
f(1, 2, 3);
复制代码
function* fn(){
    yield 1
    yield 2
    yield 3
}
var f = fn()
console.log(f) // fn {<suspended>}
Array.from(f) // [1,2,3]
复制代码

应用拓展

将数组转换为 Set 再转回数组可以起到快速 去重 的效果

const set = new Set(['foo', 'bar', 'bar', 'foo']);
Array.from(set); // ["foo", "bar"]
复制代码

Array.of( obj ):创建数组实例

基本使用

功能跟 Array 差不多:

Array(1, 2, 3);    // [1, 2, 3]
Array.of(1, 2, 3); // [1, 2, 3]
复制代码

注意事项

跟 Array 的区别在于,传入参数为数字时:

Array(7);          // [ , , , , , , ],length 为 7 的一个空位数组
Array.of(7);       // [7] // 值为 7 的单元素数组
复制代码

Array.isArray( obj ):判断是否为数组

基本使用

功能:作用:用于判断某个变量是否是数组对象,是则返回 true,否则返回 false

Array.prototype 的方法

arr.push(v)、arr.pop()

基本使用

var arr = [1,2,3]
arr.push(5) // 4
console.log(arr) // [1,2,3,5]
arr.pop() // 3
console.log(arr) // [1,2,3]
复制代码

注意事项

  • 会改变原数组
  • 返回值是改变后新数组的长度

应用拓展

验证括号是否有效匹配

arr.unshift(v)、arr.shift()

基本使用

var arr = [1,2,3]
arr.unshift(5) // 4
console.log(arr) // [5,1,2,3]
arr.shift() // 3
console.log(arr) // [1,2,3]
复制代码

注意事项

  • 会改变原数组
  • 返回值是改变后新数组的长度

应用拓展

结合 arr.push(v)、arr.pop() 可以把数组作为队列处理,可用于如 树的层序遍历

function fn(root){
    var arr = [] // 二元数组放置每一层遍历结果
    if(root == null) return arr
    var queue = [] // 维护一个队列
    queue.push(root)
    while(queue.length != 0){ // 队空表示当前队内的元素都是叶子节点上的元素,退出循环
        arr.psuh([])
        for(let i = 0;i < queue.length;i++){ // 当前队内元素按序出队
            var node = queue.shift()
            arr[arr.length - 1].push(node.val)
            if(node.left != null) queue.push(node.left) // 元素出队的同时将其子节点元素入队
            if(node.right != null) queue.push(node.right)
        }
    }
}
复制代码

arr.includes(v , [fromIndex])

基本使用

  • 参数 v:表示查找的目标元素;
  • 参数 fromIndex:可选,表示从从fromIndex 索引处开始查找;若该值为 负数,则按升序从 array.length + fromIndex 的索引开始搜(表示从倒数第 n 个开始搜的意思)

功能:判断数组中是否含有该元素,有就返回 true,否则返回 false

const arr = [1, 2, 3];
arr.includes(1); // true
arr.includes(22); // false
复制代码

注意事项

不能直接检索出引用类型,解决方案是利用其引用

const arr1 = [[1], 2, 3];
arr1.includes([1]); // false

const t = [1];
const arr2 = [t, 2, 3];
arr2.includes(t); // true
复制代码

原理实现

单纯 for 循环遍历,找到则退出循环,并返回 true,在循环结束后返回 false,时间复杂度为 O(n)

arr.indexOf(v , [fromIndex])、arr.lastIndexOf(v , [fromIndex])

基本使用

  • 参数 v:表示查找的目标元素;
  • 参数 fromIndex:可选,表示从从fromIndex 索引处开始查找;若该值为 负数,则按升序从 array.length + fromIndex 的索引开始搜(表示从倒数第 n 个开始搜的意思)

功能:

  • arr.indexOf(v , [fromIndex]):查找目标元素在数组中第一次出现的位置,返回索引,若查找不到则返回 -1
  • arr.indexOf(v , [fromIndex]):查找目标元素在数组中最后一次出现的位置,返回索引,若查找不到则返回 -1
const arr = [1, 2, 3, 6, 3, 2, 1];
arr.indexOf(1); // 0
arr.indexOf(22); // -1
arr.lastIndexOf(2); // 5
arr.lastIndexOf(10); // -1
复制代码

注意事项

不能直接检索出引用类型,解决方案是利用其引用

const arr1 = [[1], 2, 3];
arr1.indexOf([1]); // -1
arr1.lastIndexOf([1]); // -1

const t = [1];
const arr2 = [t, 2, 3, t, 1];
arr2.indexOf(t); // 0
arr2.lastIndexOf(t); // 3
复制代码

原理实现

单纯 for 循环遍历,找到则退出循环,并返回 i 值,在循环结束后返回 -1,时间复杂度为 O(n)

  • arr.indexOf(v):for 循环里的 i 是从 0 到 n 的
  • arr.lastIndexOf(v):for 循环里的 i 是从 n 到 0 的

arr = arr.concat(arr / v1,v2,…)

基本使用

  • 参数 arr:可以直接填一个数组,前面的数组会接上括号里面的数组
  • 参数 v1,v2,v3,…:也可以填多个值,接入数组时会一个一个按序接入,也可以不填

注意事项

不会改变原数组,如果需要改变原数组,记得赋值

var arr1 = [1,2,3]
var arr = [4]
arr.concat(arr1) // [4,1,2,3]
console.log(arr) // [4]
arr = arr.concat(arr1) // [4,1,2,3]
复制代码

arr.reverse()

基本使用

将数组倒序,会改变原数组,不用填参数,会改变原数组

var arr = [1,2,3]
arr.reverse() // [3,2,1]
复制代码

API 实现

利用 for 循环遍历数组,借助中间变量完成交换

var MyReverse = function(arr){
    var t = 0
    var len = arr.length
    for(let i = 0;i < Math.floor(len / 2); i++){
        t = arr[i]
        arr[i] = arr[len - 1 - i]
        arr[len - 1 - i] = t
    }
    return arr
}
var arr = [1,2,3,4]
MyReverse(arr)
复制代码

arr = str.split(“分割字符串”)

基本使用

功能:将一个 str 以指定的 分割字符串 为界限分割成多个子字符串,并返回这些子字符串组成的数组;

  • 如果原字符串中不包含分割字符串,则返回原数组
  • 如果分割字符串为空字符串,则将 str 原字符串中每个字符的数组形式返回;
  • 如果没有传入参数,则返回原字符串的数组形式

image.png

注意事项

分割界限可以是字符串,也可以是单个字符,也可以是空字符串,也可以为空,但都不改变原字符串

API 实现

【未完成】
var MySplit = function(string,str){
    var arr = []
    if(string === "" || str === null) arr.push(string) // 无传入参数,返回原字符串
    if(str === ""){
        for(let i in string){
            arr.push(string[i])
        }
    }
    else{
        for(let i in string){
            var s = ""
            if(string[i] === str) arr.push(s)
            else s += string[i]
        }
    }
    return arr
}
MySplit("hello,world!",",")
MySplit("hello,world!","")
MySplit("hello,world!","6")
MySplit("","6")
复制代码

测试:

str = arr.join(“连接字符串”)

基本使用

功能:将一个数组的每一个元素以指定的 连接字符串 为“桥梁”合并成一个字符串,并返回合并后的字符串

  • 如果传入参数为空字符串 ""的话,则把各个元素转化为字符串类型后直接拼接起来
  • 如果不传入参数的话,则默认以逗号 , 作为连接字符串
  • 如果传入参数不是字符串类型,则会将其转化为字符串类型后再拼接

注意事项

其原理是把各个元素先调用toString()后再拼接起来的,所以需要注意一下(当然有些数据类型会覆盖掉这些方法)

let arr = ["h", 9, true, null, undefined, [], {}];
arr.join("|"); // "h|9|true||||[object Object]"
复制代码

其中 nullundefined[]对应都转化为 ""{}对应转化为[object Object]

arr = arr.slice(start,[end])

基本使用

功能:按照数组索引从startend - 1截取数组,并返回截取后的数组,不改变原数组

  • 参数 start:可选,不填的话默认值为 0,若为负数表示倒数第 |start| 个
  • 参数 end:可选,不填的话默认值为 arr.length,若为负数表示倒数第 |end| 个
var arr = [1,2,3,4,5]
arr.slice() // [1,2,3,4,5]
arr.slice(1,3) // [2,3,4]
arr.slice(1,10) // [2,3,4,5]
复制代码

arr = arr.splice(start,[length],[item1,item2,…])

基本使用

功能:按照数组索引从start截取长度为length的数组,并在尾部添加元素[item1,item2,...],并返回截取后的数组,会改变原数组

  • 参数 length:不填的话,该方法默认截取到数组索引从start到字符串结束

由于 splice 方法的参数的可控性,所以 splice 方法其实有 4 种功能:

  1. 截取功能:
var arr = [0,1,2,3,4,5];
arr.splice(4); // [4,5]
arr.splice(1,3); // [1,2,3]
复制代码
  1. 从数组某个索引处插入一个或多个元素的功能:

第二个参数设置为 0,第一个参数为插入的第一个元素插入后所在的索引

var arr = [0, 1, 2, 3];
arr.splice(1, 0, "h"); // 从下标为1的地方插入元素 'h'
console.log(arr); // [0, "h", 1, 2, 3]
复制代码
  1. 截取 + 插入功能:
var arr = [0, 1, 2, 3];
arr.splice(1, 2, "h"); // 截取 [1,2] 后从下标为1的地方插入元素 'h'
console.log(arr); // [0, "h", 3]
复制代码

arr.sort( [compareFunction] )

注意事项

如果没有指明 compareFunction,则 sort 方法会按照各元素转换为的字符串的诸个字符的 Unicode 位点进行排序

  • 例如 “Banana” 会被排列到 “cherry” 之前
  • 例如 80 要比 9 要靠前,因为没有指明compareFunction的情况下,元素会被转为字符串,所以 “80” 要比 “9” 考前

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,则 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 ,则 a 和 b 的相对位置不变;
  • 如果 compareFunction(a, b) 大于 0 ,则 b 会被排列到 a 之前;
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的

所以,比较函数格式如下:

function compare(a, b) {
    if (a < b ) {           // 按某种排序标准进行比较, a 小于 b
        return -1;
    }
    if (a > b ) {
        return 1;
    }
    // a must be equal to b
    return 0;
}
复制代码

要比较数字而非字符串,比较函数可以简单的以 a 减 b,如下的函数将会将数组升序排列

function compareNumbers(a, b) {
    return a - b;
}
复制代码

原理

传入的参数 compareFunction 会被作为 sort 方法里面调用的一个子函数,无论是插入排序还是快速排序还是其他的排序方式,都逃不过对两个元素的大小进行比较,所以在 sort 方法里面在(比较两个元素大小关系)这一小步时会调用我们传入的自定义的子函数,然后拿取返回的值

返回的值 sort 里面默认需要收到三种结果(大于0的数、等于0的数、小于0的数),然后对这三种结果做出相应处理(要不要交换位置)

基本使用

功能:对数组进行排序

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers);

也可以写成:
var numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers);

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

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 6},
  { name: 'Zeros', value: 37 }
];

// sort by value
var result = items.sort(function (a, b) {
  return (a.value - b.value)
});
result === [
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 6},
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'Zeros', value: 37 },
  { name: 'And', value: 45 }
];
复制代码

排序原理

  • 数组长度不超过 10,为插入排序
  • 数组长度超过 10,为快速排序

API 实现

具体实现参考 数据结构及算法系列 — 排序算法

arr.forEach((item,index,arr)=>{})

基本使用

功能:相当于一个遍历器

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 forEach 方法的数组本身
const arr = [0, 1, 2, 3, 4, 5];
arr.forEach((item, index, arr) => {
    if (item % 2 === 0) {
        arr.push(1);
    }
})
console.log(arr); // [0, 1, 2, 3, 4, 5, 1, 1, 1]
复制代码

arr.every((item,index,arr)=>{return …}) 是否全部通过

基本使用

功能:相当于一个检测器,在 return 后面写明测试通过规则(一个函数或表达式),然后测试数组内的所有元素是否都能通过该规则。都能通过才返回 true,要是有一个元素不通过测试,则退出遍历并返回 false

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 every 方法的数组本身
const arr = [0, 1, 2, 30, 4, 5];
const result = arr.every((item, index)=> {
    console.log(item) // 0 1 2 30
    return item < 10;
});
console.log(result); // false
复制代码

arr.some((item,index,arr)=>{return …}) 是否至少一个通过

基本使用

功能:相当于一个检测器,在 return 后面写明测试通过规则(一个函数或表达式),然后遍历数组元素,如果有一个通过测试,则退出遍历并返回 true,全部元素均不通过测试则返回 false

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 some 方法的数组本身
const arr = [0, 1, 2, 30, 4, 5];
const result = arr.some((item, index)=> {
    console.log(item); // 0 1 2 30
    return item > 10;
});
console.log(result); // true
复制代码

arr.find((item,index,arr)=>{return …}) 第一个通过测试的元素的值

基本使用

功能:在 return 后面写明筛选通过规则(一个函数或表达式),然后遍历数组所以元素,通过规则即返回该元素

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 some 方法的数组本身
const arr = [5, 4, 3, 6, 7, 8];
const result = arr.find((item, index)=> {
    return item > 5 ;
});
console.log(result); // 6
复制代码

arr.findIndex((item,index,arr)=>{return …}) 第一个通过测试的元素的索引

基本使用

功能:在 return 后面写明筛选通过规则(一个函数或表达式),然后遍历数组所以元素,通过规则即返回该元素的索引

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 some 方法的数组本身
const arr = [5, 4, 3, 6, 7, 8];
const result = arr.findIndex((item, index)=> {
    return item > 5 ;
});
console.log(result); // 3
复制代码

arr.filter((item,index,arr)=>{return …}) 筛选器

基本使用

功能:相当于一个筛选器,在 return 后面写明筛选通过规则(一个函数或表达式),然后遍历数组所有元素,通过规则即把该元素放入一个数组中,最后返回该数组

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 some 方法的数组本身
const arr = [0, 1, 2, 3, 4, 5];
const result = arr.filter((item, index)=> {
    return item % 2 === 0;
});
console.log(result); // [0, 2, 4]
复制代码

API 实现

const arr = [0, 1, 2, 3, 4, 5];
const MyFilter = function(arr){
    const result = []
    for(let item of arr){
        if(item % 2 === 0) result.push(item)
    }
    return result
}
MyFilter(arr)
复制代码

arr.map((item,index,arr)=>{return …}) 平行操作器

基本使用

功能:相当于一个平行操作器,在 return 后面写明平行操作规则(一个函数或表达式),然后遍历数组所有元素,对每个元素进行规定的操作后得到一个新元素,将其放入一个数组中,最后返回该数组

  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 some 方法的数组本身
const arr = [0, 1, 2, 3, 4, 5];
const result = arr.map((item, index)=> {
    return item * 2;
});
console.log(result); // [0, 2, 4, 6, 8, 10]
复制代码

API 实现

const arr = [0, 1, 2, 3, 4, 5];
const MyMap = function(arr){
    const result = []
    for(let item of arr){
        result.push(item * 2)
    }
    return result
}
MyMap(arr)
复制代码

arr.reduce((accum,item,index,arr)=>{return …})

基本使用

功能:相当于一个累积器

  • 参数 accumulation:当前累积成果(还未执行这一步操作)
  • 参数 item:当前项
  • 参数 index:当前项的索引
  • 参数 arr:调用 reduce 方法的数组本身
var arr = [1, 2, 3, 4, 5, 6];
var result = arr.reduce((accum, item)=> {
    return accum + item;
});
console.log(result); // 21
复制代码

拓展:计算机底层原理

数组和对象一样,属于引用类型,在内存中放置的区域是堆内存,所需要的空间由 length 指定,而其引用则是放在栈内存区域,栈内存区域放置的是固定不变的值,而对数组的引用从本质上来讲就是它的内存地址,就是直达堆内存中对应的数组的第一个元素的内存地址(这只是我的理解,如果有错误可以在评论中指出,我可以修改),所以是个固定值。

数组访问某一项、数组遍历的时间复杂度怎么解释?

我的理解是这样的:数组访问某一项时(比如:arr[5])和 遍历数组 时,计算机工作的原理流程就是:

  1. 从栈内存中拿取数组 arr 的内存地址
  2. 直达堆内存中该数组第一个元素 arr[0] 的内存地址
  3. 由于数组元素挨个并排,所以元素 arr[5]arr[0] 之间的内存地址存在一个固定的差值,经过计算后也可直达,所以时间复杂度为 O(1)
  4. 如果是遍历数组,则每次只跨两两元素之间的内存地址差,所以时间复杂度为 O(n)

数组本质上也是个对象怎么理解?

这是一个数组

["dog", "pig", "cat"];
复制代码

它可以看作成这样一个对象:

{
    "0": "dog",
    "1": "pig",
    "2": "cat",
    "length": 3
}
复制代码
  • 一个数组元素对应一个对象字段
  • 数组下标(索引)就是对应字段的属性名
  • 数组元素的值就是对应字段的属性值
  • 多了一个属性 length,其值自然就是数组的长度

参考文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享