数组对象,区别于其他语言,比如JAVA,JS其实没有真正的数组,只是用对象模拟数组,即内置对象(built-in Object)
JS的数组是非经典数组
典型的数组
- 元素的数据类型相同
- 使用连续的内存存储
- 通过数字下标获取元素
JS的“数组”不同之处
- 元素的数据类型可以不同
- 内存不一定是连续的
- 不能通过数字下标,而是通过字符串下标
- 可以有任何
key
画个内存图
|
|

|
|
每个数组一创建就有
length属性,根据下标自动更新
创建一个数组
新建
|
|
语法
|
|
elementN
Array 构造器会根据给定的元素创建一个 JavaScript 数组,但是当仅有一个参数且为数字时除外(详见下面的 arrayLength 参数)。注意,后面这种情况仅适用于用 Array 构造器创建数组,而不适用于用方括号创建的数组字面量。
arrayLength
一个范围在 0 到 2^32-1 之间的整数,此时将返回一个 length 的值等于 arrayLength 的数组对象(言外之意就是该数组此时并没有包含任何实际的元素,不能理所当然地认为它包含 arrayLength 个值为 undefined 的元素)。如果传入的参数不是有效值,则会抛出 RangeError 异常。
下标,“键值”
|
|
转化
|
|
将下标隐式转换为字符串
|
|
伪数组(类数组对象)
|
|
|
|
|
|
伪数组有length属性
伪数组的原型链中并没有数组的原型,而是直接指向Object的原型
没有数组共有属性的「数组」,就是伪数组
拿到伪数组第一个要做的就是用
Array.from()变为真·数组
可用
Array.isArray(arr)判断(ES5)1 2 3 4 5 6 7 8 9function 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)
创建一个数组(续)
合并两个数组
|
|
不改变原数组,得到(返回)新数组
截取
一个数组的一部分,或全部截取
|
|
JS原生(til 2020)只提供浅拷贝
增删改查数组中的元素
删元素
和对象一样,可用delete操作符
|
|
有长度属性,但没有对应下标的数组,叫「稀疏数组」,只会带来bug
数组长度并未改变
delete方法是适用对象的,并不适合用在数组上
直接改length
|
|
不随便改
length不规范的删法
- 删除数组元素不要用
delete和改变length的错误方法
需要改length的场景
删元素(续)
删除头部元素
|
|
删除尾部元素
|
|
删除中间元素
|
|
加参数,就变改掉数组元素
|
|
|
|
查元素
查看所有元素
查看所有属性名
Object.keys(arr)
|
|
用另一种查看普通对象的遍历方法,试试
|
|
访问数组的时候并不希望访问到下标属性之外的
对象的遍历方法用在数组上不靠谱,只适合纯对象,不适合数组
所以要用其他的方法获取数组所有
keys和values
查看数组的正确姿势
for let i 循环查看数组属性名和值
区别于
for i in arr,遍历所有属性,指定具体要遍历的下标
|
|
- 所以访问数组,不要用
for in和Object.keys() - 都是访问对象的,有可能获取到非预期的东西
- 访问数组的标准做法就是
for(let i = 0; i < arr.length; i++){} - 要让
i从0增长到length - 1
数组提供的另一种方法,在数组的原型中可以看到
arr.forEach()查看数组属性名和值
|
|
继续打印[1,2,3,4,5]和对应下标
|
|
forEach用到了回调,怎么理解,自己手写一个
第一个版本,把每一个数组下标元素传到一个函数里
|
|
- 两种写法几乎等价:数组、forEach、函数三者都有
第二个版本,把每一个数组下标和下标元素传都到一个函数里
|
|
第三个版本,把数组本身也到函数里
|
|
forEach的原理就是遍历数组,每一次都调用一个函数
- 基本用到前两个参数就够了,参数名无所谓,顺序对就可以了
- 第三个参数在特殊情况下会用到,下节介绍_坑
问for循环遍历下标的方法和
forEach的区别是什么(有什么第一种可做到,而第二种做不到的一点)几乎通用,,但并不是语法糖的关系,因为以下
区别一:for循环里支持
break和continue,而forEach只是一个函数,不支持区别二:
for循环的for是一个关键字,没有函数作用域,只有块级作用域,而forEach是个函数,有函数作用域
|
|
用for循环遍历,可以随时
break或continue而
forEach按字面意思就是从开头到结尾每个都遍历到也可以用
forEach/map等原型上的函数
forEach是个坎
自己写一遍
forEach才能理解
|
|
forEach用for访问array的每一项- 对每一项调用
fn(array[i],i,array) 为什么要传入
array,龟腚1 2 3 4 5 6 7 8 9 10 11 12let 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 5let arr = [1,2,3] let isPositive = arr.every(val => val > 0) console.log(isPositive) let maybePositive = arr.some(val => val > 0) console.log(maybePositive)
查看单个元素
和对象一样
|
|
索引越界
|
|
索引不存在
|
|
举例
|
|
报一个错:
Cannot read property 'toString' of undefined意思是不可读取undefined的toString属性,不是toString是undefined
x.toString(),其中x如果是undefined,就报这个错用
console.log()大法解决
|
|
注意数组索引不要越界
查看单个属性(续)
arr.indexOf(item)
查找某个元素是否存在于数组里,并返回该元素的下标
|
|
你可以笨笨地遍历数组
|
|
通常做法
|
|
还有
lastIndexOf()方法,找数组中最后一个满足条件元素的下标
使用条件查找元素
|
|
find()方法返回数组中满足条件的第一项的值。否则返回undefined这个条件就是,提供的一个条件判断的函数
find()方法不会改变原数组。
|
|
find()可以返回满足条件的元素(键)的值,但无法返回下标(键)用
findIndex()
使用条件查找元素的索引
|
|
返回下标
|
|
查数组元素小结
- 用
Array.indexOf()按下标来查找某个元素,如果下标越界,则返回-1 - 用
Array.find(),在find()里加条件,来查找符合这个条件(即返回true)的第一个元素,如果没有符合条件的元素,返回undefined - 用
Array.findIndex(),在findIndex()里加条件,来查找符合这个条件(即返回true)的第一个元素对应的索引,如果没有符合条件的元素,返回-1
增元素
用对象的属性做一个错误的示范
|
|
正确的方式有
在尾部增加元素
|
|
|
|
在头部增加元素
|
|
|
|
在中间增加元素
|
|
|
|
数组增删改查小结
改变原来的数组(栈、队列方法)
|
|
数组变换–修改数组中的元素
拿到数组的索引,就可以赋值,即修改数组
下面说几种特别的API
替换元素
|
|
反转顺序
|
|
问:如何把字符串换个方向(排列)
|
|
即用
s.split('').reverse().join(''),一次搞定
自定义顺序
|
|
默认从小到大排序
|
|
sort()可以接受一个函数作为参数
|
|
反过来
|
|
JS 默认较小的值在前,大的在后,局限于数字和字符串的比较
比较其他,需指定数值为参数
|
|
快速简写法
|
|
方法用原地算法对数组的元素进行排序,并返回数组。
默认排序顺序是在将元素转换为字符串( ASCII 字符的字符串),然后比较它们的UTF-16代码单元值序列时构建的
由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。
数组变换(续)
按条件处理数组每项的值
|
|
map语法糖
n变n
举例:把数组每项都做平方处理
1 2 3 4 5 6 7 8 9 10 11 12 13let 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) //返回新数组,不改变原数组 //返回新数组的索引结构和原数组一致
按条件过滤数组
|
|
filter语法糖
n变少
举例:过滤掉奇数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22let 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))
按条件合并数组每项值
|
|
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的精华所在是将累计器逐个作用于数组成员上,把上一次输出的值作为下一次输入的值
|
|
reduce实质上是一个累计器函数,通过用户自定义的累计器对数组成员进行自定义累计,得出一个由累计器生成的值。另外reduce还有一个胞弟
reduceRight,两个方法的功能其实是一样的,只不过reduce是升序执行,reduceRight是降序执行。对空数组调用
reduce()和reduceRight()是不会执行其回调函数的,可认为reduce()对空数组无效
基本用法
n变1
举例:求和
1 2 3 4 5 6 7 8 9 10 11let 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 的值作为下一次的结果 // 返回新数组,不改变原数组
抢钱的比喻
|
|
合成“一个”,不一定只有一个
举例:用
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
数组填充
数组扁平
数组去重
数组最大最小值
数组成员独立拆解
数组成员个数统计
数组成员位置记录
数组成员特性分组
数组成员所含关键字统计
字符串翻转
数字千分化
异步累计
斐波那契数列
练手题
把数字变成星期
|
|
或者定义一个映射表,这是
map的本意用法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18let 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分的成绩
|
|
算出所有奇数之和
|
|
面试题
数据变换
|
|
数组变成对象,其中两项变成第一项的属性
|
|
分析
n变1,用reduce初始 空对象
改变结构 子属性
|
|
参考文章
相关文章
- 无