replace 会有undefined_lodash源码解析:reject、remove、repeat、replace、result...

cee9d62a14088893f357820de4180164.png

本篇按顺序看R开头的几个零散的小方法,包括rejectremoverepeatreplaceresultround。先分析 lodash 中对变参函数的处理,最后给出几个方法的原生实现。

对变参的处理

这几个方法中random实现比较有意思,分以下几种情况:

  1. 当不传参数时,返回[ 0, 1 ]之间的整数;
  2. 当传1个参数时,
  3. 数字,返回[ 0, arguments[0] ]
  4. 布尔值,根据真假返回[ 0, 1 ]之间的整数或浮点数;
  5. 2个参数时,
  6. 两个数字,返回[arguments[0], arguments[1] ]之间的随机整数;
  7. 一个数字和一个布尔值,根据真假返回[ 0, arguments[0] ]之间的整数或浮点数;
  8. 当传3个参数时,前两个数字,后一个布尔值,根据arguments[2]决定返回[arguments[0], arguments[1] ]之间的整数或者浮点数。

lodash 实现时的思路如下:

function random(lower, upper, floating) {
  // 判断给的参数够不够三个,在不够的情况下
  if (floating === undefined) {
    // 下面说明给了两个参数并且第二个是布尔值,修改参数为lower(不变), undefined, floating(原upper)
    if (typeof upper === 'boolean') {
      floating = upper;
      upper = undefined;
    }
    // 下面说明给了一个参数并且是布尔值,修改参数为undefined, undefined, floating(原lower)
    else if (typeof lower === 'boolean') {
      floating = lower;
      lower = undefined;
    }
  }

  // 当参数为undefined, undefined, floating时,也就是只给了一个floating参数或者没给参数时
  if (lower === undefined && upper === undefined) {
    // 将参数修改为0, 1, floating,返回0,1之间的值
    lower = 0;
    upper = 1;
  } else {
    // 下面的情况是至少有两个参数
    // 将lower改为有限数字
    lower = toFinite(lower);
    if (upper === undefined) {
      // 有两个参数的情况,把upper改为lower,lower改为0
      upper = lower;
      lower = 0;
    } else {
      // 有三个参数时,就将参数有限化
      upper = toFinite(upper);
    }
  }
  // 经过上面一系列处理,如果lower比upper大,就交换下
  if (lower > upper) {
    const temp = lower;
    lower = upper;
    upper = temp;
  }

  // 处理到这里时,已经变成正规的全参数的输入了。下面就是对random逻辑的处理

  // 如果floating为真,或者lower upper为浮点数,就返回浮点数
  if (floating || lower % 1 || upper % 1) {
    // 0-1的随机数
    const rand = Math.random();
    // 随机数长度
    const randLength = `${rand}`.length - 1;
    // 返回随机浮点数
    // freeParseFloat(`1e-${randLength}`)的操作是为了确保必为浮点数。
    // ??存疑当rand为0时还是返回0
    return Math.min(
      lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)),
      upper
    );
  }
  // 否则返回整数随机数,常规随机数操作
  return lower + Math.floor(Math.random() * (upper - lower + 1));
}

上述的 lodash 参数处理百转千回,如果是我处理可能按照之前分类的情况写,用参数长度(length)和类型(typeof)来区分不同的情况:

function random(...args) {
  let lower, upper, floating;
  const length = args.length;
  // 没有参数
  if (length === 0) {
    lower = 0;
    upper = 1;
    floating = false;
    // 一个参数
  } else if (length === 1) {
    lower = 0;
    if (typeof args[0] === 'boolean') {
      upper = 1;
      floating = args[0];
    } else {
      upper = args[0];
      floating = false;
    }
    // 两个参数
  } else if (length === 2) {
    if (typeof args[1] === 'boolean') {
      lower = 0;
      upper = args[0];
      floating = args[1];
    } else {
      lower = args[0];
      upper = args[1];
      floating = false;
    }
    // 三个参数
  } else if (length >= 3) {
    lower = args[0];
    upper = args[1];
    floating = args[2];
  }
  // 比较大小并交换
  lower = toFinite(lower);
  upper = toFinite(upper);
  if (lower > upper) {
    const temp = lower;
    lower = upper;
    upper = temp;
  }

  // 下面的业务处理就相同了

  // 如果floating为真,或者lower upper为浮点数,就返回浮点数
  if (floating || lower % 1 || upper % 1) {
    // 0-1的随机数
    const rand = Math.random();
    // 随机数长度
    const randLength = `${rand}`.length - 1;
    // 返回随机浮点数
    // freeParseFloat(`1e-${randLength}`)的操作是为了确保必为浮点数。
    // ??存疑当rand为0时还是返回0
    return Math.min(
      lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)),
      upper
    );
  }
  // 否则返回整数随机数,常规随机数操作
  return lower + Math.floor(Math.random() * (upper - lower + 1));
}

