前端入门JS篇之数组API(新人友好,本人手写实现)

1,086 阅读5分钟

Array

MDN定义 : JavaScript的 Array 对象是用于构造数组的全局对象,数组是类似于列表的高阶对象。

创建数组的几种方法

let a = [1,2]//直接赋值
let a = new Array(4)//构造函数赋值,定义数组长度,或者不给出具体长度都可以
let a = new Array('apple' , 'bear' , 'banana')//构造函数赋值,直接给出数组内容

Array常见API

操作数组的方法分为纯函数方法与非纯函数方法,纯函数方法不会改变原数组,非纯函数方法反之,下面说的API都是一些常见的API((其他实现请在网站下面评论,或者大家自己手写一遍))

/*
纯函数API : 
concat
filter
map
reduce
slice
非纯函数API
splice
shift(unshift同理)
reverse
*/

纯函数API

Array.prototype.concat

mdn定义 : concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组

语法

var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])
let arr = ['name' , {a:'zhoujing'}]
let b = [].concat(arr)
//b = ['name' , {a:'zhoujing'}]
arr[1].a = 'lvshui'
//b =['name',{a:'lvshui'}] 

需要注意的点

  • concat方法不会改变this或任何作为参数提供的数组,而是返回一个浅拷贝.也就是说,如果数组里有引用对象的话,拷贝过来的是引用地址

实现一个简易concat

//实现一个简易concat
Array.prototype._concat = function(...args){
    let context = this , result = [];
    if(!args.length){
      return context
    }
    //如果没有参数,直接返回this
    args.forEach( item => {
      if(Object.prototype.toString.call(item) !== '[object Array]'){
        result.push(item)
        //判断参数里面有没有数组
      }else{
        item.forEach( it => result.push(it))
      }
    })
    return result
}
console.log( []._concat(1,2,3,3,45))
console.log( []._concat(1,2,3,{name:'dd'},45))
console.log( []._concat([1,[2]],3,3,45))
console.log( []._concat() )
// [ 1, 2, 3, 3, 45 ]
// [ 1, 2, 3, { name: 'dd' }, 45 ]
// [ 1, [ 2 ], 3, 3, 45 ]
// []

Array.prototype.filter

mdn定义 : filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法

var new_array = arr.filter(callback(element[, index[, array]])[, thisArg])

需要注意的点

  • filter方法不会改变原数组,它返回过滤后的新数组
  • 这个过程是一次性的,后面数组改变不会影响filter的值

实现一个简易版filter函数

//实现一个简易filter
Array.prototype._filter = function(cb,thisArg){
    let context = this
    let cbThis = thisArg ? thisArg : null
    let result = []
    //this默认指向this,提供了thisArg的情况下指向thisArgs
    context.forEach( (item,index,arr) => {
      if(cb.call(cbThis,item,index,arr)){
        result.push(item)
      }
    })
    return result
}
console.log( [1,2,3,43]._filter( item => item&1 == 1 ) )
console.log( []._filter(item => item&1 == 1 , [1,2,3,43]) )
//[ 1, 3, 43 ]
//[]

Array.prototype.map

mdn定义 : map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

语法

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])

需要注意的点

  • map方法不会改变原数组,它返回新数组
  • callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身
  • map函数的第二个参数是可选项,作为callback的this指向
//实现一个简易map
Array.prototype._map = function(cb,thisArg){
    let context = this
    let cbThis = thisArg ? thisArg : null
    let result = []
    //this默认指向this,提供了thisArg的情况下指向thisArgs
    context.forEach( (item,index,arr) => {
       result.push( cb.call(cbThis,item,index,arr) )
      }) 
      return result
    }
console.log( [1,2,3]._map(Math.sqrt) )
console.log( [1,2,3]._map((index,item)=>{ return index * 2 + item}) )
// [ 1, 1.4142135623730951, 1.7320508075688772 ]
// [ 2, 5, 8 ]

Array.prototype.reduce

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

语法

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

需要注意的点

  • reduce方法不会改变原数组,它返回一个值
  • 提供初始值initialValue会更加安全(建议)
