数组对象,区别于其他语言,比如JAVA,JS其实没有真正的数组,只是用对象模拟数组,即内置对象(built-in Object)

JS的数组是非经典数组

典型的数组

  • 元素的数据类型相同
  • 使用连续的内存存储
  • 通过数字下标获取元素

JS的“数组”不同之处

  • 元素的数据类型可以不同
  • 内存不一定是连续的
  • 不能通过数字下标,而是通过字符串下标
  • 可以有任何key

画个内存图

1
2
let arr = [1,{},3]
arr['xxx'] = 1

array.jpg


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let arr = [1,2,3]
arr['xxx'] = 1 //加个'xxx'属性
arr['yyy'] = 'yyy'
arr.length
< 3
arr['4'] = 4
< 4
arr.length
< 5
arr // [1, 2, 3, empty, 4, xxx: 1, yyy: "yyy"]
arr['99'] = 'zzz'
"zzz"
arr // [1, 2, 3, empty, 4, empty × 94, "zzz", xxx: 1, yyy: "yyy"]

每个数组一创建就有length属性,根据下标自动更新

Array MDN

创建一个数组

新建

1
2
3
4
5
6
let arr1 = [1,2,3]
let arr2 = new Array(1,2,3) //元素
let arr3 = new Array(3) //仅传一个数字类型,表示数组长度 ,其元素为 empty x length 个,每个值为undefined
let arr4 = new Array("1")
let arr5 = new Array(true)
let arr6 = new Array(1,2,('length':5))

语法

1
2
3
[element0, element1, ..., elementN]
new Array(element0, element1[, ...[, elementN]])
new Array(arrayLength)

elementN

Array 构造器会根据给定的元素创建一个 JavaScript 数组,但是当仅有一个参数且为数字时除外(详见下面的 arrayLength 参数)。注意,后面这种情况仅适用于用 Array 构造器创建数组,而不适用于用方括号创建的数组字面量。

arrayLength

一个范围在 0 到 2^32-1 之间的整数,此时将返回一个 length 的值等于 arrayLength 的数组对象(言外之意就是该数组此时并没有包含任何实际的元素,不能理所当然地认为它包含 arrayLength 个值为 undefined 的元素)。如果传入的参数不是有效值,则会抛出 RangeError 异常。

下标,“键值”

1
2
3
let arr = [1,2,3]
arr[0]
arr[arr.length - 1]

转化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let str = '1,2,3'
let arr1 = str.split(',') //用','来分隔,并返回一个数组["1","2","3"]
let arr2 = '123'.split('') //用空字符串''来分隔,并返回["1","2","3"]
Array.from('123') //ES6,返回["1","2","3"]
Array.from(123) //[]
Array.from(true) //[]
Array.from({name:'frank'}) //[]
Array.from({0:'a',1:'b',2:'c',3:'d'})  //[]
Array.from({0:'a',1:'b',2:'c',3:'d',length:4})  //["a","b","c","d"] 有下标 有length属性,即可变成数组
Array.from({0:'a',1:'b',2:'c',3:'d',length:2})  //["a","b"]

将下标隐式转换为字符串

1
2
3
let arr = [1,2]
arr[1..toString()] //2
arr[(1).toString()] //2

伪数组(类数组对象)

1
2
let arr = Array.from({0:'a',1:'b',2:'c',3:'d',length:4})
arr.__proto__ === Array.prototype
1
2
3
<div></div>
<div></div>
<div></div>
1
2
3
4
5
let divList = document.querySelectorAll('div')
divList
divList.push(4) //Uncaught Type Error...
let divArray = Array.from(divList)
console.dir(divArray)

伪数组有length属性

伪数组的原型链中并没有数组的原型,而是直接指向Object的原型

没有数组共有属性的「数组」,就是伪数组

拿到伪数组第一个要做的就是用Array.from()变为真·数组

  • 可用Array.isArray(arr)判断(ES5)
1
2
3
4
5
6
7
8
9
function isArr(){
    console.log(arguments.length)
    console.log(arguments[0])
    console.log([...arguments].reduce((v1,v2)=> v1+v2))
    console.log(Array.from(arguments).reduce((v1,v2)=> v1+v2))
    return Array.isArray(arguments)
}

