每天手写 lodash 方法

base 方法

在手写了几个方法后,发现 lodash 中的方法,除了一少部分可以不借助 base 方法外,其他都需要依赖于它;所以先模仿实现一些 base 文件中提供的方法,我觉得一定有所补益。

baseIteratee

baseIteratee 方法传入一个变量

function baseIteratee(value) {
  // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
  // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
  if (typeof value == 'function') {
    return value;
  }
  if (value == null) {
    return identity;
  }
  if (typeof value == 'object') {
    return isArray(value)
      ? baseMatchesProperty(value[0], value[1])
      : baseMatches(value);
  }
  return property(value);
}

1.如果传入的是一个 function ,则会直接返回;
2.如果传入一个 null ,则返回 identity
3.如果传入是数组,返回 baseMatchesProperty(value[0], value[1])
4.如果传入是对象,返回baseMatches(value)
5.如果都不是,返回 property(value)
出了第一个显而易见,其他的都要讲讲:

Array 方法

_.findIndex(array, [predicate=_.identity], [fromIndex=0])

该方法类似_.find,区别是该方法返回第一个通过 predicate 判断为真值的元素的索引值(index),而不是元素本身。

例子

var users = [
  { 'user': 'barney',  'active': false },
  { 'user': 'fred',    'active': false },
  { 'user': 'pebbles', 'active': true }
];
 
_.findIndex(users, function(o) { return o.user == 'barney'; });
// => 0
 
// The `_.matches` iteratee shorthand.
_.findIndex(users, { 'user': 'fred', 'active': false });
// => 1
 
// The `_.matchesProperty` iteratee shorthand.
_.findIndex(users, ['active', false]);
// => 0
 
// The `_.property` iteratee shorthand.
_.findIndex(users, 'active');
// => 2

手动实现

function myFindIndex(arr = []){
    
}

官方实现

function findIndex(array, predicate, fromIndex) {
  var length = array == null ? 0 : array.length;//数组长度
  if (!length) {//如果为空数组,返回-1
    return -1;
  }
  var index = fromIndex == null ? 0 : toInteger(fromIndex);//开始查找的索引
  if (index < 0) {//如果index为负值,从末尾开始算,-1就是最后一个元素,依次计算,如果最后大于数组的长度,就是0
    index = nativeMax(length + index, 0);
  }
  //调用baseFindIndex,并且将遍历方法封装,然后将结果作为返回值返回
  return baseFindIndex(array, baseIteratee(predicate, 3), index);
}

function baseFindIndex(array, predicate, fromIndex, fromRight) {
  var length = array.length,//数组长度
      index = fromIndex + (fromRight ? 1 : -1);//数组索引
  //循环,对每个元素调用判断方法,如果结果为true,返回对应的index
  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {
      return index;
    }
  }
  return -1;//返回-1
}

function baseIteratee(value) {
  // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
  // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
  if (typeof value == 'function') {
    return value;
  }
  if (value == null) {
    return identity;
  }
  if (typeof value == 'object') {
    return isArray(value)
      ? baseMatchesProperty(value[0], value[1])
      : baseMatches(value);
  }
  return property(value);
}

分析

最合核心的,lodash 使用了 baseIteratee :封装遍历器(让遍历器不仅可以是函数,还可以是属性或者对象)

_.fill(array, value, [start=0], [end=array.length])

使用 value 值来填充(替换) array,从start位置开始, 到end位置结束(但不包含end位置)。

Note: 这个方法会改变 array(注:不是创建新数组)。

例子

var array = [1, 2, 3];
 
_.fill(array, 'a');
console.log(array);
// => ['a', 'a', 'a']
 
_.fill(Array(3), 2);
// => [2, 2, 2]
 
_.fill([4, 6, 8, 10], '*', 1, 3);
// => [4, '*', '*', 10]

手动实现

