数组操作

数组的七个 API 的实现

join()

将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。

var array = ['a','b','c']
array.join('-') // 结果是 'a-b-c'

画一下内存图:


jzdg9gaxm4hh.png
  • array.join 实际上是 Array.prototype.join 对应的函数(array.join === Array.prototype.join === ADDR401)
  • array.join('-') 等价与 array.join.call(array, '-')
  • join 函数通过 this 和 arguments[0] 可以得到 array 和 '-' 两个值
    所以我们可以大胆猜测 array.join 的源代码大概是这样的
  //只接受一个参数; char 是 '-'  的意思
    Array.prototype.join = function(char){ 
        // 用 this 来接收数组
        // 首先 取第一个值 this[0] ,如果没有就是空字符
        let result = this[0] || ''
        let length = this.length
        // 遍历 this 
        for (let i =1; i < length; i++){
        //再从 i=1 (第二个值)开始取
        //遍历 this ,让 result 每次都加上 '-' 再加上 第 i 个值 
            result +=char + this[i]
        }
        return result
    }

this 就是 array,因为你使用 array.join('-') 来调用 join 的(隐式指定this)

slice()

返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。

array.slice(beginIndex, endIndex)

显而易猜,源码大概大概大概是这样的

    //Array 原型 有个slice 属相 ,接受两个参数(开始,结束)
    Array.prototype.slice = function(begin, end){
        // 声明一个空数组
    let result = []
    //如果 begin 不存在,让begin是 0
    begin = begin || 0
    // 如果 end 不存在,让 end 是 数组的长度
    end = end || this.length
    // 遍历数组
    // i 从 0 到 end , 每一个都 push 到  result数组 里面
    for(let i = begin; i< end; i++){
        result.push(this[i])
    }
    return result
}

于是很多前端用 slice 来将伪数组,转化成数组

array = Array.prototye.slice.call(arrayLike)
或者
array = [].slice.call(arrayLike)

ES 6 看不下去这种蹩脚的转化方法,出了一个新的 API

array = Array.from(arrayLike)

专门用来将伪数组转化成真数组。
注意:伪数组与真数组的区别就是:伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有 pop、join 等属性。

sort()

方法用原地算法对数组的元素进行排序,并返回数组。排序算法现在是稳定的。默认排序顺序是根据字符串Unicode码点。

  • 听说大部分的语言内置的 sort 方法都是快速排序算法。我们就简化成选择排序吧
 //给数组排序
    // sort 接受一个函数 fn 
    Array.prototype.sort = function(fn){
        //fn 默认就是一个 a-b  这样的函数
        fn = fn || ((a,b)=> a-b)
        // 两个数 ,谁小就将谁放在第一位
        // 所以两个数只有排一轮,三个数排两轮  ;this.length - 1
        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++){
                // 调用这个函数,这个函数用第一个参数 减去 第二个参数(a - b)
                // this 的 看k 项 减去 this 的 i 项 ,若值 <0 ,第 k 项比第 i 项 小
                if(fn.call(null,this[k],this[i]) < 0){
                    // 小的要放在前面,所以讲 第 k 项的值与 第 i  项的值互换
                    [this[i],this[k] = [this[k],this[i]]]
                }
            }
        }

    }
es6 语法 ,a 与b 的值互换
a = 'a'
//"a"
b ='b'
//"b"
[a,b]=[b,a]
//(2) ["b", "a"]
a
//"b"
b
//"a"
  • fn.call(null, this[k], this[i]) 决定了第 k 项和第 i 项的前后(大小)关系。
  • fn 为 (a,b) => a-b 表示啥?fn 为 (a,b) => b-a 又表示啥?
    不重要,因为如果前者不符合你的需求,那么后者一定符合你的需求,你只需要试两边就知道用哪一个了。
forEach()

对数组的每个元素执行一次提供的函数。

