觀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 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM