翻译:道奇
作者:Dmitri Pavlutin
原文:The Magic Behind Array Length Property
开发人员每天都会处理数组,作为一个集合,它有一个很重要的用于查询的属性是项(item)的数量:Array.prototype.length。
在JavaScript中,length并不总是指的现有元素(对于稀疏矩阵)的数量,修改属性也可能会移除元素。
下面让我们揭开这个属性背后魔法的面纱。
"数组
length是一个无符号的32位整数,其数值大于数组中索引的最大值"
这个属性根据所指定的数组类型不同行为也不同,下面列举他们:当数组的元素的索引是连续的且从0开始,数组就是密集(dense)的,例如[1, 3, 4]是密集的,因为索引是连续的:0, 1和2。当数组的元素不是从0开始的连续索引,那么它就是稀疏(sparse)的,例如[1, ,4, 6]是稀疏的,因为元素的索引是不连续的:0, 2和3。
Length作为数组元素的数量
length常用的就是确定元素的数量,这在密集的集合类型下是正确的:
var fruits = ['orange', 'apple', 'banana']; //fruits密集数组
fruits.length // 打印3, 实际的元素数量
fruits.push('mango');
fruits.length // 打印4, 添加了一个元素
var empty = [];
empty.length // 打印 0, 空数组
可以在JS Bin中看这个例子
密集数组没有空槽的数组元素,元素的数量相当于最大索引值 + 1,在[3, 5, 7, 8]中最大的索引是元素8的索引3,因此数组的大小是3 + 1 = 4。
Length作为大于最大索引的数值
在稀疏数组中,length比最大索引大,但是它并不代表真实的元素数量。当查询length,它比元素的总数大。这是因为数组中存在空槽位置的原因。
var animals = ['cat', 'dog', , 'monkey']; // animals是稀疏的
animals.length // 打印 4,但是实际数量是3
var words = ['hello'];
words[6] = 'welcome'; //最大的索引是6,words是稀疏数组
words.length //打印 7, 基于最大索引
当添加或移除元素时,length仅会根据最大索引进行改变,任何不会影响数组最大索引的修改都不会影响length,例如,使用delete。
var colors = ['blue', 'red', 'yellow', 'white', 'black'];
colors.length // 打印5
delete colors[0]; // 移除第一个元素 'blue'
// 数组变成稀疏数组
colors.length // 依然打印5,因为最高的索引是4 ,没有发生改变
可以在JS Bin中看这个例子
Length修改
在上述的分析中,length是只读的。但是JavaScript允许修改这个属性,length的修改会怎样影响数组取决于新的值和现有的最大的索引,这个操作也可以移除元素或使数组变成稀疏数组。当新的length值小于或等于最大索引时,任何元素,如果它的索引大于或等于新组大小就会被移除,这种做法的一个有用的场景就是移除数组的最后一个元素。
var numbers = [1, 3, 5, 7, 8];
numbers.length = 3; // 修改数组length
numbers // 打印 [1, 3, 5], 元素7和8被移除
如果使用大于最大索引(或使用大于当前length的数值)的数值,数组就会变成稀疏的,这个用处不大。
var osTypes = ['OS X', 'Linux', 'Windows'];
osTypes.length = 5; // 创建一个稀疏数组,索引索引3和4的元素不存在
osTypes // 打印 ['OS X', 'Linux', 'Windows', undefined,undefined ]
可以在JS Bin中看这个例子
给length指定数字之外的类型也是可以的,JavaScript会将这个原始值转换为数字,如果转换的结果是NAN或是小于0的数字,就会抛出没有捕捉的范围异常:元素数组长度
var numbers = [1, 4, 6, 7];
numbers.length = '2'; // '2'被转换为数字 2
numbers.length = 'not-number'; // 抛出没有捕捉的范围异常:元素数组长度
numbers.length = -2; // 抛出没有捕捉的范围异常:元素数组长度
代码安全
修改数组length,通过delete移除元素,使用[新索引]添加元素都是通过创建稀疏数组而产生潜在问题的根源。结果是length值不一致,JavaScript提供了更安全的替代方案。
使用Array.prototype.push()向数组末尾添加元素,通过pop()移除最后一个元素
,使用unshift()向数组起始端插入元素和使用shift()移除第一个元素。对于更复杂的插入、删除或替换,splice()是足够强大的。
var companies = ['Apple', 'Dell'];
companies.push('ASUS'); // 向末尾添加元素
companies // 打印 ['Apple', 'Dell', 'ASUS']
companies.pop(); // 打印 "ASUS". 移除最后一个元素
companies // 打印 ['Apple', 'Dell']
companies.shift(); // 打印 "Apple". 移除第一个元素
companies // 打印 ["Dell"]
companies.splice(1, 0, "Microsoft", "HP"); // 添加两空公司
companies // 打印 ["Dell", "Microsoft", "HP"]
companies.length // 打印 3. 数组是密集的
可以在JS Bin中看这个例子
在极少数情况下,数组可能是稀疏的。靠length决定元素的数量是不安全的,可以使用一个帮助函数来处理缺少的元素:
/**
* 计算一个稀疏数组的元素数量
* @param {Array} collection
* @return {number}
*/
function count(collection) {
var totalCount = 0;
for (var index = 0; index < collection.length; index++) {
if (index in collection) {
totalCount++;
}
}
return totalCount;
}
in运算符可以用于判断这个对象是否有属性,它在检查元素在指定的索引下是否存在是非常有用的。
总结
就像在这篇文章中看到的,length是一个具有复杂行为的属性。
大部分情况下,都能正常工作,但是当处理稀疏数组并修改length时最好小心一点。
另一种方法是完全避免修改此属性并使用splice()方法。