• 观V8源码中的array.js,解析 Array.prototype.slice为什么能将类数组对象转为真正的数组?


    在官方的解释中,如[mdn]

         The slice() method returns a shallow copy of a portion of an array into a new array object.

       

    简单的说就是根据参数,返回数组的一部分的copy。所以了解其内部实现才能确定它是如何工作的。所以查看V8源码中的Array.js     可以看到如下的代码:


    一、方法  ArraySlice,源码地址,直接添加到Array.prototype上的“入口”,内部经过参数、类型等等的判断处理,分支为SmartSlice和SimpleSlice处理。

    function ArraySlice(start, end) { 
      CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice"); 
      var len = TO_UINT32(this.length); 
      var start_i = TO_INTEGER(start); 
      var end_i = len; 
    
      if (!IS_UNDEFINED(end)) end_i = TO_INTEGER(end);//如果没传入end,end=length,即slice第二个参数可选。 
    
      if (start_i < 0) { 
        start_i += len;//参数1的A分支 处理负值,+= length。如:为-1,则start从倒数第一个开始,负值绝对值小于length 
        if (start_i < 0) start_i = 0;//参数1的A.a分支 若仍未负值,则等于0。 即处理负值绝对值大于length [1,2,3].slice(-4)。 
      } else { 
        if (start_i > len) start_i = len;//参数1的B分支 参数大于length,则等于length,处理 [1,2,3].slice(5),返回[] 
      } 
    
      if (end_i < 0) { 
        end_i += len;//参数2的A分支 处理负值,+= length。如:为-1,则start从倒数第一个开始,负值绝对值小于length 
        if (end_i < 0) end_i = 0;//参数2的A.a分支 若仍未负值,则等于0。 即处理负值绝对值大于length [1,2,3].slice(1,-4)。 
      } else { 
        if (end_i > len) end_i = len;//参数2的B分支 参数大于length,则等于length,处理 [1,2,3].slice(1,5) == [1,2,3].slice(1) == 
      } 
        //最终返回结果的值。可以看到这里会返回一个新的真正的数组(ps:slice的好基友splice是修改原数组的。) 
      var result = []; 
      // 处理分支1   如果经历了上面代码的层层检查设置,结束值小于开始值,那么直接返回空数组,处理 [1,2,3].slice(2,1) 
      if (end_i < start_i) return result; 
      // 处理分支2 如果是数组 && !%IsObserved(this) && 结束大于1000 && %EstimateNumberOfElements(this) < 结束值 ,那么使用方法SmartSlice来处理 
      if (IS_ARRAY(this) && 
          !%IsObserved(this) && 
          (end_i > 1000) && 
          (%EstimateNumberOfElements(this) < end_i)) { 
        SmartSlice(this, start_i, end_i - start_i, len, result); 
      } else { 
        // 处理分支2 调用SimpleSlice 处理。 
        SimpleSlice(this, start_i, end_i - start_i, len, result); 
      } 
      //设置length,似乎多余?还是v8中的数组[] 需指定length。  此处待探寻。。。 
      result.length = end_i - start_i; 
    
      return result; 
    } 
    /* 
    * ...... 
    */ 
    // Set up non-enumerable functions of the Array.prototype object and 
      // set their names. 
      // Manipulate the length of some of the functions to meet 
      // expectations set by ECMA-262 or Mozilla. 
      InstallFunctions($Array.prototype, DONT_ENUM, $Array( 
        //...... 
        "slice", getFunction("slice", ArraySlice, 2) 
        //...... 
      ));

    二、  SmartSlice,源码地址,字面意思是智能的slice。SimpleSlice,源码地址,简单的slice,不管他们的判断逻辑,可以看到,所有的slice处理,都是for循环,操作新建的result空数组的。也就是说,正因为返回值是新建的真实的数组,所有Array.prototype.slice.call(ArrayLike) 才会将类数组转化为真实的数组。

     1 // This function implements the optimized splice implementation that can use
     2 // special array operations to handle sparse arrays in a sensible fashion.
     3 /**
     4  * 源码:https://github.com/v8/v8/blob/master/src/array.js#L196-L221
     5  * @param {Array} array 具体需要艹做的数组
     6  * @param {Number} start_i 参数1,从何处开始
     7  * @param {Number} del_count 需要取到的长度。 参数2 - 参数1,
     8  * @param {Number} len 数组长度
     9  * @param {Array} deleted_elements 对于slice来说,是选择的那部分数组,对于splice来说,是删除的那些数组。
    10  * @returns {undefined}  此处直接艹做 传入的reuslt,即可反馈到ArraySlice作用域的result,与真实的浏览器环境不一样!。
    11  */
    12 function SmartSlice(array, start_i, del_count, len, deleted_elements) {
    13   // Move deleted elements to a new array (the return value from splice).
    14   // 猜测? 获取start_i + del_count的key。[1,2,3,4].slice(1,2) 返回 [1,2,3,4][1+2]索引3  ,而当tart_i + del_count大于length时候返回整个数组,如[1,2,3,4].slice(2,3) 即[1,2,3,4][5] 返回整个数组
    15   var indices = %GetArrayKeys(array, start_i + del_count);
    16   if (IS_NUMBER(indices)) {
    17     var limit = indices;
    18     for (var i = start_i; i < limit; ++i) {
    19       var current = array[i];
    20       if (!IS_UNDEFINED(current) || i in array) {
    21         deleted_elements[i - start_i] = current;
    22       }
    23     }
    24   } else {
    25     var length = indices.length;
    26     for (var k = 0; k < length; ++k) {
    27       var key = indices[k];
    28       if (!IS_UNDEFINED(key)) {
    29         if (key >= start_i) {
    30           var current = array[key];
    31           if (!IS_UNDEFINED(current) || key in array) {
    32             deleted_elements[key - start_i] = current;
    33           }
    34         }
    35       }
    36     }
    37   }
    38 }
    39 
    40 
    41 // This is part of the old simple-minded splice.  We are using it either
    42 // because the receiver is not an array (so we have no choice) or because we
    43 // know we are not deleting or moving a lot of elements.
    44 /**
    45  * 源码:https://github.com/v8/v8/blob/master/src/array.js#L271-L282
    46  * @param {Array} array 具体需要艹做的数组
    47  * @param {Number} start_i 参数1,从何处开始
    48  * @param {Number} del_count 需要取到的长度。 参数2 - 参数1,
    49  * @param {Number} len 数组长度
    50  * @param {Array} deleted_elements 对于slice来说,是选择的那部分数组,对于splice来说,是删除的那些数组。
    51  * @returns {undefined}  此处直接艹做 传入的reuslt,即可反馈到ArraySlice作用域的result,与真实的浏览器环境不一样!。
    52  */
    53 function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
    54   for (var i = 0; i < del_count; i++) {
    55     var index = start_i + i;
    56     // The spec could also be interpreted such that %HasLocalProperty
    57     // would be the appropriate test.  We follow KJS in consulting the
    58     // prototype.
    59     var current = array[index];
    60     if (!IS_UNDEFINED(current) || index in array) {
    61       deleted_elements[i] = current;
    62     }
    63   }
    64 }




    三、 既然了解了实现思路,我们可以写个自己的slice方法,来实现slice的功能,不难看出。“slice.call的作用原理就是,利用call,将slice的方法作用于arrayLikeslice的两个参数为空,slice内部解析使得arguments.lengt等于0的时候 相当于处理 slice(0) : 即选择整个数组,slice方法内部没有强制判断必须是Array类型,slice返回的是新建的数组(使用循环取值)”,所以这样就实现了类数组到数组的转化,call这个神奇的方法、slice的处理缺一不可,花几分钟实现模拟slice如下:

          ps: ie低版本,无法处理dom集合的slice call转数组。(虽然具有数值键值、length 符合ArrayLike的定义,却报错)搜索资料得到?(此处待确认): 因为ie下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换 

    (function(global, undefined) {
        'use strict';
        function SimpleSlice(array, start_i, del_count, len) {
            var deleted_elements = [];
            for (var i = 0; i < del_count; i++) {
                var index = start_i + i;
                var current = array[index];
                if (current !== void(0) || index in array) {
                    deleted_elements[i] = current;
                }
            }
            return deleted_elements;
        }
        Array.prototype.mySlice = function(start_i, end_i) {
            var len = this.length;
            start_i = start_i === undefined ? 0 : start_i - 0;
            end_i = end_i === undefined ? len : end_i - 0;
            if (start_i < 0) {
                start_i = Math.max(start_i + len, 0);
            } else if (start_i > len) {
                start_i = len;
            }
    
            if (end_i < 0) {
                end_i = Math.max(end_i + len, 0);
            } else if (end_i > len) {
                end_i = len;
            }
            if (end_i < start_i)
                return [];
            return SimpleSlice(this, start_i, end_i - start_i, len);
        }
    })(this);
    var arr = [1,2,3,4,5,6,7,8,9,10];
    console.log('test ',arr)
    console.log(arr.slice(2),arr.mySlice(2))
    console.log(arr.slice(6,7),arr.mySlice(6,7))
    console.log(arr.slice(-4),arr.mySlice(-4))
    console.log(arr.slice(-4,-2),arr.mySlice(-4,-2));
    
    (function(){
        console.log('slice call arguments : ',Array.prototype.slice.call(arguments));
        console.log('mySlice call arguments : ',Array.prototype.mySlice.call(arguments));
    })([],'String',false);
    
    console.log(Array.prototype.slice.call({0:'a',length:1}),Array.prototype.mySlice.call({0:'a',length:1}));
    在控制台输出如下:
    111 
  • 相关阅读:
    python 面向对象编程的三大特征之一 多态
    python 访问控制
    python 面向对象编程的三大特征之一 继承
    朱兆祺教你如何攻破C语言学习、笔试与机试的难点
    如何画好流程图
    2013年个人计划
    30天敏捷结果(1):总体认识Getting Result敏捷方法
    每天一个linux命令目录
    国嵌C语言学习
    【head first python】1.初识python 人人都爱列表
  • 原文地址:https://www.cnblogs.com/henryli/p/3700945.html
Copyright © 2020-2023  润新知