從JavaScript中的類陣列物件說起

liuyawin000發表於2018-10-12

1 類陣列物件

JavaScript中有一種資料型別叫做類陣列物件。它是一種神奇的資料型別,似陣列而非陣列,有很多與陣列類似的用法,但是卻不能使用陣列的一些方法,比如最常見的arguments物件:

function test(){
	console.log(arguments.length);//3
	console.log(arguments[0]);//0
	
	arguments.length = 1;
	console.log(arguments[1]);//1
	console.log(arguments.slice(0, 1));//arguments.slice is not a function
}

test(0, 1, 2)
複製程式碼

從上面的例子可以看出,類陣列物件也有一個length屬性,也可以通過中括號加索引的形式訪問其“元素”。與陣列不同的是,改變length屬性並不能真正改變類陣列物件的“長度”,而且類陣列物件並不具有陣列所有的方法。
那麼它到底是個什麼樣的存在呢?
從名字得知,類陣列物件首先是一個物件,它有一些與陣列類似的特徵:

  • 它有一個數值型別的length屬性
  • 它有與length對應的非負整數屬性
    可以建立如下的類陣列物件:
var arrLikeObj = {
    length: 2,
    0: 'a',
    1: 'b'
}
複製程式碼

這樣,就可以很容易解釋上面類陣列物件的特徵了。
常見的類陣列物件有arguments物件、通過Node.childNodes得到的NodeList物件、通過document.getElementsBy...得到的HTMLCollection物件、element.attributes得到的NamedNodeMap物件等。

2 類陣列物件轉化為陣列

因為類陣列物件只是一個普通的物件,而不是陣列,因此我們不能直接在它上面呼叫一些陣列特有的方法。雖然可以通過call來應用陣列的方法,如:

var arrLikeObj = {
    length: 4,
    0: 'a',
    2: 'c',
    1: 'b',
    3: 'd'
}
Array.prototype.sort.call(arrLikeObj);

//返回{0: "a", 1: "b", 2: "c", 3: "d", length: 4}
複製程式碼

但是很多時候我們還是需要將類陣列物件轉化為陣列。下面提供三種方法。

2.1 Array.prototype.slice.call(arrLikeObj)

var arrLikeObj = {
    length: 4,
    0: 'a',
    1: 'b',
    2: 'c',
    3: 'd'
}

var arr = Array.prototype.slice.call(arrLikeObj);

//arr為["a", "b", "c", "d"]
複製程式碼

使用陣列物件的slice方法可以將類陣列物件轉化為陣列物件。slice方法用於複製陣列的一部分,為什麼它會對類陣列物件產生這樣的效果呢?為了探尋其原因,我們可以看一看slice方法實現的原始碼:

// This will work for genuine arrays, array-like objects, 
// NamedNodeMap (attributes, entities, notations),
// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
// and will not fail on other DOM objects (as do DOM elements in IE < 9)
Array.prototype.slice = function(begin, end) {
  // IE < 9 gets unhappy with an undefined end argument
  end = (typeof end !== 'undefined') ? end : this.length;

  // For native Array objects, we use the native slice function
  if (Object.prototype.toString.call(this) === '[object Array]'){
    return _slice.call(this, begin, end); 
  }

  // For array like object we handle it ourselves.
  var i, cloned = [],
    size, len = this.length;

  // Handle negative value for "begin"
  var start = begin || 0;
  start = (start >= 0) ? start : Math.max(0, len + start);

  // Handle negative value for "end"
  var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
  if (end < 0) {
    upTo = len + end;
  }

  // Actual expected size of the slice
  size = upTo - start;

  if (size > 0) {
    cloned = new Array(size);
    if (this.charAt) {
      for (i = 0; i < size; i++) {
        cloned[i] = this.charAt(start + i);
      }
    } else {
      for (i = 0; i < size; i++) {
        cloned[i] = this[start + i];
      }
    }
  }

  return cloned;
};
複製程式碼

程式碼邏輯並不複雜,對於真正的陣列,會呼叫一個native function,而對於類陣列物件,則會走下面的邏輯,返回一個真正的陣列物件(這裡參考了這篇部落格)。

2.2 Array.from()

ES6提供了Array.from方法,用於將部署了Iterator介面的物件(可遍歷物件,如Set、Map、String等)和類陣列物件轉化為陣列。

var arrLikeObj = {
    length: 4,
    0: 'a',
    1: 'b',
    2: 'c',
    3: 'd'
}

var arr = Array.from(arrLikeObj);

//arr為["a", "b", "c", "d"]
複製程式碼

2.3 擴充套件運算子...

ES6提供的擴充套件運算子...可以將可遍歷物件轉化為由逗號分隔的序列。

[...'abcd'] //["a", "b", "c", "d"]
複製程式碼

js提供的一些原生類陣列物件(如:arguments物件、NodeList物件、HTMLCollection物件等)是部署了Iterator介面的(即是可遍歷的),因此也可以用這個方法來進行轉換。

function test(){
	console.log([...arguments]);//[0, 1, 2]
}
test(0,1,2)
複製程式碼

注意,這個方法只對部署了Iterator介面的類陣列物件有效。

總結

這篇文章主要介紹了類陣列物件以及將類陣列物件轉化為陣列物件的三種方法。如果有不對的地方,歡迎指正。

相關文章