function myFill(arr,value,start = 0,end = arr.length){
    if(!Array.isArray(arr)) return [];
    if(!value) return arr;

    for(let index = 0;index< arr.length;index++){ // 本想用 for in 的,但是发现稀疏数组,不能呗
        if(index >= start && index < end){
            arr[index] = value
        }
    }
    return arr
}

官方实现


_.drop(array, [n=1])

创建一个切片数组,去除array前面的n个元素。(n默认值为1。)

例子

_.drop([1, 2, 3]);
// => [2, 3]
 
_.drop([1, 2, 3], 2);
// => [3]
 
_.drop([1, 2, 3], 5);
// => []
 
_.drop([1, 2, 3], 0);
// => [1, 2, 3]

手动实现

function myDrop(arr,n = 1){
    if(!Array.isArray(arr))return []
    if(n >= arr.length) return []
    arr.splice(0,n);
    return arr
}

官方实现

function drop(array, n=1) {
  const length = array == null ? 0 : array.length
  return length
    ? slice(array, n < 0 ? 0 : toInteger(n), length)
    : []
}

比对

实现有误, drop 是一个纯函数,不能如此处理

_.differenceWith

different 系列的,借用基础方法太多,线条过

_.differenceBy(array, [values], [iteratee=_.identity])

作用

这个方法类似_.difference ,除了它接受一个 iteratee (注:迭代器), 调用array 和 values 中的每个元素以产生比较的标准。 结果值是从第一数组中选择。iteratee 会调用一个参数:(value)。(注:首先使用迭代器分别迭代array 和 values中的每个元素,返回的值作为比较值)。

Note: 不像 _.pullAllBy,这个方法会返回一个新数组。

例子

_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
// => [3.1, 1.3]
 
// The `_.property` iteratee shorthand.
_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
// => [{ 'x': 2 }]

手动实现

实现前先分析一下,这个方法和之前实现过的 _.difference 很像,所以把流程直接借鉴过来;不同之处在于本方法第三个参数。
根据官方的例子来看,如果第三个参数是一个 function,那么就对数组的每一项都运行这个方法iteratee(item);如果没传第三个参数或者第三个参数不是 function,则按照 myDifference 返回

function myDifferenceBy(traget,array,itera){

    if(!Array.isArray(traget) ||  !Array.isArray(array) ){
        return [];
    }

    if(typeof itera != 'function'){
     return myDifference(traget,array);
    }

    const resultArr = [];
    const flatArr = array.flat(Infinity)
    const changedFlatArr = flatArr.map(item => itera(item)   )


    for(let i of traget){
        const  changedI = itera(i)
        if(!changedFlatArr.includes(changedI)){
            resultArr.push(i)
        }
    }
    return resultArr;

}

官方实现

function differenceBy(array, ...values) {
  let iteratee = last(values)
  if (isArrayLikeObject(iteratee)) {
    iteratee = undefined
  }
  return isArrayLikeObject(array)
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), iteratee)
    : []
}

分析

last 方法返回数组的最后一项目
baseDifference 方法第三个参数传入了 iteratee
总的来说, difference 和 differentBy 的核心方法都是 baseDifference;

_.difference(array, [values])

作用

创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。(注:即创建一个新数组,这个数组中的值,为第一个数字(array 参数)排除了给定数组中的值。)该方法使用 SameValueZero(Array.includes 用的也是 SameValueZero )做相等比较。结果值的顺序是由第一个数组中的顺序确定。

例子

_.difference([3, 2, 1], [4, 2]);
// => [3, 1]

手动实现

function myDifference(traget=[],array = []){
    if(!Array.isArray(traget) ||  !Array.isArray(array) ){
        return [];
    }
    const resultArr = [];
    for(let i of traget){
        if(!array.flat(Infinity).includes(i)){
            resultArr.push(i)
        }
    }
    return resultArr;
}

官方实现

function difference(array, ...values) {
  return isArrayLikeObject(array)
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
    : []
}

分析

