12

Today I stumbled upon a question here on Stack Overflow - How do I remove objects from a javascript associative array?. What struck me was that the accepted answer was both misleading and heavily upvoted, so I highlighted the possible pitfall.

However, while cobbling together a corrective answer, I realized I have no idea as to why it makes sense for delete to keep elements assign undefined instead of removal.

var elements = new Array()
elements.push(NaN)
elements.push(NaN)
elements.push(NaN)
delete elements[1]
console.log("number of elements: ", elements.length)   // returns 3

Is there a rationale behind it?

Community
  • 1
  • 1
Saul
  • 17,973
  • 8
  • 64
  • 88
  • 1
    I am amazed this has not been asked before, as this touch very "sensitive spot" in the core of JavaScript and the way it handles arrays. – Shadow The GPT Wizard Apr 02 '12 at 10:32
  • 1
    Note that the accepted answer to the linked question is correct. The phrasing of the question uses the word "array" to mean "associative array", e.g., a map or dictionary. Note that the linked question doesn't use an `Array` at all. (But the answer would still be correct if it did; JavaScript arrays aren't really arrays, see my answer for details on that.) – T.J. Crowder Apr 02 '12 at 10:32
  • @T.J.Crowder - I am familiar with `typeof`...but still, since Array is a built-in object, it should get along with `delete`. Too bad it doesn't. – Saul Apr 02 '12 at 10:36
  • @Saul: It makes total sense that `delete` does not work as you expected. `delete` works on a lower level, directly on object properties. It does not "know" that the object you are modifying is an array. Things were different if the value of `.length` was determined on access, but it is not. – Felix Kling Apr 02 '12 at 10:39
  • @FelixKling - Are you saying that an implementation where an Array instance reacts meaningfully to `delete` is worse off? – Saul Apr 02 '12 at 10:41
  • @Saul: Array gets along perfectly well with `delete`. It doesn't match your expectations, is all. You're not alone with `delete` not matching what you expect. A lot of people think `delete` deallocates memory, for instance. I've long wished they'd used a different name for the remove-a-property operator, since `delete` in JS is completely and totally different from `delete` in languages that have a similar high-level syntax (but completely different plumbing). But once you learn what it really does (and what arrays really are in JS), it all makes perfect sense and you stop being surprised. – T.J. Crowder Apr 02 '12 at 10:43
  • @T.J.Crowder - I suppose it is sometimes inevitable for similar keywords to have different meanings in different languages. In this case, my assumption was that `delete` not refreshing `length` had some hidden benefit. Turns out it's simply a language peculiarity. – Saul Apr 02 '12 at 10:48
  • @Saul: To *my* mind there's a benefit, because `delete` really has nothing to do with arrays so there's no reason it should have any effect on `length`; for it to have an effect would be surprising. For it to duplicate `splice` would be *really* surprising, because `delete` doesn't rename properties on any other object type. But sure, you could call it a peculiarity if you like. :-) One could certainly argue that deleting the *last* index of an array might reasonably change `length` to be the (new) last index + 1 (since creating a new property does), but not doing `splice` is reasonable. – T.J. Crowder Apr 02 '12 at 10:57
  • @T.J.Crowder - I suppose it depends on your viewpoint. Take for example the `+` operator. It has no qualms in mutating its behavior for different constructors (Number vs Array). In contrast, `delete` is a lot less sophisticated. If you ask me, a `delete` operation which updates `length` of an Array sounds quite reasonable when compared to `+` which yields different return types depending on operands.. – Saul Apr 02 '12 at 11:30
  • @Saul: (`+` doesn't have any special behavior vis-a-vis arrays, but it does vary its behavior between, say, `Number` and `String` so your point still holds.) Sure, they could have done that. And re "peculiarity," I think anyone who knows JavaScript well would agree that its "arrays" are a bit...peculiar. ;-) Powerful and cool (to me, at least), but definitely off the beaten path, like so many other aspects of the language. – T.J. Crowder Apr 02 '12 at 11:32
  • @T.J.Crowder - In some ways they have...contrast `new Number() + new Number()` vs `new Array() + new Array()`. Both operations do involve magic but at the end of the day `+` provides different behaviour for different constructors. That is why I was wondering if there was some special reason for `delete` being as it is. – Saul Apr 02 '12 at 12:05
  • @Saul: Yeah, and a perfectly valid question. The magic isn't for arrays (the same is done for all non-number operands to `+`), but that's a total side point. (The effect you're seeing is type conversion, not special `+` handling for arrays.) But again, that's just a side point. There's a special reason for `+` to treat non-numerics the way it does (they wanted it to concatenate strings). There's no special reason for `delete` being as it is; it's just there's no special reason for it to affect `length`, either -- either Brendan Eich didn't think of it, or didn't want to have it do that. :-) – T.J. Crowder Apr 02 '12 at 13:11
  • @T.J.Crowder - To nitpick, I was referring to the external behavior of `+`. For example, `(null + null) === 0` although `typeof null` returns "object". Likewise `isNaN((0 + undefined)) === true`. To summarize, `+` is quite smart: it returns a concatenation of string representations if at least one operand is a string, a function or an object, otherwise it returns an arithmetic sum of operands as a number. I for one wouldn't mind if Brendan Eich would have wanted `delete` to do a bit more.. – Saul Apr 02 '12 at 14:38
  • @Saul: My point was that nitpicking, `+` is not smart particularly smart (it handles numbers and strings, that's all). Type conversion is smart(ish). It's the [`ToPrimitive` op](http://es5.github.com/#x9.1) that does the work you're attributing to [`+`](http://es5.github.com/#x11.6.1). But we're way into discussion-land now. :-) Best, – T.J. Crowder Apr 02 '12 at 14:42
  • @T.J.Crowder - Nope, I am attributing to `+` what its output becomes in relation to its input. Sure the internal specification says how it's done but if internals become primary then why provide magic in the first place - abstraction is supposed make things easier, not the other way around. After all, what more can a modification to a programming language require than a pile of comments on Stack Overflow.. – Saul Apr 02 '12 at 15:33
  • @Saul: My point is that understanding how it's defined usually helps with understanding how the whole hangs together, and how things are sometimes simpler and more consistent than they first appear. It certainly did for me. – T.J. Crowder Apr 02 '12 at 15:37
  • @T.J.Crowder - Fair enough. Who knows, Brendan Eich or someone equivalent may very well stumble over this discussion in some distant future and have a divine revelation on how to improve `delete`, for better or for worse. – Saul Apr 02 '12 at 15:58

4 Answers4

12

I realized I have no idea as to why it makes sense for delete to assign undefined instead of removal.

It doesn't. delete removes properties from objects, it does not set them to undefined. Here's an easy way to tell:

var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
delete a[1];
console.log(1 in a); // logs "false"

Note that after the delete, a doesn't have a property called 1 anymore. At all.

Contrast with:

var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
a[1] = undefined;
console.log(1 in a); // logs "true"

There, a still has a property called 1, it's just that the property's value is undefined.

It's useful to understand that in JavaScript, arrays aren't really arrays at all. They're just objects, array "indexes" are just property names (which are strings — yes, really, we just tend to write them as numbers), arrays have special handling of property names that are all numeric (indexes), a special length property, and some functions they get from Array.prototype. This is very clearly laid out in Section 15.4 of the spec. Once you have it set firmly in your head that JavaScript arrays aren't really arrays, they make a lot more sense. :-)

Deleting an array "index" property from an array does not change its length (not even if you delete the highest-numbered one); it just creates a hole in the array (JavaScript "arrays" are sparse arrays by their nature; e.g., they can have gaps in them). So in my first example above, I get exactly the same array that I'd've gotten if I'd done this:

var a = [];
a[0] = 'a';
a[2] = 'c';

Note the gap, the array has no 1 element/property.

If you say:

var foo = a[3];

...foo can get the value undefined for two completely different reasons:

  1. a has a property called 3 that has the value undefined, or:
  2. a has no property called 3 at all; the result of a property accessor operation on an object that doesn't have a property by that name is undefined. This if-no-property-return-undefined is covered by the spec in a fairly convoluted way, but mostly in Section 8.12.3.

These are very distinct things.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • firefox (12) does set array elements to `undefined` when called `delete` on the index – jAndy Apr 02 '12 at 10:13
  • @Saul: Because `delete` works generically on objects and does not update the length of the array, it's always the highest index + 1. But even removing the last element of the array with `delete` does not update `.length`. – Felix Kling Apr 02 '12 at 10:15
  • @jAndy: Every debugger will show the property as `undefined` because arrays are supposed to have continuous keys. If an intermediate index is missing, it will be shown as `undefined`, since accessing non-existing properties returns `undefined`. Try `a = []; a[5] = 42;`. – Felix Kling Apr 02 '12 at 10:16
  • 2
    @Saul: The `length` property is just the highest numbered property of the array plus one. Try `var a = new Array(); a[100] = 1; alert(a.length);` – Tim Down Apr 02 '12 at 10:16
  • @jAndy: I find that very hard to believe. It would be massively at variance with the specification. – T.J. Crowder Apr 02 '12 at 10:17
  • @FelixKling: yes you're right. But I thought in your example there, the array will have `5` elements nonetheless. – jAndy Apr 02 '12 at 10:18
  • @FelixKling - Well..I would expect deletion of an Array element to be reflected in its reported length. That is why I asked. Is there a benefit in not doing it? – Saul Apr 02 '12 at 10:18
  • @jAndy: No, Felix's array has one element. It's a *sparse* array. – T.J. Crowder Apr 02 '12 at 10:19
  • @Saul: However, using `delete` does not update the `length` property. – Tim Down Apr 02 '12 at 10:19
  • @Saul: It's just how arrays are defined. The spec doesn't say deleting an index property from an array lowers `length` (even if you delete the last one), and so it doesn't. – T.J. Crowder Apr 02 '12 at 10:20
  • @Saul: And because of that it is wrong to use `delete` for arrays. It is not made for the specific nature of arrays. – Felix Kling Apr 02 '12 at 10:20
  • @T.J.Crowder: Are you sure about that? I mean consider this `var a = []; a[5] = 42; // (undef, undef, undef, undef, 42)` and then `a[2] = 'wtf';` we got `(undef undef, "wtf", undef, 42)`. So the interpreter must create all the elements, even if they are `undefined` value – jAndy Apr 02 '12 at 10:22
  • @jAndy: No, run `console.dir(a)` and have a look at the output. But because `a` is an array, the tool iterates over the elements from `0` to `a.length`. – Felix Kling Apr 02 '12 at 10:23
  • @FelixKling: so conclusion, `var a = []; a[500] = true;` does not create `500` array elements, but `var a = new Array(500);` would do. hmm.. well it seems like `new Array(n)` also does not create array elements.. wtf I'm confused now. – jAndy Apr 02 '12 at 10:25
  • @jAndy: Yes, I'm sure. :-) See the section of the spec I linked toward the end of the answer. Re `new Array(500)`: No, that doesn't create 500 elements either. `var a = new Array(500);` is exactly like `var a = []; a.length = 500;` You have an array with no elements and a `length` property of `500`. That's all. Arrays are not really arrays at all in JavaScript, they're just objects with some special handling for `length` and numerically-named properties. – T.J. Crowder Apr 02 '12 at 10:25
  • @jAndy: Nope, `new Array(500)` does not create 500 elements either (see `console.dir` again). It just sets `length` to 500. You might also want to have a look at the spec: http://es5.github.com/#x15.4.2.2 – Felix Kling Apr 02 '12 at 10:26
  • @FelixKling: reading the spec right now. Anyway I will do some browser memory testing since I'm still not convinced that this spec is implemented that way. by the way, I was pretty shocked about that button face on the left, almost looks like the trollface :p – jAndy Apr 02 '12 at 10:30
  • So it might be worth to point out that the value of `.length` is not determined at the moment it is accessed, but when the array is modified using array methods such as `push`, `splice` and assignment. Any other methods are not modifying `.length`. – Felix Kling Apr 02 '12 at 10:30
  • @jAndy: Yeah, it's a fantastic HTML version of the spec, but I do wish they would ditch that big green globeface... – T.J. Crowder Apr 02 '12 at 10:33
  • 1
    @jAndy: *"Anyway I will do some browser memory testing since I'm still not confinced that this spec is implemented that way."* Implementations are free to do whatever they like, as long as it meets the semantics of the specification. I'm quite certain that `new Array(500)` doesn't allocate memory for 500 properties (elements) in any successful engine, though. That doesn't mean, though, that an engine might not use a contiguous-memory array in places where it can within spec and where there's a performance benefit. That just has to be transparent to the coder; it must work per spec. – T.J. Crowder Apr 02 '12 at 10:36
6

It doesn't assign undefined. It deletes the property. (If you try to access a property that doesn't exist, you will get undefined, and length is based on the highest numbered item in the array).

It makes sense, because it works that way on any kind of object. For it to act otherwise, it would have to special case objects if they were an instanceof Array but only if it was a property with a numeric name.

Use splice if you want to remove an item from an array.

> var elements = [NaN, NaN, NaN];
> elements.splice(1,1);
> console.log(elements);
[ NaN, NaN ]
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
3

JavaScript arrays can be "sparse". That is, some slots can be empty in the sense of never having had a value assigned, or having a value deleted. If you test the value associated with that index you'll get back undefined because it doesn't exist, not because it was assigned the value undefined.

When delete removes an item from an array it doesn't automatically slide the rest of the elements up to fill the space: the other elements retain their existing indexes, which in turn means the .length property doesn't change since .length is defined as being equal to the highest assigned index plus one.

If you want to remove an array element and have the other elements renumbered use the .splice() method.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
1

This is because delete operator removes property, and removed property has value undefined. To remove element from array you can use splice method of array. All of this is because of how the delete operator in javascript work.

When you do not set property of object and try to check its value it will be undefined:

var obj = {};
alert(obj.foo); // undefined

which is the same as:

alert(obj['foo']); // undefined

And look at this:

// create empty object
var obj = {};
// check its property named 1
alert(obj[1]); // undefined

// set property named 1 to value 'fooo'
obj[1] = 'fooo';
// and check it
alert(obj[1]); // 'fooo'

// now delete it 
delete obj[1];
// and check again
alert(obj[1]); // undefined 

delete has removed property and its value is undefined - and all above code was about object.

Now look at the arrays:

var arr = []; // it's better to use [] than new Array();
alert(arr[1]); // undefined  - same as above

// assign value
arr[1] = 'fooo' 
// check it
alert(arr[1]); // 'fooo' - same as above

// remove it 
delete arr[0];
// and check
alert(arr[1]); // undefined - same as above

So behavior is the same, but what about length property of array. Specification http://es5.github.com/#x15.4.5.2 says:

"The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index."

So when you look at this:

var arr = ['foo', 'bar', 'foobar'];
alert(arr.length); // 3
// delete first
delete arr[1]; 
// and check length
alert(arr.length); // 3

Last check gives 3 because the last deletable property in this array has index 2 - first property (with index 0) has value undefined (delete operator set this value), second item (with index 1) has value 'bar' and third (with index 2) has value 'foobar'. So according to specification length = 2 + 1 ('allways numerically greater than last deletable');

This is visible also in this code:

var arr = [];

arr[10] = 'foo';
// element with index 10 is set to 'foo' but elements from 0 to 9 don't have value - they are undefined
// now check length
alert(arr.lenght); // 11 

Last deletable property index is 10, so 10 + 1 gives 11, despite of previous elements that are undefined.

So delete operator does his work, but it is not designed to remove items from array.

Community
  • 1
  • 1
Mateusz W
  • 1,981
  • 1
  • 14
  • 16
  • 1
    *"This is because delete operator sets property value to undefined."* No, that is simply wrong, is is your later statement *"`delete` set `undefined` value for passed property"*. – T.J. Crowder Apr 02 '12 at 10:52