sum(1,2,3)

创建一个数组(续)

合并两个数组

1
2
3
let arr1 = [1,1,1]
let arr2 = [2,2,2]
let arr3 = arr1.concat(arr2)

不改变原数组,得到(返回)新数组

截取

一个数组的一部分,或全部截取

1
2
let arr4 = arr1.slice(1) //从第二个元素开始截取
let arr5 = arr1.slice(0) //全部截取,即为复制

JS原生(til 2020)只提供浅拷贝


增删改查数组中的元素

删元素

和对象一样,可用delete操作符

1
2
3
4
5
6
let arr = ['a','b','c']
delete arr['0'] //true
arr //[empty,'b','c'] 数组长度length 并没有变
delete arr['1']
delete arr['2']
arr //[empty x 3] length 并没有变

有长度属性,但没有对应下标的数组,叫***「稀疏数组」***,只会带来bug

数组长度并未改变

delete方法是适用对象的,并不适合用在数组上

直接改length

1
2
3
4
5
6
let arr = ['a','b','c']
arr.length = 1
arr //['a']

// 清空数组
arr.length = 0

不随便改length

不规范的删法

  • 删除数组元素不要用delete和改变length的错误方法
需要改length的场景

删元素(续)

删除头部元素

1
arr.shift() //删掉开始的元素,原数组 arr 被修改,并返回被删掉的元素

删除尾部元素

1
arr.pop() //删掉末尾的元素,原数组 arr 被修改,并返回被删掉的元素

删除中间元素

1
2
3
4
5
6
// index 从 0 开始
arr.splice(index,1) //删除从index开始的一个元素,第二个参数的数字表示要删除元素的个数,返回被删除的元素所组成的数组

/* 边删边加 */
arr.splice(index,1,'x') //并在删除位置添加'x'
arr.splice(index,1,'x','y') //并在删除位置添加'x','y'

加参数,就变改掉数组元素

1
2
3
4
5
6
7
let arr6 = [1,2,3,4,5,6,7,8]
arr6.splice(2,3) // [3,4,5],返回被删除的元素组成的数组,原数组改变
arr6 //[1,2,6,7,8]

let arr7 = [1,2,3,4,5,6,7,8]
arr.splice(5,1,666,777,888) //返回 [6]
arr7 // [1,2,3,4,5,666,777,888,7,8]

MDN: Array.prototype.splice()

1
// mdn

查元素

查看所有元素

查看所有属性名

Object.keys(arr)

1
2
3
4
let arr = [1,2,3,4,5]
arr.x = 'xxx'
Object.keys(arr) // ["1","2","3","4","x"]
Object.values(arr) // [1,2,3,4,5,"xxx"]

用另一种查看普通对象的遍历方法,试试

1
2
3
4
5
6
let arr = [1,2,3,4,5]
arr.x = 'xxx'
Object.keys(arr)
for (let key in arr){
    console.log(`${key}:${arr[key]}`)
}

访问数组的时候并不希望访问到下标属性之外的

对象的遍历方法用在数组上不靠谱,只适合纯对象,不适合数组

所以要用其他的方法获取数组所有keysvalues


查看数组的正确姿势

for let i 循环查看数组属性名和值

区别于for i in arr,遍历所有属性,指定具体要遍历的下标

1
2
3
4
5
let arr = [1,2,3,4,5]
arr.x = 'xxx'
for(let i = 0; i < arr.length; i++){ // for循环的标准写法
    console.log(`${i}:${arr[i]}`) //ES6字符串模板
}
  • 所以访问数组,不要用for inObject.keys()
  • 都是访问对象的,有可能获取到非预期的东西
  • 访问数组的标准做法就是for(let i = 0; i < arr.length; i++){}
  • 要让i0增长到length - 1

数组提供的另一种方法,在数组的原型中可以看到

arr.forEach()查看数组属性名和值
1
2
3
arr.forEach(function(item,index){
    console.log(`${index}: ${item}`)
}) //返回值是空 undefined