试验了下,和lodashrandom执行结果一致

依赖的 lodash 方法

negate

negate是一个参数类型为function,返回结果类型也是function的函数,最终目的就是对参数返回的结果取反。

/**
 * 创建一个针对断言函数 `func` 结果取反的函数。
 * `func` 断言函数被调用的时候,this 绑定到创建的函数,并传入对应参数。
 *
 * @since 3.0.0
 * @category Function
 * @param {Function} predicate 需要对结果取反的断言函数。
 * @returns {Function} 返回一个新的取反函数。
 * @example
 *
 * function isEven(n) {
 *   return n % 2 == 0
 * }
 *
 * filter([1, 2, 3, 4, 5, 6], negate(isEven))
 * // => [1, 3, 5]
 */
function negate(predicate) {
  // predicate如果不是函数就报错
  if (typeof predicate !== 'function') {
    throw new TypeError('Expected a function');
  }
  // 返回一个匿名函数
  return function (...args) {
    // 返回的匿名这个函数执行后会返回(predicate函数的执行结果)取反,
    // 执行predicate时把this绑定到该匿名函数上,顺带带上参数
    return !predicate.apply(this, args);
  };
}

export default negate;

filter

/**
 * 遍历 array(数组)元素,返回 predicate(断言函数)返回真值 的所有元素的数组。
 * predicate(断言函数)调用三个参数:(value, index, array)。
 *
 * **注意:** 与`remove`不同,此方法会返回一个新数组
 *
 * @since 5.0.0
 * @category Array
 * @param {Array} array 要迭代的数组
 * @param {Function} predicate 每次迭代调用的断言函数
 * @returns {Array} 返回新的筛选过的数组
 * @see pull, pullAll, pullAllBy, pullAllWith, pullAt, remove, reject
 * @example
 *
 * const users = [
 *   { 'user': 'barney', 'active': true },
 *   { 'user': 'fred',   'active': false }
 * ]
 *
 * filter(users, ({ active }) => active)
 * // => objects for ['barney']
 */
function filter(array, predicate) {
  // 各种初始化操作
  let index = -1;
  let resIndex = 0;
  const length = array == null ? 0 : array.length;
  const result = [];

  // 迭代
  while (++index < length) {
    // value为每一个元素
    const value = array[index];
    // 执行断言函数,返回真时,就push到result种
    if (predicate(value, index, array)) {
      result[resIndex++] = value;
    }
  }
  // 返回结果
  return result;
}

export default filter;

filterObject

/**
 * 迭代`object`的属性,返回 predicate(断言函数)返回真值 的所有元素的数组。
 * predicate(断言函数)调用三个参数:(value, index, array)。
 *
 * 如果想返回一个对象,请看`pickBy`
 *
 * @since 5.0.0
 * @category Object
 * @param {Object} object 要迭代的对象
 * @param {Function} predicate 每次迭代调用的函数
 * @returns {Array} 返回一个新的过滤后的数组
 * @see pickBy, pull, pullAll, pullAllBy, pullAllWith, pullAt, remove, reject
 * @example
 *
 * const object = { 'a': 5, 'b': 8, 'c': 10 }
 *
 * filterObject(object, (n) => !(n % 5))
 * // => [5, 10]
 */
function filterObject(object, predicate) {
  // 初始化操作
  object = Object(object);
  const result = [];

  // 迭代每一个键
  Object.keys(object).forEach((key) => {
    // value为每一个键对应的值
    const value = object[key];
    // 当断言函数返回真时,push到result种
    if (predicate(value, key, object)) {
      result.push(value);
    }
  });
  return result;
}

export default filterObject;

lodash 方法

random

random方法实际上是对Math.random的封装,可以返回固定范围内的整数或小数。

import toFinite from './toFinite.js';

/** 内置方法引用,不依赖于`root` */
const freeParseFloat = parseFloat;