//实现一个简易reduce
Array.prototype._reduce = function(reducer,initialValue){
    let context = this , startIndex = 1
    debugger
    //this默认指向this,提供了thisArg的情况下指向thisArgs
    if(initialValue !== undefined){
        startIndex = 0
    }else{
        initialValue = context[0]
    }
    for(let i = startIndex ; i < context.length ; i ++){
        initialValue = reducer(initialValue,context[i],i,context)
    }
      return initialValue
    }
console.log([1,2,3,34]._reduce((a,b) => a+b , 12))
console.log([1,2,3,34]._reduce((a,b) => a.concat(b) , []))
// 52
// [ 1, 2, 3, 34 ]

Array.prototype.slice

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

语法

arr.slice([begin[, end]])

需要注意的点

  • slice方法是对数组项的浅拷贝
  • slice方法不会对原始数组进行修改
//实现一个简易slice
Array.prototype._slice = function(...args){
    let len = this.length , result = []
    let startIndex = args[0] === undefined ? 0 : args[0]
    let endIndex = args[1] === undefined ? len : args[1] >= len?len:args[1]
    for(let i = startIndex ; i < endIndex ; i ++ ){
        result.push(this[i])
    }
    return result
}
console.log( [1,2,3,34,3,3,3,33,3]._slice() )
//[1, 2,  3, 34, 3,3, 3, 33,  3]

非纯函数API

Array.prototype.splice

mdn定义 :splice()方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

语法

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

需要注意的点

  • 指定修改的开始位置(从0计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);如果负数的绝对值大于数组的长度,则表示开始位置为第0位。
  • 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。 如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素
//实现一个简易splice
Array.prototype._splice = function(startIndex,count,...items){
    debugger
    let context = this , len = context.length;
    let temp = this.slice()
    startIndex = startIndex < 0 ? Math.abs(startIndex>=len)?0:len + startIndex : startIndex
    //这里是判断startIndex负值相关情况
    //这里是判断count值得情况
    count = count <=0 ? 0 : count//count可以是负值
    if( count >= len - startIndex ){
        for(let i = 0 ; i < len - startIndex ; i ++){
            context.pop()
        }
        items.forEach( item => {
            context.push(item)
        })
    }else{//正常情况下得
        context.length = len - count + items.length//数组原地操作
        for(let j = context.length - 1 ; j >= startIndex + count ; j--){
            context[j] = context[ j + count - items.length ]
        }
        for(let i = 0 ; i < items.length ; i ++){
            context[startIndex + i] = items[i]
        }
    }
    console.log(this)
    return count === undefined ? temp.slice(startIndex) : temp.slice(startIndex,startIndex + count)
}
console.log( [1,2,3,34]._splice(1,2,3,4,45) )
// [ 1, 3, 4, 45, 34 ]
// [ 2, 3 ]

Array.prototype.shift

mdn定义 : shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。

语法

arr.shift()

需要注意的点

  • shift方法并不局限于数组,这个方法能够通过 call 或 apply 方法作用于类似数组的对象上.前提是对象具有length属性
//实现一个简易shift
Array.prototype._shift = function(){
    if(!this.length) return undefined;
    let context = this.slice()
    for(let i = 0 ; i < this.length ; i ++){
        this[i] = this[i+1]
    }
    this.length--
    console.log(this)
    return context[0]
}
console.log( []._shift() )
// undefined
// [ 2, undefined, 3, 4, 54, 56, 76 ]

Array.prototype.reverse

mdn定义 : 方法将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。

语法

arr.reverse()

需要注意的点

  • 方法颠倒数组中元素的位置,改变了数组,并返回该数组的引用
  • 也可应用于类数组对象中(建议查看MDN原文档)
//实现一个简易reverse
Array.prototype._reverse = function(){
    if(!this.length || this.length === 1) return this;
    let len = this.length , middle = parseInt(len / 2)
    for(let i = 0 ; i < middle ; i++){
        index = this[i]
        this[i] = this[len-i-1]
        this[len-i-1] = index
    }
    return this
}
console.log( [1,2,3,4,54,56,76]._reverse() )
console.log( [0,1,2,3,4,54,56,76]._reverse() )
// [76, 56, 54, 4,3,  2,  1]
// [76, 56, 54, 4,3,  2,  1, 0]