继续打印[1,2,3,4,5]和对应下标

1
2
3
4
5
let arr = [1,2,3,4,5]
arr.x = 'xxx'
arr.forEach(function (xxx,yyy){
    console.log(`${yyy}: ${xxx}`)
})
  • forEach用到了回调,怎么理解,自己手写一个

第一个版本,把每一个数组下标元素传到一个函数里

1
2
3
4
5
6
7
8
function forEach2(array,fn){
    for(let i = 0; i < array.length; i++){
        fn(array[i]) //把获取的每一项作为形参传给fn
    }
}
forEach2(['a','b','c'],function(){})
forEach2(['a','b','c'],function(){console.log('执行了一次')})
forEach2(['a','b','c'],function(x){console.log(x)}) //forEach的基本原理,只是参数颠倒了,下节介绍_坑
  • 两种写法几乎等价:数组、forEach、函数三者都有

第二个版本,把每一个数组下标和下标元素传都到一个函数里

1
2
3
4
5
6
function forEach2(array,fn){
    for(let i = 0; i < array.length; i++){
        fn(array[i], i)
    }
}
forEach2(['a','b','c'],function(x,y){console.log(x,y)})

第三个版本,把数组本身也到函数里

1
2
3
4
5
6
function forEach2(array,fn){
    for(let i = 0; i < array.length; i++){
        fn(array[i], i, array)
    }
}
forEach2(['a','b','c'],function(x,y,z){console.log(x,y,z)})

forEach的原理就是遍历数组,每一次都调用一个函数

  • 基本用到前两个参数就够了,参数名无所谓,顺序对就可以了
  • 第三个参数在特殊情况下会用到,下节介绍_坑

问for循环遍历下标的方法和forEach的区别是什么(有什么第一种可做到,而第二种做不到的一点)

几乎通用,,但并不是语法糖的关系,因为以下

区别一:for循环里支持breakcontinue,而forEach只是一个函数,不支持

区别二:for循环的for 是一个关键字,没有函数作用域,只有块级作用域,而forEach是个函数,有函数作用域

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let arr = [1,2,3,4,5,6,7,8]
for(let i = 0; i < arr.length; i++){
    console.log(`${i}: ${arr[i]}`)
    if(i===3){ //循环到`4`,就停止循环
        break;
    }
}
arr.forEach((x,y)=> {console.log(x)})
arr.forEach((x,y)=> {
    console.log(x)
    if(y===3){return false} // 没用,仍打印全部
})

用for循环遍历,可以随时breakcontinue

forEach按字面意思就是从开头到结尾每个都遍历到

也可以用forEach/map等原型上的函数

forEach是个坎

自己写一遍forEach才能理解