/**
 * 产生一个包括 lower 与 upper 之间的数。
 * 如果只提供一个参数返回一个0到提供数之间的数。
 * 如果 floating 设为 true,或者 lower 或 upper 是浮点数,结果返回浮点数。
 *
 * **注意:** JavaScript 遵循 IEEE-754 标准处理无法预料的浮点数结果。
 *
 * @since 0.7.0
 * @category Number
 * @param {number} [lower=0] 下限
 * @param {number} [upper=1] 上限
 * @param {boolean} [floating] 指定是否返回浮点数
 * @returns {number} 返回随机数
 * @see uniqueId
 * @example
 *
 * random(0, 5)
 * // => 0到5之间的整数
 *
 * random(5)
 * // => 也是0到5之间的整数
 *
 * random(5, true)
 * // => 0到5之间的浮点数
 *
 * random(1.2, 5.2)
 * // => 1.2到5.2之间的浮点数
 */
function random(lower, upper, floating) {
  // 判断给的参数够不够,在不够的情况下
  if (floating === undefined) {
    // 说明给了两个参数,修改参数为lower, undefined, floating
    if (typeof upper === 'boolean') {
      floating = upper;
      upper = undefined;
    }
    // 说明给了一个参数,修改参数为undefined, undefined, floating
    else if (typeof lower === 'boolean') {
      floating = lower;
      lower = undefined;
    }
  }
  if (lower === undefined && upper === undefined) {
    // 当参数为undefined, undefined, floating时,将参数修改为0, 1, floating
    lower = 0;
    upper = 1;
  } else {
    // 下面的情况是至少有两个参数
    // 将lower改为有限数字
    lower = toFinite(lower);
    if (upper === undefined) {
      // 有两个参数的情况,把upper改为lower,lower改为0
      upper = lower;
      lower = 0;
    } else {
      // 有三个参数时,就将参数有限化
      upper = toFinite(upper);
    }
  }
  // 经过上面一系列处理,如果lower比upper大,就交换下
  if (lower > upper) {
    const temp = lower;
    lower = upper;
    upper = temp;
  }
  // 如果floating为真,或者lower upper为浮点数,就返回浮点数
  if (floating || lower % 1 || upper % 1) {
    // 0-1的随机数
    const rand = Math.random();
    // 随机数长度
    const randLength = `${rand}`.length - 1;
    // 返回随机浮点数
    // freeParseFloat(`1e-${randLength}`)的操作是为了确保必为浮点数。
    // ??存疑当rand为0时还是返回0
    return Math.min(
      lower + rand * (upper - lower + freeParseFloat(`1e-${randLength}`)),
      upper
    );
  }
  // 否则返回整数随机数,常规随机数操作
  return lower + Math.floor(Math.random() * (upper - lower + 1));
}

export default random;

reject

reject是利用了negate实现的filter的相反方法。

import filter from './filter.js';
import filterObject from './filterObject.js';
import negate from './negate.js';

/**
 * `filter`的相反方法,
 * 此方法 返回 predicate(断言函数) 不 返回 truthy(真值)的collection(集合)元素。
 *
 * @since 0.1.0
 * @category Collection
 * @param {Array|Object} collection 要迭代的集合
 * @param {Function} predicate 每次迭代时调用的函数
 * @returns {Array} 返回新的过滤后的数组
 * @see pull, pullAll, pullAllBy, pullAllWith, pullAt, remove, filter
 * @example
 *
 * const users = [
 *   { 'user': 'barney', 'active': true },
 *   { 'user': 'fred',   'active': false }
 * ]
 *
 * reject(users, ({ active }) => active)
 * // => objects for ['fred']
 */
function reject(collection, predicate) {
  // 当collection参数是数组时使用filter方法,是集合时使用filterObject方法
  const func = Array.isArray(collection) ? filter : filterObject;
  // 过滤参数其实是对所有的predicate取反,实现过滤掉所有为真的元素
  return func(collection, negate(predicate));
}

export default reject;

remove

remove方法在原数组中删除元素,并返回被删掉的元素组成的数组。

import basePullAt from './.internal/basePullAt.js';

