zoukankan      html  css  js  c++  java
  • 关于Array.prototype.map 的 polyfill 函数中使用>>>的疑问,以及改进方法?

    Polyfill

    在 MDN 网站上关于数组的 map 方法在低版本浏览器上使用一个垫片函数,地址:
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
    这个垫片函数的实现如下:

    if (!Array.prototype.map) {
    	Array.prototype.map = function (callback) {
            var T, A, k;
            if (this == null) {
              throw new TypeError('this is null or not defined');
            } 
            var O = Object(this); 
            var len = O.length >>> 0; 
            if (typeof callback !== 'function') {
              throw new TypeError(callback + ' is not a function');
            } 
            if (arguments.length > 1) {
              T = arguments[1];
            }
            A = new Array(len);
            k = 0;
            while (k < len) {
              var kValue, mappedValue; 
              if (k in O) {
                kValue = O[k];
                mappedValue = callback.call(T, kValue, k, O); 
                A[k] = mappedValue;
              } 
              k++;
            }
            return A;
        };
    };
    

    在上述函数中对调用对象进行了无符号的左移操作,也就是:

    O.length >>> 0
    

    Problem

    那这么做的意义是什么呢?解决了什么问题?会不会带来什么问题?

    先说我对于此的理解:左移0个操作数位并不是没有意义的。首先,这保证数组的 length 是一个非负整数,其次保证了 length 对新数组是可用的。至于为什么,我会在接下来的篇幅中阐述。其二是会不会有问题?我认为这么做是不好的,是有问题的,最起码在此 polyfill 中是不完备的。

    Why

    >>> 这个操作符是无符号右移位操作符,MDN上有详细的介绍:
    [https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift]。
    但是要注意它并不是把一个数实际在内存中存储二进制码进行操作,而是以计算机转换整数的二进制码规则转换后进行操作。什么意思呢?我们知道,JavaScript 是动态类型语言,和 Java 、C 等静态类型语言不同,他把整数和浮点数都按 Number 类型处理,遵循 IEEE 754 国际标准,实际上就是将所有的数都按双精度浮点数的规则进行存储,简书上的这篇文章算是讲的简单易懂了:[https://www.jianshu.com/p/ab2bc4d7e001]。>>> 并不是操作这种64位的二进制数,而是32位的二进制整数。《计算机原理》书上说的很清楚,计算机方便运算,将整数按源码-->反码-->补码的方式转换成二进制进行计算,在32位二进制中最高位为符号位,0表示正数,1表示负数,只有负数才会取反码和补码。位操作符就是按这个逻辑先将整数转换成二进制数(如果不是整数,则只取整数部分,舍去小数),再进行左移(<<)还是右移(>>)或者加上符号位变动(>>>),然后再将变换后的二进制数按相应规则转换成整数。

    综上,>>> 操作符有两个特点:

    1. 结果为非负整数
    2. 结果的范围为 0 ~ 20+21+22...+231,也就是 0 ~ 4294967295

    其实这个范围正好也是数组的 length 属性的取值范围,虽然在 JS 中数组的长度是动态增加的,但也并不是没有上线,如果length > 4294967295 ,数组的索引就不会再增加了,当然数组还可以添加属性,但是不能在索引(index)上添加元素。你可以做如下操作试一下:

    var arr = new Array(4294967295);
    arr.push(1); //Uncaught RangeError: Invalid array length
    

    当然,直到这里是不是认为 O.length >>> 0 多此一举?答案是否定的。因为 JS 中伪数组的存在。

    比方说,你定义一个对象:

    var obj = {
        0:'a',
        1:'b',
        length:2
    }
    

    你可以通过这种方法将其转换成数组:

    Array.prototype.map.call(obj,x=>x);
    

    现在,不管那些数组或者伪数组的定义,设置 length 为无意义的值:

    obj.length = -2;
    

    或者;

    obj.length = 4294967296;
    

    那么:

    var len = O.length >>> 0;
    

    至少可以保证 len 为一个可用的值。

    如果你将 obj.length 改为一个非 Number 类型的值,比如:

    obj.length = '10';
    var len = obj.length;
    

    以此创建一个新数组,长度为10,得到的并不是理想的结果:

    var A = new Array(len);
    //output: A:["10"]{0:"10",length:1}
    

    而位移操作符始终得到的都是一个整数,不管对谁操作。

    虽然一个操作符解决了所有问题,少写了好几行代码,但是我认为这样处理并不好。

    还是用上面的特殊情况的例子:

    obj.length = -2 >>>0;
    //output:4294967294
    

    那么在 map 函数中循环就有42亿次以上,我在 node 环境下运行了15分钟,其二:

    obj.length = 4294967297 >>> 0;
    //output:1
    

    那么我将 obj 转换成数组只有 key 为 0 的属性会存入数组中,其他的值就会丢失。其三:

    obj.length = "a" >>>0;
    //output:0
    

    我在编码中误把 length 值的类型变成了字符串类型,那么我在数组转换时,得到的是一个空数组,而且我得不到程序的反馈,错误在哪里。

    注:之前没有想到的,位操作符这里应该也会做隐式转换。在位操作之前应该会把被操作的对象转换成数字

    '10'>>>0;		//output:10
    '1e2'>>>0;		//output:100
    

    Resolution

    既然是“Invalid array length”,一种解决方式是把他们都抛出 Uncaught RangeError;

    var O = Object(this); 
    var len = O.length;
    if(typeof(len)!=='number'||len<0||len>2**32-1){
        throw RangeError("Invalid array length");
    }
    

    但是这样做不精细,我在浏览器中试了一下 Array 中的 map,当 length 不能转换成 Number 或者小于0,则会返回空数组,超过数组最大长度会抛出错误:

    var len = Number(O.length) && Number(O.length) > 0 ? Number(O.length) : 0;
    

    这样就和原生的 map 函数输出结果是一样的了,完整代码如下:

    Array.prototype.myMap = function (callback) {
        var T, A, k;
        if (this == null) {
          throw new TypeError('this is null or not defined');
        } 
        var O = Object(this); 
        var len = Number(O.length) && Number(O.length) > 0 ? Number(O.length) : 0;
        if (typeof callback !== 'function') {
          throw new TypeError(callback + ' is not a function');
        } 
        if (arguments.length > 1) {
          T = arguments[1];
        }
        A = new Array(len);
        k = 0;
        while (k < len) {
          var kValue, mappedValue; 
          if (k in O) {
            kValue = O[k];
            mappedValue = callback.call(T, kValue, k, O); 
            A[k] = mappedValue;
          } 
          k++;
        }
        return A;
    };
    
  • 相关阅读:
    python中logging的使用
    从零到Django大牛的的进阶之路02
    PostgreSQL 输出 JSON 结果
    Hello World
    Node多国语言包
    更改ejs模板引擎的后缀为html
    Node.js 调试小技巧
    JavsScript 一些技巧方法
    如何预测 Pinterest 和 Instagram 的未来发展潜力?
    如何获得div对象的绝对坐标
  • 原文地址:https://www.cnblogs.com/arduka/p/13582734.html
Copyright © 2011-2022 走看看