isArrayLikeObject用于判断一个变量是否是 ArryLike 或者 ObjectLike;
baseDifference 基本可以理解为作用和本方法(_.difference)没有区别;
baseFlatten 用于扁平化数组

_.concat(array, [values])

作用

创建一个新数组,将array与任何数组 或 值连接在一起。

例子

var array = [1];
var other = _.concat(array, 2, [3], [[4]]);
 
console.log(other);
// => [1, 2, 3, [4]]
 
console.log(array);
// => [1]

手动实现

function myConcat(){
    const argument =  Array.from(arguments)
    // 如果不让用 ES6,就换个替换的方案,问题不大
    //const argument = Array.prototype.slice.call(arguments);
    const arr = argument.shift();

    const arg =[]

    for(let a of argument){
        // 同理,不让用ES6 就自己写一个 isArray 的方法,有很多办法实现
        if(Array.isArray(a)){ // 如果是数组就降低一维
            arr.push(...a)
        }else{    // 如果不是数组就直接 push 进去
            arr.push(a)
        }

    }
    return arr 
}

官方实现

function concat() {
      var length = arguments.length;
      if (!length) {
        return [];
      }
      var args = Array(length - 1),
          array = arguments[0],
          index = length;

      while (index--) {
        args[index - 1] = arguments[index];
      }
      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
    }

分析

原理基本是一致的,去遍历传入的参数列表;如果是数组就降低一维,如果不是数组就直接 push 到返回数组中
loadsh 在这个方法中复用了自己之前实现的方法:baseFlatten、copyArray

_.compact(array)

作用

创建一个新数组,包含原数组中所有的非假值元素。例如false, null, 0, "", undefined, 和 NaN 都是被认为是“假值”。

_.compact([0, 1, false, 2, '', 3]);
// => [1, 2, 3]

手动实现

function myCompact(arr= []){
    const newArr = [];
    for(let item of arr){
       !isX(item) && newArr.push(item)
        function isX(item){
            let isX = false
            if(item === false){
                isX = true
            }else if(item === 0){
                isX = true
            }else if(item === ''){
                isX = true
            }else if(item !== item){ // NaN
                isX = true
            }else if(!item){ // undefined 和 null
                isX = true    
            }
            return isX;
        }
    }
       return newArr;
}

官方实现

function compact(array) {
  let resIndex = 0
  const result = []

  if (array == null) {
    return result
  }

  for (const value of array) {
    if (value) {
      result[resIndex++] = value
    }
  }
  return result
}

分析

  1. 检查传入值,如果是 null ,则直接返回
  2. 官方并没有分别去判断某一个值是不是虚值,直接用 if(value)运算,为真,则必不是虚值
  3. 没有使用 push ,而是指定下标的形式填充数组 result[resIndex++] = value (为什么?)

_.chunk(array, [size=1])

将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组。 如果array 无法被分割成全部等长的区块,那么最后剩余的元素将组成一个区块。

用法

_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
 
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]

手动实现:

function myChunk(arr,size = 1){
    const result = [];
    const arrLength = arr.length;
    let  index = 0;
    let resultNums = 0
    while(arrLength !== resultNums ){
        let thisArr = arr.splice(index,size)
        resultNums += thisArr.length
        result.push(thisArr);
    }
    return result;
}

官方实现

function chunk(array, size = 1) {
  size = Math.max(toInteger(size), 0)
  const length = array == null ? 0 : array.length
  if (!length || size < 1) {
    return []
  }
  let index = 0
  let resIndex = 0
  const result = new Array(Math.ceil(length / size))

  while (index < length) {
    result[resIndex++] = slice(array, index, (index += size))
  }
  return result
}

分析

  1. 对传入的 size 做了判断,先转为 number,如果小于 0,则直接返回
  2. 对传入的 array 做了判断,如果不是数组,则直接返回
  3. 使用 new Array 生成确定长度的数组;
Comments
Write a Comment