Các chỉ mục phủ định trong mảng JavaScript có nên đóng góp vào độ dài của mảng không?


84

Trong javascript, tôi xác định một mảng như thế này

var arr = [1,2,3];

tôi cũng có thể làm

arr[-1] = 4;

Bây giờ nếu tôi làm

arr = undefined;

Tôi cũng mất tham chiếu đến giá trị tại arr [-1] .

VẬY đối với tôi về mặt logic thì có vẻ như arr [-1] cũng là một phần của arr .

Nhưng khi tôi làm theo (không đặt arr thành không xác định)

arr.length;

Nó trả về 3 không phải 4 ;

Vì vậy, quan điểm của tôi là nếu các mảng có thể được sử dụng với các chỉ mục âm, thì các chỉ mục âm này cũng phải là một phần của độ dài của chúng **. Tôi không biết có thể tôi sai hoặc tôi có thể thiếu một số khái niệm về mảng.


Tại sao bạn muốn sử dụng chỉ số phủ định? Tôi không thấy điểm trừ khi tôi thiếu một cái gì đó.
elclanrs

11
Bạn cũng có thể viết arr[1.5] = 1và điều đó cũng không ảnh hưởng đến độ dài. Đặc tả ngôn ngữ rất rõ ràng về những gì ảnh hưởng đến độ dài. Bạn có thể không thích nó nhưng bạn phải sống với nó. Hoặc thiết kế ngôn ngữ cạnh tranh của riêng bạn và thuyết phục mọi người chuyển sang ngôn ngữ đó.
Raymond Chen,

1
@DigvijayYadav: Có thể được coi là một lỗ hổng hoặc một tính năng giống như nhiều thứ khác trong JavaScript. Điều này là do các mảng hoạt động hơi giống các đối tượng, bạn cũng có thể sử dụng một chuỗi var a = []; a['foo'] = 'baz'nhưng điều đó không có nghĩa là bạn nên làm như vậy; rõ ràng nó chống lại tất cả các quy ước.
elclanrs

2
Các bạn, điểm chính ở đây là ĐẾN LÀ ĐỐI TƯỢNG. Không có sự khác biệt. Đó là lý do của hành vi này, và nó hoàn toàn có chủ đích ngay cả khi bạn không thích. Chỉ cần đợi cho đến khi bạn biết rằng TẤT CẢ các số được biểu diễn dưới dạng dấu phẩy động, thậm chí là số nguyên. JavaScript là một ngôn ngữ tò mò ...
Tony R

2
Bạn có thể tìm thấy chi tiết về thuật toán xác định cài đặt của các phần tử mảng trong đặc tả: es5.github.com/#x15.4.5.1 . (đặc biệt là bước 4) và "chỉ mục mảng" được xác định tại đây: es5.github.com /#x15.4 .
Felix Kling,

Câu trả lời:


109

VẬY đối với tôi về mặt logic thì có vẻ như arr [-1] cũng là một phần của arr.

Đúng là như vậy, nhưng không phải theo cách bạn nghĩ.

Bạn có thể gán các thuộc tính tùy ý cho một mảng (giống như bất kỳ Đối tượng nào khác trong JavaScript), đó là những gì bạn đang làm khi bạn "lập chỉ mục" mảng tại -1và gán một giá trị. Vì đây không phải là một thành viên của mảng và chỉ là một thuộc tính tùy ý, bạn không nên lengthxem xét thuộc tính đó.

Nói cách khác, đoạn mã sau thực hiện điều tương tự:

var arr = [1, 2, 3];

​arr.cookies = 4;

alert(arr.length) // 3;

31
Ok, Điều đó có nghĩa là các chỉ mục phủ định không thực sự hoạt động như các chỉ mục thực.
me_digvijay 29/11/12

4
Đúng, chúng chỉ là các thuộc tính tùy ý trên mảng.
Andrew Whitaker

Nhưng nếu tôi muốn sử dụng các chỉ mục dương làm thuộc tính không giống như chỉ mục và tôi không muốn chúng đóng góp vào độ dài mảng thì sao?
me_digvijay 29/11/12