forEach源代码:

    Array.prototype.forEach = function(fn){
        //遍历 this
        for (let i=0; i < this.length;i++){
             // 如果 i 是 this 的 key
            if( i in this){
                fn.call(undefined, this[i], i ,this)
            }
        }
    }
a = [1,2,3]
(3) [1, 2, 3]
a.forEach ( (item)=>{
    console.log(item)
})
VM132:2 1
VM132:2 2
VM132:2 3

a.forEach((item,index,array)=>{
    console.log(item ,index,array )
 // fn.call(undefined, this[i], i ,this)
})
1 0 (3) [1, 2, 3]
 2 1 (3) [1, 2, 3]
 3 2 (3) [1, 2, 3]

forEach 把给我的函数在遍历的时候 call 一下
//fn 是  (item)=>{console.log(item) 这个函数
// a 是 this       
//a[i]] 是 this[i]  

//用item 来命名第一个参数 this[i]                               元素
//用 index 来命名 i                                            下标
//array 来表示 this ,因为已经知道了this 是a 这个数组            整个数组

forEach 和 for 的区别主要有两个:

  1. forEach 没法 break
    break: 此语句导致程序终止包含它的循环,并进行程序的下一阶段(整个循环后面的语句),即,不是跳到下一个循环周期而是退出循环。
  2. forEach 用到了函数,所以每次迭代都会有一个新的函数作用域;而 for 循环只有一个作用域(著名前端面试题就是考察了这个)
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 //将返回的值放到数组里面
}
  • 由于 map 和 forEach 功能差不多,区别只有返回值而已,所以我推荐忘掉 forEach,只用 map 即可。
  • 想用 map 的返回值就用,不用想就放在一边。

filter():过滤

创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。就是 过滤操作。

   Array.prototype.filter = function(fn){
        let result = []
        let temp
            for(let i=0;i<this.length;i++){
                if(i in this){
                    if(temp = fn.call(undefined,this[i],i,this)){
                        // 判读temp = fn.call()的值是否为真值 
                        //值为真 就放入数组里面
                        result.push(temp)
                        // 有选择的将值加入数组(满足条件)
                    }
                }
            }
        return result
    }


<!-- 6个假值
    false  0  ''  NAN    undefimed  null
-->


a = [1,2,3,4,5,6]
(6) [1, 2, 3, 4, 5, 6]
a.filter((value)=>{  
return value % 2 ===0
})
(3) [2, 4, 6]
  • fn.call() 返回真值就 push 到返回值,没返回真值就不 push。
reduce :求和

对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

 Array.prototype.reduce = function(fn,init){
        let result = init//初始值
        // 遍历每一个元素;fn.call()
        // 除了 this[i],i,this 这三个参数,还要传一个每一次调用之后(上一次)的结果
        //reduce :每一次把上次的结果和当前的数字进行操作;fn进行操作
        for(let i=0;i<this.length;i++){
            if(i in this){
                result  = fn.call(undefined,result,this[i],i,this)
            }
        }
        return result
    }
    
    
    
    
    a= [1,2,3,4,5]
(5) [1, 2, 3, 4, 5]
a.reduce((result,item,index,arrray)=>{
    return result + item
},0) 
15
map、filter 和 reduce 的区别:
  • map 是等量的一对一的关系
  • filter 是从多到少的过程 (过滤)
  • reduce 进行一个累加
map、filter 和 reduce 的联系:

map 可以用 reduce 表示

 array2 = array.map( (v) => v+1 )
 可以写成 
 array2 = array.reduce( (result, v)=> {
     result.push(v + 1)//push 当前值加 1
     return result //返回 result作为下一次的result
 }, [ ] )//result 最开始是空数组

filter 可以用 reduce 表示

 array2 = array.filter( (v) => v % 2 === 0 )
 可以写成
 array2 = array.reduce( (result, v)=> {
     if(v % 2 === 0){ result.push(v) }
     return result
 }, [])
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359