join
先看效果
var array = ['a', 'b', 'c']
array.join('-') // 结果是 'a-b-c'
- array.join 实际上是 Array.prototype.join 对应的函数
- array.join('-') 等价于 array.join.call(array, '-')
- join 函数通过 this 和 arguments[0] 可以得到 array 和 '-' 两个值 因此,我们可以猜测 join 的源码是这样子的:
Array.prototype.join = function(char){
let result = this[0] || ''
let length = this.length
for(let i = 0; i<length; i++){
result += char + this[i]
}
return result
}
上述中的 this 就是 array,因为我们是使用 array.join('-') 来调用 join 的。
slice
先看效果
var array = [1,2,3,4,5,6]
array.slice(0, 4) // [1,2,3,4]
array.slice(beginIndex, endIndex)
我们猜测, slice 的源码如下:
Array.prototype.slice = function(begin, end){
let result = []
begin = begin || 0
end = end || this.length
for(let i=begin; i<end;i++){
result.push(this[i])
}
return result
}
于是,可以使用 slice 将伪数组转换成真实的数组。
array = Array.prototype.slice.call(arrayLike)
// OR
array = [].slice.call(arrayLike)
因此,ES6 专门出了一个 API 来将伪数组转换成真实的数组
array = Array.from(arrayLike)
伪数组与真实数组之间的差距就是:伪数组的原型链中没有 Array.prototype 属性,而真实数组中有 pop、push等操作。function 中的 arguments 即为伪数组。
伪数组
真实数组
sort
按照选择排序猜测 sort 的源码如下:时间复杂度O(n²),它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
Array.prototype.sort = function(fn){
fn = fn || (a-b)=>a-b
let roundCount = this.length-1 // 比较的轮数
for(let i=0; i<roundCount; i++){
let minIndex = this[i]
for(let k=i+1;k<this.length;k++){
if(fn.call(null, this[k], this[i]) < 0){
[this[i], this[k]] = [this[k], this[i]]
}
}
}
}
forEach
Array.prototype.forEach = function(fn){
for(let i=0;i<this.length;i++){
if(i in this){
fn.call(undefined, this[i], i, this)
}
}
}
forEach 和 for 的区别:
- forEach 没法 break
- forEach 每次迭代都会有一个新的作用域,而 for 在执行过程中只有一个作用域
经典面试题
// 点击每个 li 并输出其下标
<ul>
<li>0000001</li>
<li>0000002</li>
<li>0000003</li>
<li>0000004</li>
<li>0000005</li>
<li>0000006</li>
</ul>
let items = document.querySelectorAll('li')
for(var i=0; i<items.length; i++){
items[i].onlick = function(){
console.log(i)
}
} //// 点击每一项得出的都是 i === 6
//document.querySelectorAll得到的是伪数组
let itemArray = Array.from(items)
// 下面的slice也可以将伪数组转换成真数组
let itemArray = Array.prototype.slice.call(items)
itemArray.forEach((item, i)=<{
item.onlick = function(){
console.log(i)
}
})
/// forEach使用了函数,因此每次循环都会有一个新的作用域,故每次点击都是在新的作用域下,
/// 不会互相影响,可以输出正确的i
map
map 和 forEach 功能差不多,区别只是 map 有返回值而已
Array.prototype.map = function(fn){
let result = []
for(let i=0;i<this.length;i++){
if(i in this){
result[i] = fn.call(undefined, this[i], i, this)
}
}
return result
}
filter
Array.prototype.filter = function(fn){
let result = []
for(let i=0;i<this.length;i++){
if(i in this){
// temp 临时去保存 fn.call 的结果
if(fn.call(undefined, this[i], i, this)){
result.push(this[i])
}
}
}
return result
}
js 中有六个假值:false、0、NaN、null、undefined、'',只要不是这六个值中的值就是真值,返回真值就push,不返回真值就不push。
reduce
Array.prototype.reduce = function(fn, init){
let result = init
for(let i=0;i<this.length;i++){
if(i in this){
result = fn.call(undefined, result, this[i], i, this)
}
}
return result
}
map、reduce、filter的区别
- map 是等量的一对一的关系
- filter 是筛选,从多到少的操作
- reduce 是累加,将多个值变成一个 map、reduce、filter逻辑上的关联
- reduce 将上一次的结果跟下一个值做操作
- map 可以替代 forEach 使用,map 是有返回值的 forEach
- reduce 的初始值是空数组 [] 的话,其作用可以替代 map map可以用 reduce 表示
array2 = array.map(v=>v+1)
// 可以写成
array2 = array.reduce((result, v)=>{
result.push(v+1)
return result
}, [])
filter 也可以用 reduce 表示
array2 = array.filter(v=>v%2 === 0)
// 可以写成
array2 = array.reduce((result, v)=>{
if(v%2 === 0){
result.push(v)
}
return result
}, [])