7
Sau đó, không sử dụng một mảng, hãy sử dụng một đối tượng ol 'thuần túy. Nhưng trong trường hợp đó, bạn sẽ mất lengthtài sản tích hợp sẵn .
Andrew Whitaker

2
@Digvijay: Bảng điều khiển chỉ hiển thị các thuộc tính có tên số nguyên dương vì (tôi giả sử) nó chỉ thực hiện một forvòng lặp thông thường từ 0đến .length. Làm console.dir(arr)để xem các đối tượng hoàn chỉnh. Ngoài ra, việc sử dụng ký hiệu ngoặc để truy cập mảng không phải là một đặc điểm của mảng, nó là một đặc tính cố hữu của đối tượng ( var obj = {foo: 'bar'}; obj.foo or obj['foo']).
Felix Kling,

25

Các lengthbất động sản sẽ trả về một số một cao hơn so với giao "chỉ số" cao nhất, nơi Array "chỉ số" là các số nguyên lớn hơn hoặc bằng không. Lưu ý rằng JS cho phép các mảng "thưa thớt":

var someArray = [];
someArray[10] = "whatever";
console.log(someArray.length); // "11"

Tất nhiên nếu không có phần tử thì length0. Cũng lưu ý rằng dấu lengthkhông được cập nhật nếu bạn sử dụng deleteđể xóa phần tử cao nhất.

Nhưng mảng là các đối tượng, vì vậy bạn có thể gán thuộc tính với các tên thuộc tính tùy ý khác bao gồm số âm hoặc phân số:

someArray[-1] = "A property";
someArray[3.1415] = "Vaguely Pi";
someArray["test"] = "Whatever";

Lưu ý rằng đằng sau JS chuyển đổi tên thuộc tính thành chuỗi ngay cả khi bạn cung cấp một số như -1. (Các chỉ mục số nguyên dương cũng trở thành chuỗi, vì vấn đề đó.)

Các phương thức mảng, chẳng hạn như .pop(), .slice()v.v., chỉ hoạt động trên "chỉ mục" số nguyên 0 hoặc cao hơn, không hoạt động trên các thuộc tính khác, vì vậy lengthnhất quán về điểm đó.


Cảm ơn bạn câu trả lời của bạn đã giúp rất nhiều. Ngoài ra, tôi đã thử phương pháp ghép nối với chỉ số âm, tôi thấy rằng nếu tôi sử dụng -1, nó sẽ chuyển đến phần tử cuối cùng của mảng, cho -2 sẽ chuyển đến phần tử cuối cùng thứ hai, v.v.
me_digvijay 29/11/12

+1. Độ dài không cho bạn biết có bao nhiêu phần tử hoặc có bao nhiêu thuộc tính. Nó chỉ là chỉ số cao nhất + 1. ví dụ như đã cho var a = new Array(9), acó độ dài là 9 và không có phần tử nào cả.
RobG

1
@DigvijayYadav - Có, Array.slice()phương pháp được cho là để làm điều đó. Các String.slice()phương pháp làm điều tương tự.
nnnnnn

7

Lưu ý rằng khi bạn sử dụng chỉ mục vị trí (hoặc 0), các giá trị được đặt trong mảng:

var array = [];

array[0] = "Foo";
array[1] = "Bar";

// Result: ["Foo", "Bar"]
// Length: 2

Đây không phải là trường hợp khi bạn thêm các giá trị không phải chỉ mục (không phải 0-9 +):

var array = [];

array[0]  = "Foo";
array[1]  = "Bar";
array[-1] = "Fizzbuzz"; // Not a proper array index - kill it

// Result: ["Foo", "Bar"]
// Length: 2

Giá trị chỉ được đặt trong mảng khi bạn chơi theo luật. Khi bạn không, họ không được chấp nhận. Tuy nhiên, chúng được chấp nhận trên chính đối tượng Array, đó là trường hợp của bất kỳ thứ gì trong JavaScript. Mặc dù ["Foo", "Bar"]là các giá trị duy nhất trong mảng của chúng tôi, chúng tôi vẫn có thể truy cập "Fizzbuzz":

array[-1]; // "Fizzbuzz"

