Blog icon indicating copy to clipboard operation
Blog copied to clipboard

语法和API - 实现数组方法(上)

Open logan70 opened this issue 5 years ago • 0 comments

实现数组方法(上)

所有数组方法的实现均忽略参数校验、边界条件判断,主要关注核心逻辑的实现。

部分数组方法会基于Array.prototype.reduce方法来实现,关于reduce方法的讲解及实现详见彻底搞懂数组reduce方法

Array.prototype.concat()

MDN - Array.prototype.concat()

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

Array.prototype._concat = function(...arrs) {
  return arrs.reduce((newArr, cur) => {
    return Array.isArray(cur)
      ? [...newArr, ...cur] // 传入项为数组则展开后合并
      : [...newArr, cur] // 传入项非数组则直接合并
  }, [...this])
}

Array.prototype.copyWithin()

MDN - Array.prototype.copyWithin()

copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

Array.prototype._copyWithin = function(target, start = 0, end = this.length) {
  const len = this.length
  const iTarget = target < 0
    ? Math.max(target + len, 0) // target为负从末尾计算,小于0时取0
    : Math.min(target, len) // target不为负时,若大于数组长度,取数组长度
  const iStart = start < 0
    ? Math.max(start + len, 0) // start为负从末尾计算,小于0时取0
    : Math.min(start, len) // start不为负时,若大于数组长度,取数组长度
  const iEnd = end < 0
    ? Math.max(end + len, 0) // end为负从末尾计算,小于0时取0
    : Math.min(end, len) // end不为负时,若大于数组长度,取数组长度
  
  const count = Math.min(iEnd - iStart, len - iTarget)
  // 这样实现便于理解,不考虑性能
  if (count > 0) {
    const copy = this.slice(iStart, iStart + count)
    this.splice(iTarget, count, ...copy)
  }
  return this
}

Array.prototype.entries()

MDN - Array.prototype.entries()

entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。

Array.prototype._entries = function() {
  function *gen() {
    for (let i = 0; i < this.length; i++) {
      yield [i, this[i]]
    }
  }
  return gen.call(this)
}

Array.prototype.every()

MDN - Array.prototype.every()

every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。若收到一个空数组,此方法在一切情况下都会返回 true。

every具有短路特性,一旦某次迭代返回false,则跳过后序迭代,直接返回false

Array.prototype._every = function(callback, thisArg) {
  for (let i = 0; i < this.length; i++) {
    if (i in this && !callback.call(thisArg, this[i], i, this)) {
      return false
    }
  }
  return true
}

Array.prototype.fill()

MDN - Array.prototype.fill()

fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

Array.prototype._fill = function(value, start = 0, end = this.length) {
  const len = this.length
  // 开始/结束索引 为负/超出范围处理
  let iStart = start < 0 ?
    Math.max(len + start, 0) :
    Math.min(start, len)
  const iEnd = end < 0 ?
    Math.max(len + end, 0) :
    Math.min(end, len)
  
  while (iStart < iEnd) {
    this[iStart] = value
    iStart++
  }
  return this
}

Array.prototype.filter()

MDN - Array.prototype.filter()

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

Array.prototype._filter = function(callback, thisArg) {
  return this.reduce((acc, cur, i, arr) => {
    return callback.call(thisArg, cur, i, arr)
      ? [...acc, cur]
      : acc
  }, [])
}

Array.prototype.find()

MDN - Array.prototype.find()

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

Array.prototype._find = function(callback, thisArg) {
  for (let i = 0; i < this.length; i++) {
    if(callback.call(thisArg, this[i], i, this)) {
      return this[i]
    }
  }
  return undefined
}

Array.prototype.findIndex()

MDN - Array.prototype.findIndex()

findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

Array.prototype._findIndex = function(callback, thisArg) {
  for (let i = 0; i < this.length; i++) {
    if(callback.call(thisArg, this[i], i, this)) {
      return i
    }
  }
  return -1
}

Array.prototype.flat()

MDN - Array.prototype.flat()

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。使用reduce实现。

