不定期更新的源码阅读日常——lodash-1

  1. 不定期更新的源码阅读日常将不会采用逐行摘抄源码然后分析阅读的方式进行源码阅读,而是提炼分享源码中个人发人深省的部分进行摘录总结,知识补足。
  2. 不定期更新的源码阅读日常阅读的库都是模块零碎化或者小功能库。方便灵活,而且不需要连续阅读。
  3. 不定期更新的源码阅读日常将不定期更新。
  4. 欢迎大家关注我的个人博客,来查看我每周都会更新的一些文章。

今天我们来读lodash的Array部分。

数组length的边界处理

  lodash中Array部分相关操作,经常需要对入参的取值进行边界处理。比如调用fill方法中startend的大小,chunk方法中size的大小,以确保函数的正常执行。

  在处理数组的length边界时,lodash借助位操作符,仅用一行代码,保证了数组length在0之上,最大值范围之下,我们借baseSlice方法中的一段源码来学习一下。

/**
  * @param {Array} array The array to slice.
  * @param {number} [start=0] The start position.
  * @param {number} [end=array.length] The end position.
  * @returns {Array} Returns the slice of `array`.
*/
function baseSlice(array, start, end) {
    // ....省略不关键部分
    length = start > end ? 0 : ((end - start) >>> 0);
    start >>>= 0;
    var result = Array(length);
    // ....省略不关键部分
}

  baseSlice功能和目前Array自带的slice方法功能相同,截取数组startend部分,返回截取的新数组。截取的代码部分正在进行是根据startend的差值长度,生成新的数组对象,后面以便循环推入数据并返回结果。

  baseSlicelength根据startend的差值做了一个边界处理。当startend小时,直接判length为0;当endstart大时,取end - start的差,并做了一个>>>位运算符号,并且在后续,对start做了一个>>>=的操作处理。

  要想知道如此处理的原因,首先需要知道Array.length的边界规定,我们引用一下mdn上关于Array.length的定义。

length 是Array的实例属性。返回或设置一个数组中的元素个数。该值是一个无符号 32-bit 整数,并且总是大于数组最高项的下标。

  无符号 32-bit 整数意味着32-bit都可以用来进行数据的储存,而不需要匀第一位出来作为正负符号的标记。因此数组的长度范围应该在0 ~ Math.pow(2, 32) - 1长度之间。而在不知道传入endstart大小的情况下,length的长度实际上是有可能超出这个长度的。

  我们接着来看>>>操作的定义:

a >>> b将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)

      9 (base 10): 00000000000000000000000000001001 (base 2)
                   --------------------------------
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

  因此,baseSlice使用length >>> 0的方式保证了length的长度永远在32-bit的范围。即当数字大于2的32次方时候,>>>会崛弃所有大于32-bit的位数部分,即减去Math.pow(2, 32)。而小于范围的数字由于位移的是0则不受任何影响。之后对start也做了一个确保,是因为baseSlice需要截取start这一位到end为止的数组数据,start的数字必须也要确保在length的范围内。

调用优化

  在difference一系列方法源码的时候,lodash都使用baseRest引导使用的函数重新绑定了作用域到lodash_上。而在baseRest中,都统一调用了一个setToString方法,它能让传入的函数都拥有一个toString方法,调用能够直接看到传入函数的函数体,即看到该函数的代码。这在后续的一些需要传入函数的方法中方便使用者调试起到了非常重要的作用。

/**
  * The base implementation of `_.rest` which doesn't validate or coerce arguments.
  * @param {Function} func The function to apply a rest parameter to.
  * @param {number} [start=func.length-1] The start position of the rest parameter.
  * @returns {Function} Returns the new function.
  */
function baseRest(func, start) {
    return setToString(overRest(func, start, identity), func + '');
}
/**
  * Sets the `toString` method of `func` to return `string`.
  *
  * @private
  * @param {Function} func The function to modify.
  * @param {Function} string The `toString` result.
  * @returns {Function} Returns `func`.
  */
var setToString = shortOut(baseSetToString);

  但我重点关注的其实是shortOut这个函数的代码,很有意思,我们来看一下源码:

/** Used to detect hot functions by number of calls within a span of milliseconds. */
var HOT_COUNT = 800,
    HOT_SPAN = 16;

/**
  * Creates a function that'll short out and invoke `identity` instead
  * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
  * milliseconds.
  * @param {Function} func The function to restrict.
  * @returns {Function} Returns the new shortable function.
  */
function shortOut(func) {
    var count = 0,
    lastCalled = 0;

    return function() {
        // nativeNow 即 Date.now
        var stamp = nativeNow(),
            remaining = HOT_SPAN - (stamp - lastCalled);

        lastCalled = stamp;
        if (remaining > 0) {
          if (++count >= HOT_COUNT) {
            return arguments[0];
          }
        } else {
          count = 0;
        }
        return func.apply(undefined, arguments);
    };
}

  该方法实际上是使用了一个闭包包裹了一下传入的函数,记录下了函数调用次数count以及上次调用时间lastCalled。并针对这两个数值,对常用函数调用做了一个调用限制的优化。

  我们可以看到,在每次调用函数前,这个方法都会利用Date.now去记录一下当前调用的时间,并且和**上一次调动该函数时间(lastCalled)**进行一个比较。当这个差值大于HOT_SPAN(当前版本是16,即16ms)的时候,使用apply调用并清空调用次数(count)为0。当差值小于HOT_SPAN,即两次函数调用之间时间小于HOT_SPAN,而且调用次数大于HOT_COUNT(当前版本为800,即800次),就停止调用该函数,而是返回函数入参的第一项,根据注释,这第一项应该是一个函数的identity

  上面有提到过,在诸如setToString这样的报错机制处理时,使用了shortOut方法进行一个高阶函数的包装。setToString这个函数本身就是为了服务lodash的一些报错机制,让传入的函数都能拥有得到函数体代码的toString方法,这样可以保证在大批量数据处理的时候,根据不同的性能情况,进行不同的容错处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值