Nhưng lưu ý một lần nữa rằng đây không phải là một phần của các giá trị mảng, vì "chỉ mục" của nó không hợp lệ. Thay vào đó, nó đã được thêm vào mảng như một thành viên khác. Chúng tôi có thể truy cập các thành viên mảng khác theo cùng một kiểu:

array["pop"]; // function pop() { [native code] }

Lưu ý ở đây rằng chúng tôi đang truy cập popphương thức trên mảng, phương thức này thông báo cho chúng tôi rằng phương thức này chứa mã gốc. Chúng tôi không truy cập bất kỳ giá trị mảng nào có khóa là "pop", mà là một thành viên trên chính đối tượng mảng. Chúng tôi có thể xác nhận thêm điều này bằng cách đi qua các thành viên công khai của đối tượng:

for (var prop in array) 
    console.log(prop, array[prop]);

Câu nào giải thích sau đây:

 0 Foo
 1 Bar
-1 Fizzbuzz

Vì vậy, một lần nữa, đó là trên các đối tượng , nhưng nó không phải trong các mảng .

Câu hỏi tuyệt vời! Khiến tôi chắc chắn phải chụp hai lần.


1
+1 Ngay cả thông số kỹ thuật cũng tuyên bố rằng các thuộc tính có tên thuộc tính số dương được gọi là phần tử của mảng (và bất kỳ thuộc tính nào khác thì không): es5.github.com/#x15.4 .
Felix Kling

Kết quả không phải là: ["Foo", "Bar"]nhưng ["Foo", "Bar", -1: "Fizzbuzz"]hãy kiểm tra fiddle ở đây . Giống như bạn đã viết, nó trở thành một thuộc tính với khóa -1 nhưng nó chắc chắn hiển thị trong đầu ra. Nếu không thì câu trả lời của bạn rất hay và đầy đủ. Nếu bạn thay đổi, tôi sẽ ủng hộ một lần nữa, phản đối của tôi rất khắc nghiệt, nhưng không thể loại bỏ nó; hết thời gian.
Héo

5

Nếu bạn thực sự muốn các chỉ mục phủ định và các chỉ mục khác được bao gồm trong độ dài, thì hãy tự tạo chức năng:

function getExtendedArray() {
    var a = [];

    Object.defineProperty(a, 'totalLength', { 
        get : function() { return Object.keys(a).length; }
    });

    return a;
}

Sau đó, ví dụ:

var myArray = getExtendedArray();
console.log(myArray.totalLength); // 0
myArray.push("asdf");
myArray[-2] = "some string";
myArray[1.7777] = "other string";
console.log(myArray.totalLength); // 3

Và nếu bạn muốn chiều dài để bao gồm chỉ số chỉ số / tài sản bạn có thể thêm một câu lệnh if cho phím - if(Number(key) != NaN) length++;Nếu bạn muốn lọc ra những cái phao thêm && key % 1 == 0để tình trạng này
Bonnev

4

Mảng chỉ là một loại Đối tượng đặc biệt trong JS. Có một cú pháp đặc biệt, rất tốt, vì nó cho phép chúng tôi làm việc với chúng một cách hiệu quả. Hãy tưởng tượng sẽ tẻ nhạt như thế nào khi viết một mảng theo cách đó:

const array = {0: 'foo', 1: 'bar', 2: 'baz'} //etc...

Nhưng chúng vẫn là vật thể . Vì vậy, nếu bạn làm:

const myArray = [42, 'foo', 'bar', 'baz'];

myArray[-1] = 'I am not here';
myArray['whatever'] = 'Hello World';

Các thuộc tính không phải là số nguyên đó sẽ được gắn vào một đối tượng (là mảng), nhưng không thể truy cập được bằng một số phương thức gốc như Array.length.

Bạn có thể giả định rằng phương thức gốc 'length' là một getter đếm tất cả các thuộc tính (và chỉ mục thuộc tính, trong khi các giá trị là giá trị thực ) có thể chuyển đổi thành số nguyên dương hoặc 0. Một cái gì đó giống như triển khai giả này:

Do các thông số kỹ thuật , lengthphương thức gốc - dưới dạng getter ( exampleArray.length) - sẽ kiểm tra tất cả các khóa 'số nguyên không âm nhỏ hơn 2 32 ', lấy khóa lớn nhất và trả về giá trị số + 1 của nó.

Là một setter ( exampleArray.length = 42), nó sẽ tạo một số vị trí trống cho các chỉ số 'bị thiếu' nếu độ dài đã cho lớn hơn số khóa nguyên không âm thực tế hoặc xóa tất cả các khóa số nguyên không âm (và giá trị của chúng) không lớn hơn thì chiều dài cho trước.

Triển khai giả:

const myArray = {
  '-1': 'I am not here', // I have to use apostrophes to avoid syntax error
  0: 42,
  1: 'foo',
  2: 'bar',
  3: 'baz',
  whatever: 'Hello World',

  get myLength() {
    let highestIndex = -1;
    for (let key in this) {
      let toInt = parseInt(key, 10);
      if (!Number.isNaN(toInt) && toInt > -1) {
        // If you're not an integer, that's not your business! Back off!
        if (toInt > highestIndex) highestIndex = toInt;
      }
    }
    return highestIndex + 1;
  },
  set myLength(newLength) {
    /* This setter would either:
      1) create some empty slots for 'missing' indices if the given length is greater than the actual number of non-negative-integer keys
      2) delete all non-negative-integer keys (and their values) which are not greater then the given length.
    */
  }
}

console.log(myArray.myLength); // 4

myArray[9] = 'foobar';

console.log(myArray.myLength); // 10


3

Mảng trong JavaScript thực sự là các đối tượng. Chúng đơn giản được tạo mẫu từ phương thức khởi tạo Array.

Chỉ mục mảng thực sự là các khóa trong một bản đồ băm và tất cả các khóa được chuyển đổi thành chuỗi. Bạn có thể tạo bất kỳ khóa nào (tức là "-1"), nhưng các phương thức trên Mảng được điều chỉnh để hoạt động giống như một mảng. Vì vậy, lengthkhông phải là kích thước của đối tượng, nó chỉ được đảm bảo lớn hơn chỉ mục số nguyên lớn nhất. Tương tự, việc in arrsẽ chỉ liệt kê các giá trị có khóa số nguyên> = 0.

Thông tin thêm ở đây.


chính xác. hoặc chúng ta có thể bắt đầu tự hỏi tại sao mảng ['foo'] không phải là một chỉ mục hợp lệ - javascript cho phép bạn làm điều đó, nhưng không có nghĩa là định nghĩa về những gì một mảng (và các phần tử của nó) thay đổi từ ngôn ngữ này sang ngôn ngữ khác. Làm việc như dự định.
tw airball

Trên thực tế, "mảng" không tồn tại trong JS. Nhưng "mảng kết hợp", còn được gọi là "đối tượng", là cốt lõi của JS. Vì vậy, không phải là một bước nhảy vọt khi tạo ra một đối tượng Array bắt chước một mảng thông thường.
Tony R

1
Có một điểm khác biệt rất đặc biệt giữa mảng và đối tượng đơn giản: thuộc tính tự điều chỉnh độ dài.
RobG

1

Theo MDN:

Giá trị của thuộc tính length là một số nguyên có dấu dương và giá trị nhỏ hơn 2 đến lũy thừa 32 (232)

Trong Javascript, bạn có thể đặt một thuộc tính trên bất kỳ đối tượng nào bạn tạo.

var array = new Array();
array = [1,2,3];
array["boom"] = "pow";

Tương tự như vậy khi bạn đặt một chỉ mục âm, nó sẽ lưu trữ nó như một thuộc tính trên mảng chứ không phải là một phần của chỉ mục.

array[-1] = "property does not add to array length";

Đây là lý do tại sao độ dài không phản ánh nó nhưng vòng lặp for..in cho thấy nó.


-1

Khi bạn sử dụng chỉ số không dương hoặc không phải số, mảng hoạt động như một mảng kết hợp có các cặp khóa-giá trị.

Để lặp qua mảng bạn có thể sử dụng

for(var index in myArray) {
  document.write( index + " : " + myArray[index] + "<br />");
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.