Array.prototype._flat = function(depth = 1) {
  const flatBase = (arr, curDepth = 1) => {
    return arr.reduce((acc, cur) => {
      // 当前项为数组,且当前扁平化深度小于指定扁平化深度时,递归扁平化
      if (Array.isArray(cur) && curDepth < depth) {
        return acc.concat(flatBase(cur, ++curDepth))
      }
      return acc.concat(cur)
    }, [])
  }
  return flatBase(this)
}

Array.prototype.flatMap()

MDN - Array.prototype.flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

Array.prototype._flatMap = function(callback, thisArg) {
  return this.reduce((acc, cur, i, arr) => {
    return acc.concat(callback.call(thisArg, cur, i, arr))
  }, [])
}

Array.prototype.forEach()

MDN - Array.prototype.forEach()

forEach() 方法对数组的每个元素执行一次提供的函数,使用reduce来实现。

Array.prototype._forEach = function(callback, thisArg) {
  this.reduce((_, cur, i, arr) => {
    callback.call(thisArg, cur, i, arr)
  })
}

Array.prototype.includes()

MDN - Array.prototype.includes()

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

includes()同样具有短路特性。

Array.prototype._includes = function(valToFind, fromIndex = 0) {
  const start = fromIndex < 0 ?
    Math.max(this.length + fromIndex, 0) :
    Math.min(fromIndex, this.length)
  for (let i = start; i < this.length; i++) {
    const cur = this[i]
    if (valToFind === cur) {
      return true
    }
    if (Number.isNaN(valToFind) && Number.isNaN(cur)) {
      return true
    }
  }
  return false
}

Array.prototype.indexOf()

MDN - Array.prototype.indexOf()

indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

indexOf()同样具有短路特性,与includes()不同的是,indexOf()会跳过数组空项,且不会判定NaN的特殊情况,即任何数组indexOf(NaN)都返回 -1。

includes()会将数组空项判定为undefined,且NaN会判定为相等,即任意含有NaN的数组includes(NaN)都返回true

Array.prototype._indexOf = function(valToFind, fromIndex = 0) {
  const start = fromIndex < 0 ?
    Math.max(this.length + fromIndex, 0) :
    Math.min(fromIndex, this.length)
  for (let i = start; i < this.length; i++) {
    if (i in this && valToFind === this[i]) {
      return i
    }
  }
  return -1
}

Array.prototype.join()

MDN - Array.prototype.join()

join()方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。

Array.prototype._join = function(separator = ',') {
  const len = this.length
  let str = ''
  for (let i = 0; i < len; i++) {
    const cur = this[i]
    const curStr = (cur === undefined || cur === null)
      ? ''
      : cur.toString()
    str += curStr
    if (i !== len - 1) {
      str += separator
    }
  }
  return str
}

Array.prototype.keys()

MDN - Array.prototype.keys()

keys()方法返回一个包含数组中每个索引键的Array Iterator对象。与entries()方法实现相似。

Array.prototype._keys = function() {
  function *gen() {
    for (let i = 0; i < this.length; i++) {
      yield i
    }
  }
  return gen.call(this)
}

Array.prototype.lastIndexOf()

MDN - Array.prototype.lastIndexOf()

lastIndexOf()方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。与indexOf()实现相似。

lastIndexOf()也会跳过数组空项,同样也使用严格相等进行判定,所以任何数组lastIndexOf(NaN)都返回 -1。

Array.prototype._lastIndexOf = function(valToFind, fromIndex = this.length - 1) {
  const start = fromIndex < 0 ?
    Math.max(this.length + fromIndex, 0) :
    Math.min(fromIndex, this.length)
  for (let i = start; i >= 0; i--) {
    if (i in this && valToFind === this[i]) {
      return i
    }
  }
  return -1
}

Array.prototype.map()

MDN - Array.prototype.map()

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

Array.prototype._map = function(callback, thisArg) {
  const len = this.length
  const arr = new Array(len)
  for (let i = 0; i < len; i++) {
    if (i in this) {
      arr[i] = callback.call(thisArg, this[i], i, this)
    }
  }
  return arr
}

Array.prototype.pop()

MDN - Array.prototype.pop()

pop()方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

Array.prototype._pop = function() {
  const valToDel = this[this.length - 1]
  delete this[this.length - 1]
  this.length--
}

logan70 avatar Dec 17 '19 15:12 logan70