免费IT学习资料 加群:272292492
数组是一个超常用的数据结构,JavaScript的数组方法都有什么怎样的特性呢?
JavaScript中数组是一个对象,默认的赋值是传了一个引用。针对结果是引用还是拷贝,对原数组的变更与否,分为两类方法:必写方法、只读方法。
必写方法
splice
Array
.prototype
.
splice
(start
: number
, deleteCount
: number
,
...items
: any
[
]
)
: any
[
]
arr.splice(start, deleteCount, …items) 是将arr[start, start + deleteCount) 的部分裁去,然后在这里插入items。
这个 splice 的表达能力非常强大,在数组的特定位置裁一刀,并用一个数组补上去。并返回因为被裁掉而生成的数组。虽然它看起来是块级的操作好像可以实现常数时间复杂度,但是其实它是一个线性的操作,从参数列表中可以看出它是线性的。
思考:splice 对于插入参数的长度而言的插入效率如何?[如果Array以链表实现,插入的代价最快是常数时间的]
参考:是线性时间复杂度的,而不是常数时间的。注意它的参数列表,传参方式决定了它是逐一处理参数的。例如调用splice(0, 0, [1, 2]) 的结果是插入了一个[1, 2] 而不是1, 2 这两个数。
copyWithin
Array
.prototype
.
copyWithin
(target
: number
, start
: number
, end
: number
)
:
this
arr.copyWithin(target, start, end) 是将 arr[start, end) 这部分先做一个拷贝,然后再贴到从arr[target] 开始的位置。
思考:如何用 splice 实现 copyWithin?
fill
Array
.prototype
.
fill
(value
: number
, start
: number
, end
: number
)
:
this
arr.fill(value, start, end) 是将 arr[start, end) 的部分都填充成同一个 value。
var arr
=
[
1
,
2
,
3
]
;
arr
.
fill
(
0
,
1
,
2
)
;
arr
.
fill
(
0
)
;
var arr
=
[
{
}
,
{
}
]
;
arr
[
0
]
== arr
[
1
]
;
arr
.
fill
(
{
}
)
;
arr
[
0
]
== arr
[
1
]
;
push, unshift, pop, shift
Array
.prototype
.
push
(
...items
: any
[
]
)
: number
Array
.prototype
.
unshift
(
...items
: any
[
]
)
: number
Array
.prototype
.
pop
(
)
: any
Array
.prototype
.
shift
(
)
: any
将Array看作是一个双向队列(deque)可能是比较恰当的。
提示:组合使用 push 与 pop 可以使得 Array 变成一个栈;组合使用 push 与 shift 可以使得 Array 变成一个队列。
思考:组合使用 unshift 与 shift 是否实现了栈?组合使用 unshift 与 pop 是否实现了队列?
参考:是,但这种方式有一个小坑点。因为从结果上说,unshift(1, 2)与先后调用 unshift(1), unshift(2)不同。可以推测,unshift与push是splice的特殊情况。unshift(…items) 与 splice(0, 0, …items) 是一致的,push(…items) 与 splice(this.length, 0, …items) 是一致的。BTW,shift() 与 splice(0, 1) 一致; pop()与splice(this.length - 1, 1) 一致;
sort
Array
.prototype
.
sort
(sortFn
:
(a
: any
, b
: any
)
=
> number
)
:
this
var arr
=
[
2
,
1
]
;
arr
.
sort
(
)
;
arr
.
sort
(
(a
, b
)
=
> b
- a
)
;
reverse
Array
.prototype
.
reverse
(
)
:
this
var arr
=
[
1
,
2
,
3
]
;
arr
.
reverse
(
)
;
arr
;
乍一想,反转可以算是一种按照索引号反序的特殊的排序,但 sort 的比较函数不能按照索引号写,这就比较尴尬了。
var arr
=
[
1
,
2
,
3
]
;
arr
= arr
.
map
(
(v
, i
)
=
>
{
return
{value
: v
, index
: i
}
;
}
)
.
sort
(
(a
, b
)
=
> b
.index
- a
.index
)
.
map
(v
=
> v
.value
)
;
当然,这种方式看起来简直蠢爆了,从时间、空间效率上看都不能采用,只是体现了一种思路。
只读方法
forEach
Array
.prototype
.
forEach
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
=
> undefined
, thisArg
: any
)
: undefined
forEach 与 for 循环
var arr
=
[
1
,
3
]
;
arr
.
forEach
(v
=
> arr
.
push
(v
)
)
;
arr
;
var arr
=
[
1
,
3
]
;
for
(
var i
=
0
; i
< arr
.length
; i
++
) arr
.
push
(arr
[i
]
)
;
var arr
=
[
1
,
3
]
;
for
(
var i
in arr
) arr
.
push
(arr
[i
]
)
;
arr
;
var arr
=
[
1
,
3
]
;
for
(
var i
of arr
) arr
.
push
(i
)
;
这里提供了一种简单的Hack方式(forEach 的 for…in 实现):
Array
.prototype
.forEach
=
function
(callbackFn
, thisArg
)
{
for
(
var i
in
this
) callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
;
}
由于 for…in 循环还能遍历对象的属性,还可以写一个Object版本的forEach:
Object
.prototype
.forEach
=
function
(callbackFn
, thisArg
)
{
for
(
var i
in
this
) callbackFn
.
call
(thisArg
,
this
[i
]
, i
,
this
)
;
}
映射 map
Array
.prototype
.
map
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
=
> T
, thisArg
: any
)
: T
[
]
var arr
=
[
1
,
2
,
3
]
;
arr
.
map
(v
=
> v
* v
)
;
arr
;
Array
.prototype
.map
=
function
(callbackFn
, thisArg
)
{
var ret
=
[
]
;
for
(
var i
in
this
) ret
.
push
(callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
)
;
return ret
;
}
聚合 reduce, reduceRight, every, some, join, indexOf, lastIndexOf, find, findIndex
Array
.prototype
.
reduce
(callbackFn
:
(previousValue
: any
, currentValue
: any
, currentIndex
: number
, array
:
this
)
=
> any
, initialValue
: any
)
: any
Array
.prototype
.
reduceRight
(callbackFn
:
(previousValue
: any
, currentValue
: any
, currentIndex
: number
, array
:
this
)
=
> any
, initialValue
: any
)
: any
Array
.prototype
.
every
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
, thisArg
: any
)
: boolean
Array
.prototype
.
some
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
, thisArg
: any
)
: boolean
Array
.prototype
.
join
(separator
: string
)
: string
Array
.prototype
.
find
(callbackFn
:
(value
: T
, index
: number
, array
:
this
)
=
> boolean
, thisArg
: any
)
: T
Array
.prototype
.
findIndex
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
=
> boolean
, thisArg
: any
)
: number
Array
.prototype
.
indexOf
(item
: any
, start
: number
)
: number
Array
.prototype
.
lastIndexOf
(item
: any
, start
: number
)
: number
聚合 reduce & reduceRight
reduce 是遍历的同时将某个值试图不断更新的方法。
reduceRight 功能一样,但是从右侧开始(索引号大的一侧)。
可以非常简单地做到从一个数组中得出一个值 的操作,如求和,求最值等。
[
1
,
3
,
2
]
.
reduce
(
(pre
, cur
)
=
> pre
+ cur
,
1
)
;
[
1
,
3
,
2
]
.
reduce
(
(pre
, cur
)
=
> Math
.
max
(pre
, cur
)
,
-
Infinity
)
;
[
1
,
3
,
2
]
.
reduce
(
(pre
, cur
, i
)
=
> pre
+
'+'
+ cur
+
'*x^'
+ i
,
''
)
;
[
1
,
3
,
2
]
.
reduceRight
(
(pre
, cur
, i
)
=
> pre
+
'+'
+ cur
+
'*x^'
+ i
,
''
)
;
Array
.prototype
.reduce
=
function
(callbackFn
, initialValue
)
{
for
(
var i
in
this
)
callbackFn
(initialValue
,
this
[i
]
,
~
~i
,
this
)
;
return initialValue
;
}
谓词 every & some
every与some分别是数组中全称、存在量词的谓词。
全称谓词 every: 是否数组中的元素全部都满足某条件。
存在谓词 some: 是否数组中的元素有一个满足某条件。
[
1
,
2
,
3
]
.
every
(v
=
> v
>
0
)
;
[
1
,
2
,
3
]
.
every
(v
=
> v
==
1
)
;
[
1
,
2
,
3
]
.
some
(v
=
> v
==
1
)
;
[
1
,
2
,
3
]
.
some
(v
=
> v
==
0
)
;
var x
=
[
]
;
[
1
,
2
,
3
]
.
every
(v
=
>
{x
.
push
(v
)
;
return v
<
2
;
}
)
x
;
var x
=
[
]
;
[
1
,
2
,
3
]
.
some
(v
=
>
{x
.
push
(v
)
;
return v
==
2
;
}
)
x
;
Array
.prototype
.every
=
function
(callbackFn
, thisArg
)
{
return
this
.
reduce
(
function
(previousValue
, currentValue
, currentIndex
, array
)
{
return previousValue
&& callbackFn
.
call
(thisArg
, currentValue
, currentIndex
, array
)
;
}
,
true
)
;
}
Array
.prototype
.some
=
function
(callbackFn
, thisArg
)
{
return
this
.
reduce
(
function
(previousValue
, currentValue
, currentIndex
, array
)
{
return previousValue
|| callbackFn
.
call
(thisArg
, currentValue
, currentIndex
, array
)
;
}
,
false
)
;
}
结果对了,然而很抱歉,尽管每次逻辑运算有短路判定了,但是reduce遍历的开销去不掉,性能不够。
Array
.prototype
.every
=
function
(callbackFn
, thisArg
)
{
var ret
=
true
;
for
(
var i
in
this
)
{
if
(ret
==
false
)
break
;
ret
&
= callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
;
}
return ret
;
}
Array
.prototype
.some
=
function
(callbackFn
, thisArg
)
{
var ret
=
false
;
for
(
var i
in
this
)
{
if
(ret
==
false
)
break
;
ret
|
= callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
;
}
return ret
;
}
串行化 join
join可以将一个数组以特定的分隔符转化为字符串。
[
1
,
2
,
3
]
.
join
(
)
;
[
1
,
2
,
3
]
.
join
(
','
)
;
[
1
,
2
,
3
]
.
join
(
' '
)
;
[
1
,
2
,
3
]
.
join
(
'\n'
)
;
[
1
,
2
,
3
]
.
join
(
'\b'
)
;
[
1
,
2
,
3
]
.
join
(
'heiheihei'
)
;
Array
.prototype
.join
=
function
(separator
)
{
if
(separator
=== undefined
) separator
=
','
;
return
this
.
reduce
(
(pre
, cur
)
=
> pre
+
(pre
? separator
:
''
)
+ cur
,
''
)
;
}
Array.prototype.toString() 可以等效于 Array.prototype.join()。当然,这两个函数对象本身是不同的。
搜索 find, findIndex, indexOf, lastIndexOf
返回从头开始第一个符合条件的元素的索引号 findIndex
返回从头开始第一个特定元素的索引号 indexOf
返回从尾开始第一个特定元素的索引号 lastIndexOf
[
1
,
3
,
2
,
1
]
.
find
(v
=
> v
>
1
)
;
[
1
,
3
,
2
,
1
]
.
find
(v
=
> v
>
3
)
;
[
1
,
3
,
2
,
1
]
.
findIndex
(v
=
> v
>
1
)
;
[
1
,
3
,
2
,
1
]
.
findIndex
(v
=
> v
>
3
)
;
[
1
,
3
,
2
,
1
]
.
indexOf
(
1
)
;
[
1
,
3
,
2
,
1
]
.
indexOf
(
4
)
;
[
1
,
3
,
2
,
1
]
.
lastIndexOf
(
1
)
;
[
1
,
3
,
2
,
1
]
.
lastindexOf
(
4
)
;
[
1
,
3
,
2
,
1
]
.
indexOf
(
1
,
1
)
;
[
1
,
3
,
2
,
1
]
.
lastIndexOf
(
1
,
2
)
;
Array
.prototype
.find
=
function
(callbackFn
, thisArg
)
{
return
this
.
reduce
(
(pre
, cur
, i
)
=
>
{
if
(pre
=== undefined
&& callbackFn
.
call
(thisArg
, cur
, i
,
this
)
)
return cur
;
}
)
;
}
Array
.prototype
.findIndex
=
function
(callbackFn
, thisArg
)
{
return
this
.
reduce
(
(pre
, cur
, i
)
=
>
{
if
(pre
==
-
1
&& callbackFn
.
call
(thisArg
, cur
, i
,
this
)
)
return i
;
}
,
-
1
)
;
}
这个reduce写法并不具备短路优化,与every, some的reduce写法一样存在性能问题。
Array
.prototype
.find
=
function
(callbackFn
, thisArg
)
{
for
(
var i
in
this
)
if
(callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
)
return
this
[i
]
;
}
Array
.prototype
.findIndex
=
function
(callbackFn
, thisArg
)
{
for
(
var i
in
this
)
if
(callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
)
return i
;
}
然后,indexOf 可看作是 findIndex 的一个特例。
Array
.prototype
.indexOf
=
function
(item
, start
)
{
return
this
.
findIndex
(
(v
, i
)
=
> i
>= start
&& v
== item
)
;
}
子数组 与 filter, slice
Array
.prototype
.
filter
(callbackFn
:
(value
: any
, index
: number
, array
:
this
)
=
> boolean
, thisArg
: any
)
: any
[
]
Array
.prototype
.
slice
(start
: number
, end
: number
)
: any
[
]
过滤 filter
[
1
,
2
,
3
]
.
filter
(v
=
> v
%
2
==
0
)
;
[
1
,
2
,
3
]
.
filter
(v
=
> v
&
1
)
;
[
1
,
2
,
3
]
.
filter
(
(v
, i
)
=
> i
>=
1
)
;
Array
.prototype
.filter
=
function
(callbackFn
, thisArg
)
{
var ret
=
[
]
;
for
(
var i
in
this
)
if
(callbackFn
.
call
(thisArg
,
this
[i
]
,
~
~i
,
this
)
)
ret
.
push
(
this
[i
]
)
;
return ret
;
}
如果强行把 子数组也看成一个数的话,也可以写成reduce:
Array
.prototype
.filter
=
function
(callbackFn
, thisArg
)
{
return
this
.
reduce
(
(pre
, cur
, i
)
=
>
{
if
(callbackFn
.
call
(thisArg
, cur
, i
,
this
)
)
pre
.
push
(cur
)
;
return pre
;
}
,
[
]
)
;
}
切片 slice
生成数组在区间[start, end) 中的切片。
[
1
,
2
,
3
]
.
slice
(
)
;
[
1
,
2
,
3
]
.
slice
(
0
)
;
[
1
,
2
,
3
]
.
slice
(
1
)
;
[
1
,
2
,
3
]
.
slice
(
1
,
2
)
;
[
1
,
2
,
3
]
.
slice
(
2
,
2
)
;
[
1
,
2
,
3
]
.
slice
(
2
,
1
)
;
Array
.prototype
.slice
=
function
(start
, end
)
{
return
this
.
filter
(
(v
, i
)
=
> i
>= start
&& i
< end
)
;
}
超数组 与 concat
生成原数组的超数组,保持原数组在超数组中的顺序不变。
Array
.prototype
.
concat
(
...items
: any
[
]
)
: any
[
]
[
1
,
2
,
3
]
.
concat
(
)
;
[
1
,
2
,
3
]
.
concat
(
1
,
5
)
;
[
1
,
2
,
3
]
.
concat
(
[
1
,
5
]
)
;
[
1
,
2
,
3
]
.
concat
(
1
,
[
3
]
,
[
[
5
,
6
]
]
,
6
)
;
[
]
.
concat
(
{a
:
1
}
)
;
Array
.prototype
.concat
=
function
(
...items
)
{
var ret
=
[
]
;
for
(
var i
in
this
) ret
.
push
(
this
[i
]
)
;
for
(
var i
in items
)
{
if
(Array
.
isArray
(items
[i
]
)
)
for
(
var j
in items
[i
]
) ret
.
push
(items
[i
]
[j
]
)
;
else ret
.
push
(items
[i
]
)
;
}
return ret
;
}
同 filter 的思路,也有reduce的写法,感觉不是很优雅,就留作日后思考吧:)
结语
函数式编程使人受益匪浅,集中在“思考问题的本质”这个角度。
Functional programming considers what the problem is rather than how the solution works.
比起思考解决方案如何运作,函数式编程更注重思考这个问题的本质是什么。