数组相关操作

194 阅读1分钟

join

先看效果

var array = ['a', 'b', 'c']
array.join('-')    // 结果是 'a-b-c'
  1. array.join 实际上是 Array.prototype.join 对应的函数
  2. array.join('-') 等价于 array.join.call(array, '-')
  3. 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 即为伪数组。

伪数组

image.png

真实数组

image.png

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 的区别:

  1. forEach 没法 break
  2. 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的区别

  1. map 是等量的一对一的关系
  2. filter 是筛选,从多到少的操作
  3. reduce 是累加,将多个值变成一个 map、reduce、filter逻辑上的关联
  4. reduce 将上一次的结果跟下一个值做操作
  5. map 可以替代 forEach 使用,map 是有返回值的 forEach
  6. 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
}, [])

underscore