/**
 * 移除数组中predicate(断言)返回为真值的所有元素,并返回移除元素组成的数组。
 * predicate(断言) 会传入3个参数: (value, index, array)。
 *
 * **注意:** 和 `filter`不同, 这个方法会改变数组 `array`。使用`pull`来根据提供的value值从数组中移除元素。
 *
 * @since 2.0.0
 * @category Array
 * @param {Array} array 要修改的数组
 * @param {Function} predicate 每次迭代调用的函数
 * @returns {Array} 返回移除元素组成的新数组
 * @see pull, pullAll, pullAllBy, pullAllWith, pullAt, reject, filter
 * @example
 *
 * const array = [1, 2, 3, 4]
 * const evens = remove(array, n => n % 2 == 0)
 *
 * console.log(array)
 * // => [1, 3]
 *
 * console.log(evens)
 * // => [2, 4]
 */
function remove(array, predicate) {
  // 初始化
  const result = [];
  // 条件不满足就直接返回空数组
  if (!(array != null && array.length)) {
    return result;
  }
  // 继续初始化
  let index = -1;
  const indexes = [];
  const { length } = array;

  // 迭代
  while (++index < length) {
    // 取到数组每个元素值
    const value = array[index];
    // 如果断言返回真
    if (predicate(value, index, array)) {
      // 就把 value push到result,组成了返回的结果
      result.push(value);
      // 把index push到indexes,便于使用pullAt
      indexes.push(index);
    }
  }
  // 在原数组中删掉了对应元素
  basePullAt(array, indexes);
  // 把被删掉元素组成的数组返回
  return result;
}

export default remove;

repeat

repeat方法利用了平方求幂算法进行了快速重复。

/**
 * 重复 `n` 次给定字符串。
 *
 * @since 3.0.0
 * @category String
 * @param {string} [string=''] 要重复的字符串。
 * @param {number} [n=1] 重复的次数。
 * @returns {string} 返回重复的字符串。
 * @example
 *
 * repeat('*', 3)
 * // => '***'
 *
 * repeat('abc', 2)
 * // => 'abcabc'
 *
 * repeat('abc', 0)
 * // => ''
 */
function repeat(string, n) {
  let result = '';
  // 当参数为空,或者重复次数不合理,直接返回空字符串
  if (!string || n < 1 || n > Number.MAX_SAFE_INTEGER) {
    return result;
  }
  // 利用平方求幂算法,以实现更快的重复。
  // 具体细节见 https://en.wikipedia.org/wiki/Exponentiation_by_squaring
  do {
    // 奇数
    if (n % 2) {
      // result就加一个当前字符串
      result += string;
    }
    n = Math.floor(n / 2);
    if (n) {
      // 当 n > 0 时,就字符串翻倍
      string += string;
    }
  } while (n);

  return result;
}

export default repeat;

replace

/**
 * 将`string`字符串中匹配的`pattern`替换为给定的`replacement`
 *
 * **注意:** 该方法基于 [`String#replace`](https://mdn.io/String/replace).
 *
 * @since 4.0.0
 * @category String
 * @param {string} [string=''] 要修改的字符串
 * @param {RegExp|string} pattern 要匹配的内容
 * @param {Function|string} replacement 替换的内容
 * @returns {string} 返回替换后的字符串
 * @see truncate, trim
 * @example
 *
 * replace('Hi Fred', 'Fred', 'Barney')
 * // => 'Hi Barney'
 */
function replace(...args) {
  // string是第一个参数,而且要进行字符串化
  const string = `${args[0]}`;
  // 参数少于三个时,直接返回string
  // 否则,就调用String.prototype.replace方法
  return args.length < 3 ? string : string.replace(args[1], args[2]);
}

export default replace;

result

result是一个类似get的方法,实现的方式很有意思,尽量少的定义变量。

import castPath from './.internal/castPath.js';
import toKey from './.internal/toKey.js';

/**
 * 这个方法类似`get`, 但是如果解析到的值是一个函数的话,
 * 就绑定 `this` 到这个函数并返回执行后的结果。
 *
 * @since 0.1.0
 * @category Object
 * @param {Object} object 要查询的对象
 * @param {Array|string} path 要解析的属性路径
 * @param {*} [defaultValue] 解析到`undefined`后返回的默认值
 * @returns {*} 返回解析后的值
 * @example
 *
 * const object = { 'a': [{ 'b': { 'c1': 3, 'c2': () => 4 } }] }
 *
 * result(object, 'a[0].b.c1')
 * // => 3
 *
 * result(object, 'a[0].b.c2')
 * // => 4
 *
 * result(object, 'a[0].b.c3', 'default')
 * // => 'default'
 *
 * result(object, 'a[0].b.c3', () => 'default')
 * // => 'default'
 */