1
2
3
4
5
6
7
8
function myForEach(array,fn){
    for(let i = 0; i < array.length; i++){
        fn(array[i],i,array)
    }
}
// 遍历数组 ,参数为一个匿名的回调函数
// 回调函数里的参数为:遍历到的元素、元素下标、数组本身
myForEach([1,2],(val,index,array)=>{console.log([val,index,array])})
  • forEachfor访问array的每一项
  • 对每一项调用fn(array[i],i,array)
  • 为什么要传入array,龟腚
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let arr = [1,2,3]
arr.forEach(function(val){
    console.log(val)
    console.log(arguments)
})
arr.forEach(function(val,index){
    console.log(`val ${arr[index]}`)
    console.log(`val ${val}`)
})
arr.forEach(function( (val) =>{
    console.log(val)
})

arr.every()arr.some()按条件判断

  • arr.every()仅当所有回调函数都返回true的时候,才返回true,与筛选
  • arr.some()有任意一个回调函数返回true的时候,就返回true,或筛选
  • 不改变原数组
1
2
3
4
5
let arr = [1,2,3]
let isPositive = arr.every(val => val > 0)
console.log(isPositive)
let maybePositive = arr.some(val => val > 0)
console.log(maybePositive)

查看单个元素

和对象一样
1
2
let arr = [111,222,333]
arr[0] //0是字符串
索引越界
1
2
arr[arr.length] === undefined
arr[-1] === undefined

索引不存在

1
2
3
4
let arr = [1,2,3,4,5,6,7,8]
for(let i = 0; i <= arr.length; i++){ //i 取不到 arr.length
    console.log(arr[i].toString()) //i =8; undefined.toString() 会报错  undefined不是对象 没有toString()方法
}
举例
1
2
3
4
let arr = [1,2,3,4,5,6,7,8]
for(let i = 0; i <= arr.length; i++){
    console.log(arr[i].toString())
}

报一个错:Cannot read property 'toString' of undefined意思是不可读取undefinedtoString属性,不是toStringundefined

x.toString(),其中x如果是undefined,就报这个错

console.log()大法解决

1
2
3
4
5
let arr = [1,2,3,4,5,6,7,8]
for(let i = 0; i <= arr.length; i++){
    console.log(`arr[i]: ` + arr[i]) //把变量打出来
    console.log(arr[i].toString())
}

注意数组索引不要越界


查看单个属性(续)

arr.indexOf(item)

查找某个元素是否存在于数组里,并返回该元素的下标

1
2
arr.indexOf(item) // 存在返回索引下标,否则返回`-1`
arr.indexOf(xxx) === -1 // 判断数组中是否存在某个元素

你可以笨笨地遍历数组

1
2
3
4
5
6
let arr2 = [12,23,12,22,45,16]
for(let i = 0; i < arr2.length; i++){
    if(arr2[i] === 12){
        console.log(`在第${i+1}个位置有一个12`)
    }
}

通常做法

1
2
3
4
let arr2 = [12,23,12,22,45,16]
arr2.indexOf(12) // 返回第一个满足条件元素的下标,index下标从0开始
arr2.indexOf(16)
arr2.indexOf(13) // -1 表示没有

还有lastIndexOf()方法,找数组中最后一个满足条件元素的下标

使用条件查找元素
1
arr.find(item => item % 2 === 0) //找第一个偶数

find()方法返回数组中满足条件的第一项的值。否则返回 undefined

这个条件就是,提供的一个条件判断的函数

find()方法不会改变原数组。

1
2
3
4
5
let arr2 = [12,23,12,22,45,16]
arr2.find((x)=> x%5 === 0)
arr2.find(function(x){
    return x%5===0
})

find()可以返回满足条件的元素(键)的值,但无法返回下标(键)

findIndex()

使用条件查找元素的索引
1
arr.findIndex(item => item % 2 === 0) //找第一个偶数的索引

返回下标

1
2
3
4
let arr2 = [12,23,12,22,45,16]
arr2.findIndex(function(x){
    return x%5===0
})

查数组元素小结

  • Array.indexOf()按下标来查找某个元素,如果下标越界,则返回-1
  • Array.find(),在find()里加条件,来查找符合这个条件(即返回true)的第一个元素,如果没有符合条件的元素,返回undefined
  • Array.findIndex(),在findIndex()里加条件,来查找符合这个条件(即返回true)的第一个元素对应的索引,如果没有符合条件的元素,返回-1

增元素

用对象的属性做一个错误的示范

1
2
3
let arr = [1,2,3,4,5,6]
arr[7] = 777
arr[100] = '0.1k' //变成稀疏数组 [1, 2, 3, 4, 5, 6, empty, 777, empty × 92, "0.1k"]

正确的方式有

在尾部增加元素

1
2
arr.push(newItem) //修改arr,返回新长度
arr.push(item1,item2) //修改arr,返回新长度
1
2
3
let arr = [1,2,3,4,5,6,7,8]
arr.push(999) // 返回9, arr变成 [1, 2, 3, 4, 5, 6, 7, 8, 999]
arr.push('a','b','c') // 返回12, arr变成 [1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c"]

在头部增加元素

1
2
arr.unshift(newItem) //修改arr,返回新长度
arr.unshift(item1,item2) //修改arr,返回新长度
1
2
let arr = [1,2,3,4,5,6,7,8]
arr.unshift('x','y','z') //返回11,arr变成 ["x", "y", "z", 1, 2, 3, 4, 5, 6, 7, 8]

在中间增加元素

1
2
arr.splice(index,0,'x') //在index前插入'x'
arr.splice(index,0,'x','y')
1
2
3
let arr = [1,2,3,4,5,6,7,8]
arr.splice(6,0,6.5) // 前插元素
arr.splice(6,1)

数组增删改查小结

改变原来的数组(栈、队列方法)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 改变原数组
arr = []
arr.push(1,2,3) // 返回 数组长度
arr.pop() // 返回从数组中删掉的 最后一个值 3
arr.shift() // 返回从数组中删掉的 第一个值 1
arr.unshit(1,2) // 返回 数组长度
arr.splice(beforeIndex,numbers,[newValues1,...]) // 可实现删除 增加和替换 返回 一个空数组 [] 并改变原数组

// 不改变原数组
let arr2 = arr.concat([1,2,3])
let arr 3 = arr.concat(8,9,10)

数组变换–修改数组中的元素

拿到数组的索引,就可以赋值,即修改数组

下面说几种特别的API

替换元素

1
2
3
4
let arr = [1,2,3,4,5,6,7,8]
arr.splice(5,1,6.66)
/* 更简单的 */
arr[5] = 666

反转顺序

1
2
3
let arr = [5,3,4,1,2]
arr.reverse() //改变了原数组
console.log(arr)

问:如何把字符串换个方向(排列)

1
2
3
4
let s = 'abcd'
s.split('') // ['a','b','c','d'] 将字符串中的字符按照传入参数中的字符分隔,并返回数组
s.reverse() // ['d','c','b','a']
s.join('') // 'dcba' 将数组合并为字符串

即用s.split('').reverse().join(''),一次搞定

自定义顺序

1
arr.sort((a,b)=> a-b)

默认从小到大排序

1
2
3
let arr = [5,3,4,1,2]
arr.sort() // [1,2,3,4,5]
console.log(arr) // 改变了原数组

sort()可以接受一个函数作为参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let arr = [5,3,4,1,2]
arr.sort(function(a,b){
    if(a > b){
        return 1
    }else if(a === b){
        return 0
    }else{
        return -1
    }
}) // [1,2,3,4,5]

反过来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let arr = [5,3,4,1,2]
arr.sort(function(a,b){
    if(a > b){
        return -1
    }else if(a === b){
        return 0
    }else{
        return 1
    }
}) //[1,2,3,4,5]

JS 默认较小的值在前,大的在后,局限于数字和字符串的比较

比较其他,需指定数值为参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let arr = [
    {name: "小方", score: 100},
    {name: "小圆", score: 95},
    {name: "小角", score: 99}
]
arr.sort() //没用 無駄、無駄、無駄
arr.sort(function(a,b){
    if(a.score > b.score){
        return 1
    }else if(a.score === b.score){
        return 0
    }else{
        return -1
    }
})

快速简写法

1
2
3
4
let arr = [5,3,4,1,2]
let arr2 = arr.sort((a,b) => {a - b} ) //错误写法
let arr3 = arr.sort((a,b) => {a - b} ).slice(0) //错误写法
arr.sort((a,b) => a - b) // 正确写法,得到:正值时,返回1;等值时,返回0;负值时,返回-1

方法用原地算法对数组的元素进行排序,并返回数组。

默认排序顺序是在将元素转换为字符串( ASCII 字符的字符串),然后比较它们的UTF-16代码单元值序列时构建的

由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。


数组变换(续)

按条件处理数组每项的值

1
2
3
4
5
6
7
function map1(arr, fn){
    let result = []
    for (let val of result){
        res.push(fn(val))
    }
    return result
} // 手写模拟`map`

map语法糖

nn

  • 举例:把数组每项都做平方处理
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let arr = [1,2,3,4,5,6]
let arr1 = arr.slice(0)

/* 先用for循环实现 */
for(let i = 0; i < arr.length; i++){
    arr[i] = arr [i] * arr[i]
}
console.log(arr)
/* 用for循环 太麻烦 */

/* map() 映射 */
arr2 = arr1.map(item => item * item) //返回新数组,不改变原数组
//返回新数组的索引结构和原数组一致

按条件过滤数组

1
2
3
4
5
6
7
function screen(arr, fn){
    let result = []
    for (let val of result){
        if(fn(v)) res.push(val)
    }
    return result
} // 手写模拟`filter`

filter语法糖

n

  • 举例:过滤掉奇数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
let arr = [1,2,3,4,5,6]
arr.filter(item => item %2 === 0 ? true : false) //[ 2, 4, 6 ]
arr.filter(item => item %2 === 0) //逻辑简写
arr.filter(item => {return item %2 === 0})
Object.keys(arr).filter(key => typeof arr[key] === 'number')
// 返回新数组,不改变原数组

/* 实现任意数组组内 数字元素的累加 拓展Array增加sum方法 */
Array.prototype.sum = function(){
    return this.filter(v => typeof v === 'number').reduce((v1,v2) => v1 + v2)
}
let summary = [3,'hello',4,{}].sum()

/* 实现数组元素 累加 */
Array.prototype.myRuduce = function(fn, initValue){
    let tmpArr = (initValue === undefined ? [] : [initValue]).concat(this)
    while(tmpArr.length > 1){
        tmpArr.splice(0, 2, fn(tmpArr[0],tmpArr[1]))
    }
    return tmpArr[0]
}
console.log([1,2,3,4,5].myRuduce((v1,v2)=> v1+v2))

按条件合并数组每项值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function myReduce(arr,fn,initValue){
    let tempArray = (initValue === undefined? [] : [initValue]).concat(arr) // 初始[0].concat(arr)
    /* 处理过程 每次只处理两个参数 */
    while(tempArray.length > 1){
        tempArray.splice(0,2,fn(tempArray[0],tempArray[1]))
    }
    /* 最终返回*/
    return tempArray[0]
} //部分模拟reduce

function reduce(arr,fn,initValue){}
reduce([3,-1,4,0],(res,v)=> res+v**2,0)

/* 算法分析
res     v   =>  res+v**2    [0, 3, -1, 4, 0]
0       3   =>  9           [   9, -1, 4, 0]
9      -1   =>  10          [      10, 4, 0]
10      4   =>  26          [         26, 0]
26      0   =>  26          [            26]   26
*/

reduce 语法糖

  • ES5新增的常规数组方法之一
  • 定义:对数组中的每个元素执行一个自定义的累计器,将其结果汇总为单个返回值
  • 形式:array.reduce((t, v, i, a) => {}, initValue)
  • 参数
    • callback:回调函数(必选) - 回调函数的参数 - - total(t):累计器完成计算的返回值(必选) - - value(v):当前元素(必选) - - index(i):当前元素的索引(可选) - - array(a):当前元素所属的数组对象(可选)
      • initValue:初始值(可选)
  • 过程
    • 以t作为累计结果的初始值,不设置t则以数组第一个元素为初始值
    • 开始遍历,使用累计器处理v,将v的映射结果累计到t上,结束此次循环,返回t
    • 进入下一次循环,重复上述操作,直至数组最后一个元素
    • 结束遍历,返回最终的t

reduce的精华所在是将累计器逐个作用于数组成员上,把上一次输出的值作为下一次输入的值

1
2
3
4
const arr = [3, 5, 1, 4, 2];
const a = arr.reduce((t, v) => t + v);
// 等同于
const b = arr.reduce((t, v) => t + v, 0);

reduce实质上是一个累计器函数,通过用户自定义的累计器对数组成员进行自定义累计,得出一个由累计器生成的值。

另外reduce还有一个胞弟reduceRight,两个方法的功能其实是一样的,只不过reduce是升序执行,reduceRight是降序执行。

对空数组调用reduce()reduceRight()是不会执行其回调函数的,可认为reduce()对空数组无效

基本用法

n1

  • 举例:求和
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let arr = [1,2,3,4,5,6]
let arr1 = arr.slice(0)
let sum = 0
for(let i = 0; i < arr.length; i++){
    sum += arr[i]
}
console.log(sum)

arr1.reduce((sum, item)=>{return sum + item} ,0) // 结果的初始值是0,每次压缩变量,return 的值作为下一次的结果

// 返回新数组,不改变原数组

抢钱的比喻

1
[100,200,300,400,500].reduce((sum,money)=>{return sum+money}, 0)

合成“一个”,不一定只有一个

  • 举例:用reduce实现把数组每项都做平方处理
1
[1,2,3,4,5,6].reduce((product,item)=>{return product.concat(item*item)},[]) //不用push,因为push每次返回的是数组长度

有了reduce()就不需要map了,map就是一个语法糖而已

  • 举例:用reduce()配合concat()实现过滤掉数组中的奇数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[1,2,3,4,5,6].reduce((result,item)=>{
    if(item %2 === 1){
        return result
    }else{
        return result.concat(item)
    }
},[])

[1,2,3,4,5,6].reduce((result,item)=>{
    return item % 2 === 1 ? result.concat() :  result.concat(item)
    }
,[])

[1,2,3,4,5,6].reduce((result,item)=> result.concat(item %2 === 1 ? [] : item)
,[])
// 返回新数组 不改变原数组
  • arr.reduce((val1,val2)=>{...})
  • arr.reduce((result,item)=>{...},initValue)
  • 遍历数组,调用回调函数,将数组元素合成一个值
  • 初始值可选

reduce()高级用法总结

25种场景来应用reduce的高级用法

累加累乘
权重求和
代替reverse
代替map和filter
数组过滤
代替some和every
数组分割,部分代替splice
数组填充
数组扁平
数组去重
数组最大最小值
数组成员独立拆解
数组成员个数统计
数组成员位置记录
数组成员特性分组
数组成员所含关键字统计
字符串翻转
数字千分化
异步累计
斐波那契数列

练手题

把数字变成星期

1
2
3
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map(item =>item === 0? '周日' :item === 1? '周一' : item === 2? '周二': item === 3? '周三': item === 4?'周四':item === 6? '周六':'周五')
console.log(arr2) // ['周日', '周一', '周二', '周二', '周三', '周三', '周三', '周四', '周四', '周四', '周四','周六']
  • 或者定义一个映射表,这是map的本意用法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] //映射表 下标对应
