Array.from的Polyfill都做了什么

Array.from的Polyfill都做了什么

今天在看MDN中关于Array.from方法Polyfill实现,发现其中还是有很多值得学习的地方。

if (!Array.from) {
  Array.from = (function () {
    /* 
    * 保存toString方法
    * 减少原型链查找消耗,另外保证代码的体面
    */ 
    var toStr = Object.prototype.toString;
    
    // 判断是否为一个方法
    var isCallable = function (fn) {
      /*
      * 为什么要用 toStr.call 来判断是否为 [object Function]
      * 无论 fn 是通过赋值还是直接定义,其自身的 toString 方法并不能够表示其类型
      * 因为 toString 方法是将自身的值通过字符串展示出来
      *
      * 赋值 
      * var fn = function() {} 
      * fn.toString() === 'function() {}'
      *
      * 定义 
      * function fn() {}  
      * fn.toString() === 'function fn() {}'
      */
      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
    };
    
    // 将值整数化
    var toInteger = function (value) {
      var number = Number(value);
      // 非数字返回 0
      if (isNaN(number)) { return 0; }
      /*
      * isFinite 方法用来判断数字是否是正(负无穷
      *   IEEE 754 标准规定: 只有大于等于1.7976931348623158 x 10^308 的数才会被 round 到 Infinity
      *
      *   1.7976931348623158e+308 * 1.000000000000001 的结果为 Infinity
      
      *   Javascript中的 Number.MAX_VALUE 距离这个数还差很远 
      *   Number.MIN_SAFE_INTEGER 更是小于 Number.MAX_VALUE
      *
      * 如果数字是 0 或者不为正(负)无穷,则返回自己
      */
      if (number === 0 || !isFinite(number)) { return number; }
      
      /*
      * Math.floor() 向下取整 Math.floor(4.6) => 4
      * Math.ceil()  向上取整 Math.ceil(4.1) => 5
      * Math.round() 四舍五入 Math.round(4.1) => 4, Math.round(4.5) => 5
      * 
      * 这里主要处理负数取整的问题,先保留符号,再乘以绝对值之后向下取整。
      * 可以认为是,当前这个处理方法遇到小数时,都是向 0 取整。
      */
      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
    };
    
    // 当然作为Polyfill,Number.MIN_SAFE_INTEGER 这个静态常量也是不存在的
    // 手动计算一下。
    var maxSafeInteger = Math.pow(2, 53) - 1;
    
    // 拿到一个大于等于 0 的值
    var toLength = function (value) {
      var len = toInteger(value);
      return Math.min(Math.max(len, 0), maxSafeInteger);
    };
    
    // 数据类型Set的实现
    var toItems = function (value) {
      // support set
      if (value.size > 0 && value.values) {
        let values = value.values()
        var it = values.next()
        var o = []
        while (!it.done) {
          o.push(it.value)
          it = values.next()
        }
        return o
      }
      return Object(value);
    };
    
    // The length property of the from method is 1.
    return function from(arrayLike/*, mapFn, thisArg */) {
      // 1. Let C be the this value.
      var C = this;

      // 2. Let items be ToObject(arrayLike).
      var items = toItems(arrayLike);

      // 3. ReturnIfAbrupt(items).
      if (arrayLike == null) {
        throw new TypeError("Array.from requires an array-like object - not null or undefined");
      }

      // 4. If mapfn is undefined, then let mapping be false.
      /*
      * void expression 最后都会获得一个undefined的返回
      */
      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
      var T;
      if (typeof mapFn !== 'undefined') {
        // 5. else      
        // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
        if (!isCallable(mapFn)) {
          throw new TypeError('Array.from: when provided, the second argument must be a function');
        }

        // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
        if (arguments.length > 2) {
          T = arguments[2];
        }
      }

      // 10. Let lenValue be Get(items, "length").
      // 11. Let len be ToLength(lenValue).
      var len = toLength(items.length);

      // 13. If IsConstructor(C) is true, then
      // 13. a. Let A be the result of calling the [[Construct]] internal method 
      // of C with an argument list containing the single item len.
      // 14. a. Else, Let A be ArrayCreate(len).
      var A = isCallable(C) ? Object(new C(len)) : new Array(len);

      // 16. Let k be 0.
      var k = 0;
      // 17. Repeat, while k < len… (also steps a - h)
      var kValue;
      while (k < len) {
        kValue = items[k];
        
        // 实现mapFn
        if (mapFn) {
          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
        } else {
          A[k] = kValue;
        }
        k += 1;
      }
      // 18. Let putStatus be Put(A, "length", len, true).
      A.length = len;
      // 20. Return A.
      return A;
    };
  }());
}

Detail

1. 关于Object.prototype.toStirng()

Object作为Javascript万物之源,几乎所有的对象都继承自Object,如果在继承时原型上的toString方法不被覆盖掉,它会默认的打印出 [object type],type为当前数据的类型。

但是一些继承与Object的基础类型会将toString方法覆盖,且不继承。

比如:

  • String

    String.prototype.toString()的返回值和String.prototype.valueOf()的一样,都是返回对象的字符串形式。

  • Number

    Numer.prototype.toString()接受一个参数,并且按照参数返回指定基数的字符串表示。

  • Array

    Array.prototype.toString()返回以逗号分隔,连接好的数组。

2. 取整

数字的向0取整是个基本的策略,就是想绝对值较小的那个方向取整。

但是值得思考的是,为什么要使用向0取整这个策略。难道是因为正数向上,负数向下取整可能会超过精度的最大值?

Reference

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/string/toString

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/number/toString

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/array/toString

https://www.zhihu.com/question/24423421

你可能感兴趣的:(Array.from的Polyfill都做了什么)