function result(object, path, defaultValue) {
  // 把path转为路径数组
  path = castPath(path, object);

  let index = -1;
  let length = path.length;

  // 当path为空的时候确保能进入循环
  if (!length) {
    length = 1;
    object = undefined;
  }
  // 迭代
  while (++index < length) {
    // object不为空时,value设为取object的每一级
    // toKey(path[index])是获取当前级的键
    let value = object == null ? undefined : object[toKey(path[index])];
    // 只要有某一级value为undefined时,打破循环,直接等于defaultValue
    if (value === undefined) {
      index = length;
      value = defaultValue;
    }
    // 当某一级value是函数时,直接执行value.call(object)并赋值给object,
    // 否则把value直接赋值给object,object因此被降级
    object = typeof value === 'function' ? value.call(object) : value;
  }
  // 把最后的object返回
  return object;
}

export default result;

round

round方法是实现带精度的四舍五入,本质上利用的是createRound这个方法实现的。

import createRound from './.internal/createRound.js';

/**
 * 根据 `precision`(精度) 四舍五入 `number`。
 *
 * @since 3.10.0
 * @category Math
 * @param {number} number 要四舍五入的数字。
 * @param {number} [precision=0] .四舍五入的精度。
 * @returns {number} 返回四舍五入后的数字。
 * @example
 *
 * round(4.006)
 * // => 4
 *
 * round(4.006, 2)
 * // => 4.01
 *
 * round(4060, -2)
 * // => 4100
 */
// 调用createRound返回的方法
const round = createRound('round');

export default round;

createRound()方法,参数可以为floorceilround,返回不同的近似函数

/**
 * 创建一个函数,比如`round`
 *
 * @private
 * @param {string} methodName 当求近似时使用的`Math`方法名
 * @returns {Function} 返回一个新的求近似的函数
 */
function createRound(methodName) {
  // 选择一种近似方法,floor,ceil,round
  const func = Math[methodName];
  // 返回一个函数
  return (number, precision) => {
    // 当精度precision不存在时,赋值为0
    // precision大于等于0时,不能超过292
    // precision小于0时,不能小于-292
    precision =
      precision == null
        ? 0
        : precision >= 0
        ? Math.min(precision, 292)
        : Math.max(precision, -292);
    // 当精度不为0时
    if (precision) {
      // 用指数表示法转换以避免浮点问题。
      // 查看 [MDN](https://mdn.io/round#Examples) 更多细节.

      // 把数字split,pair[0]为系数,pair[1]为指数 比如(9876543211234567899876, -20)
      // 9876543211234567899876也就是98765432112345678e+21
      let pair = `${number}e`.split('e'); // pair => ['9.876543211234568', '+21']
      const value = func(`${pair[0]}e${+pair[1] + precision}`); // value => func(9.8765432112345678e1) => 99
      pair = `${value}e`.split('e'); // pair => ['99', '']
      // 在这一步恢复了原来的幂指数
      return +`${pair[0]}e${+pair[1] - precision}`; // 99e20
    }
    // 精度为0就直接调用Math的原生方法
    return func(number);
  };
}

export default createRound;

上面的情况说明的是带 e 显示的浮点数,下面再看看不带 e 的处理过程。

// (9.876, 2)
let pair = `${number}e`.split('e'); // pair => ['9.876', '']
const value = func(`${pair[0]}e${+pair[1] + precision}`); // value => func(9.876e2) => 988
pair = `${value}e`.split('e'); // pair => ['988', '']
// 在这一步恢复了原来的幂指数
return +`${pair[0]}e${+pair[1] - precision}`; // 988e-2 =>9.88

原生实现

repeatreplaceroundECMAScriptString.prototype 原生已经实现了的,直接使用即可。removeresult原生实现时也得和lodash差不多的思路。下面直接分析下 reject 的原生实现。

reject

reject 方法是一个类似 filter 的方法,只不过结果与 filter 完全相反。所以可以通过创建一个 complement 函数,该函数接收一个函数 f 作为参数,返回一个新的函数新的函数执行时返回的结果其实是!f(...args),实现了功能。

const reject = function (arr, predicate) {
  const complement = (f) => (...args) => !f(...args);
  return arr.filter(complement(predicate));
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值