let arr2 = arr.map(item => week[item])
console.log(arr2)

/* 在内部定义也可 */
let arr3 = arr.map((item)=>{
    let week =['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  return week[item]
})

/* 用reduce() */

let arr1 = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr4 = arr1.reduce((result,item)=>{
    let week =['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  return result.concat(week[item])
},[])

找出所有大于60分的成绩

1
2
3
let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
let scores2 = scores.filter(item => item > 60)
console.log(scores2) //  [95,91,82,72,85,67,66, 91]

算出所有奇数之和

1
2
3
4
5
let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
let sum = scores.reduce((sum, n)=> {
  return sum + (n %2 === 1? n : 0)
}, 0)
console.log(sum) // 奇数之和:598

面试题

数据变换

1
2
3
4
5
let arr = [
    {名称: '动物', id: 1, parent: null},
    {名称: '狗子', id: 2, parent: 1},
    {名称: '喵', id: 3, parent: 1}
]

数组变成对象,其中两项变成第一项的属性

1
2
3
4
5
6
{
    id: 1, 名称: '动物', children: [
    {id: 2, 名称: '狗子', children: null},
    {id: 3, 名称: '喵', children: null},
    ]
}

分析

n1,用reduce

初始 空对象

改变结构 子属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let arr = [
    {名称: '动物', id: 1, parent: null},
    {名称: '狗子', id: 2, parent: 1},
    {名称: '喵', id: 3, parent: 1}
]

/* 初始值是空对象 */
arr.reduce((result,item)=>{},{})

/* 先试试直接把item按照id的顺序放到result */
arr.reduce((result,item)=>{
    result[item.id] = item.id
    return result
},{})

/* id 在最前 result.id = id放到最前 */
arr.reduce((result,item)=>{
    if(item.parent === null){
        result.id = item.id
        result['名称'] = item['名称']
    }else{// null | 1
        result.children.push(item)
        delete item.parent
        item.children = null
    }
    return result
},{id: null, children: []}) //初始化children数组,初始化id


参考文章

相关文章


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名