数组越界:与undefined进行比较,还是检查长度?

30

这似乎是一种常见的JavaScript用法:

function foo (array, index) {
    if (typeof array[index] == 'undefined')
        alert ('out of bounds baby');
}

与其他语言中更普遍且概念上更简单的相对应:

function foo (array, index) {
    if (index >= array.length)
        alert ('boo');
}

我理解第一种情况也可以适用于数组中存在“间隙”的情况,但这种情况是否常见到足以使用该习惯用法呢?

触发此问题的代码示例可以在此处查看:here。在这种情况下,在函数内部使用'argument'变量时,假设它是一个连续的数组是否是合理的呢?


我认为您误解了链接代码。replace函数可以随机访问参数数组(例如字符串"{1} {3} {5} {2}"),这可能会对传递给它的参数产生有趣的影响。我的观点是,那里的检查不是为了检查数组的边界,而是为了检查存在性并在未定义命名属性时提供回退。99%的情况下,您要检查length属性,链接的代码是少数几次不需要检查的情况之一。 - Nick Husher
另外,测试应该是if (index < array.length),因为长度始终比最后一个索引多一个,并且array[array.length]总是未定义的。 - RobG
1
作为练习,请尝试执行以下代码:var arr = []; arr[5] = 2;。在执行第一个循环时,您将立即收到索引0的越界消息,但是arr.length将正确告诉您arr有6个元素。 - c1moore
6个回答

37

唯一的正确方法是检查索引与长度。

一个元素可能被赋值为undefined。在这里使用它作为哨兵只是愚蠢。(可能有其他有效且可能重叠的原因来检查未定义,但不是“越界检查”——当给定参数的值真正undefined时,其他问题中的代码将呈现出明显错误的结果。)

编码愉快。


23

你也可以写成:

if (index in array) {

即使 array[index] 被设置为 undefined,它也会返回 true。


12
这种方法并不总是有效,尽管我相信它在仅仅“类似数组”的arguments中总是有效。因为在JavaScript中的数组是“稀疏”的,所以for (v in array) { ... }有时会出现问题。(有一个区别在于是否有一个“未设置”的索引,这种情况无法使用该方法,而只有一个undefined值,可以使用该方法)。例如,考虑到var a = []; a[1] = "foo"; 0 in a的结果是false。数组a具有自身属性“1”和“length”,但它没有名为“0”的属性。 - user166390

1

不要测试未定义。你应该使用数组的长度。有些情况下,测试未定义根本行不通,因为未定义是合法的数组条目值。这是一个合法的JS数组:

var legalArray = [4, undefined, "foo"];

你可以像这样访问它:

var legalArray = [4, undefined, "foo"];

var result = "";
for (var i = 0; i < legalArray.length; i++) {
    result += legalArray[i] + "<br>";
}

$("#result").html(result);

产生此输出:

4
undefined
foo

在这个 jsFiddle 中可以看到:http://jsfiddle.net/Jfriend00/J5PPe/

0

据我所知,这并不常见,更为普遍的是:

for (var i=0, iLen=array.length; i<iLen; i++) {
  // do stuff
}

不应该与未定义的数值进行比较,因为数组中的成员可能被赋值为 undefined ,或者根本没有被赋任何值。

例如:

var a = [0,,,,];

alert(a.length); // 4 (or 5 in buggy IE);

a[1] === undefined; // true but not out of bounds

使用for循环的主要原因是,如果使用for..in循环,则可能无法按数字顺序返回数组属性。
对于稀疏数组,for..in循环更有效率,但如果顺序很重要(继承和非数字可枚举属性也必须避免),则必须处理可能的乱序访问。

0
在JavaScript中,数组可以是稀疏的 - 它们可以包含“空洞”。例如:
const array = new Array(3);

结果是一个包含三个“空洞”(而非值)的数组。因此,

const isInBounds = 0 <= index && index < array.length;

正确判断index是否在array的范围内,但并不表示array[index]上是否有值。

可以使用Object.prototype.hasOwnProperty()来确定index上是否存在值。需要注意的是,语言的不同部分在存在"空洞"时可能会有不同的行为。

// ESLint: no-prototype-builtins)
const hasOwnProperty = Object.prototype.hasOwnProperty;

function show(array, index) {
  const msg =
    0 > index || index >= array.length
      ? `index ${index} is out of bounds`
      : !hasOwnProperty.call(array, index)
      ? `index ${index} is a hole`
      : `index ${index} holds ${array[index]}`;

  console.log(msg);
}

const subject = [undefined, , 1];

show(subject, -1);
// "index -1 is out of bounds"

for (let i = 0; i < subject.length; i += 1) show(subject, i);
// "index 0 holds undefined"
// "index 1 is a hole"
// "index 2 holds 1"

show(subject, 3);
// "index 3 is out of bounds"

const toString = (value) =>
  value !== undefined ? value.toString() : 'undefined';

// for..of doesn't skip holes
const byForOf = [];
for (const value of subject) byForOf.push(toString(value));
console.log(`Values found by for..of: ${byForOf.join(', ')}`);
// "Values found by for..of: undefined, undefined, 1"

// .forEach skips holes
const byForEach = [];
subject.forEach((value) => byForEach.push(toString(value)));
console.log(`Values found by .forEach: ${byForEach.join(', ')}`);
// "Values found by .forEach: undefined, 1"

// .reduce skips holes
const reducer = (acc, value) => {
  acc.push(toString(value));
  return acc;
};
const byReduce = subject.reduce(reducer, []);
console.log(`Values found by .reduce: ${byReduce.join(', ')}`);
// "Values found by .reduce: undefined, 1"

// .map preserves holes
const byMap = subject.map(toString);
console.log(`Values found by .map: ${byMap.join(', ')}`);
// "Values found by .map: undefined, , 1"

-2
在这种情况下,测试的目的是确保它不会意外地将字符串“undefined”添加到调用字符串中。在下面的情况下,它实际上会这样做:
var undefd;
"{0} is dead, but {1} is alive! {0} {2}".format("ASP", undefd, "ASP.NET")
// output as "ASP is dead, but {1} is alive! ASP ASP.NET"

个人而言,我可能会简单地缓存长度,然后进行数字比较。

编辑

顺便说一下:他的方法也避免了NaN检查,但强制执行严格的并行:

// this will fail unless 0001 is cast to a number, which means the method
// provided will fail. 
"{0} is dead, but {1} is alive! {0001} {2}".format("ASP", "ASP.NET")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接