DEV Community

Kyle Simpson proved I STILL don't know JavaScript (arrays)

Paceaux on December 06, 2019

If you've never read Kyle Simpson's fantastic series, You Don't know JS, I'd first encourage to stop reading this, go read that , and then come bac...
Collapse
 
blindfish3 profile image
Ben Calder

I generally don't concern myself too much with what's happening under the hood; but this was interesting :)

So - the output of this is enlightening:

const array1 = [, 'b', 'c'];
console.log(typeof array1[0]); // undefined
console.log(array1.propertyIsEnumerable(0)); // false

const array2 = [undefined, 'b', 'c'];
console.log(typeof array2[0]); // undefined
console.log(array2.propertyIsEnumerable(0)); // true

const array3 = new Array(3);
console.log(typeof array3[0]); // undefined
console.log(array3.propertyIsEnumerable(0)); // false

The implicit/explicit undefined looks like a red herring to me.

Array properties that haven't been explicitly declared are simply not set as enumerable; but I guess as far as the interpreter is concerned the values to be iterated over have been defined:

The for...of statement iterates over values that the iterable object defines to be iterated over. (MDN)

Also, see the output from console.log(array1.entries().next()).

Collapse
 
mattmcmahon profile image
Matt McMahon

Yep. Beat me to it. It's all the difference between having an ownProperty or not.

I'm wondering, since I've never tried to do this and can't conveniently try it right now, what the in operator reports for things like 0 in [,1,2]? Is that statement true or false? Or is it a syntax error?

Collapse
 
paceaux profile image
Paceaux

I tested five loops: for in, for of, forEach, map and for of was the only one that iterated on all slots.

Here's a gist of what I wrote. Feel free to run it in a few different browsers and tell me if my tests were wrong.

Collapse
 
paceaux profile image
Paceaux

I went ahead and updated my test gist with an if in test to see what it did.

const slotted = [,,'one'];
let i = 0;

while (i < slotted.length) {
 if (i++ in slotted) {
  console.log(`${i - 1} is in the array`);
 }
}

it, too, will inform us that only an index of 2 exists. This I think further confirms that these items in the array are not "hidden properties" at all.

Collapse
 
paceaux profile image
Paceaux

You make a great argument, but I don't think it's a red herring. (I'm more than willing to be proven wrong, though).

I read your comment a few times and wondered, "If a property isn't explicitly declared, does that mean it's implicitly undefined?" i.e.... are we really saying the same thing?

I decided to dig into the specs to see if a property really is created, though. And I don't think one is.

I've found at least one spot in the ecma specs that defines this behavior in section 12.2.5:

Elided array elements are not defined.

When you say

Array properties that haven't been explicitly declared are simply not set as enumerable;

At least in this one section, it doesn't appear as though these empty slots are properties created on the array.

Most telling is step 8 of 22.1.3 of the specs that describe how an array is created:

Repeat, while k < numberOfArgsa.

a. Let Pk be ! ToString(k)
b. Let itemK be items[k
c. Let defineStatus be CreateDataProperty(array, Pk, itemK)\
d. Assert: defineStatus is true
e. Increase k by 1

So, Pk is the key, and itemK is the value. If you have an array containing an undefined, e.g. [undefined, 1, 2], it's an item ... at least I think ... according this logic.

Similarly, that would mean that in the case of [,1,2], there is no "item" in the first slot.

But that all depends on what CreateDataProperty does. When I read how CreateDataProperty behaves, in section 7.3.4, steps 3 and 4 don't give a condition for setting the enumerable prop in the descriptor to false:

  1. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]:true }.
  2. Return ? O.[DefineOwnProperty]

So, if there were a value... any value, it would be enumerable. But if there isn't a value... it's not.

So I think "implicit undefined" is an accurate description of this case because the specifications dictate in at least one place that "elided array elements are not defined" ... meaning no properties are created on the array and browser implementers have to give you something if you ask for a value at a slot that has no value. So the Array internals are giving you undefined.

I think.

Collapse
 
blindfish3 profile image
Ben Calder

Like I said - this isn't something I tend to dig into too much...

But I think we're more or less in agreement - I'm just not comfortable with the idea of a distinction between explicit/implicit undefined. However you get to it undefined === undefined :D

I didn't dig into the spec but did think to do this:

var array1 = [,"a","b"];
// since an Array is just a fancy Object:
console.log(Object.keys(array1)); // ["1", "2"]
console.log(array1.hasOwnProperty(0)); // false
console.log(array1.hasOwnProperty(1)); // true
console.log(array1.hasOwnProperty(2)); // true

// seems obvious but for ... of isn't the only way to 
// get the implicit undefined
for(let i = 0; i<array1.length; i++) {
  console.log(array1[i]);
} // undefined a b

array1[0] = "c";
console.log(Object.keys(array1)); // ["0", "1", "2"]

// and just for good measure:
console.log(Object.keys([,"a",,,"b",])); // ["1", "4"]

So the above suggests that:

  • keys are only set for values that are explicitly declared
  • modern iterator functions loop over Object.keys - i.e. enumerable properties
  • for ... of assumes that Object.keys should conform to array standards (i.e. start at 0 increment by 1)
  • the old fashioned for loop just does what you tell it :)
  • undefined is always returned when you try to access an object property that doesn't exist

Thanks for the article! It turns out that thinking about what happens under the hood can consume far too much of my time :D

Thread Thread
 
paceaux profile image
Paceaux

I updated my article to reflect our conversation (seemed too useful not to)

I think this goes back to an odder quirk of JavaScript where sometimes undefined also means "undeclared", but not always.

this "implicit undefined" is really more like an "undeclared", but JavaScript doesn't provide a means to distinguish the two very easily.

Collapse
 
blindfish3 profile image
Ben Calder

Oh - and a massive +1 for recommending the "I don't know JS" series!

Collapse
 
genspirit profile image
Genspirit • Edited

I think this actually makes perfect sense if you actually take a look at the
documentation. When you set something to undefined you are initializing it. So while it is still undefined its not the same as uninitialized. Array.forEach isn't invoked on uninitialized values and for... in(which you shouldn't use with arrays anyways) does something similar(more specifically it will loop over enumerable properties). What you are referring to as implicitly undefined and explicitly undefined is largely just initialized and not initialized.

Collapse
 
paceaux profile image
Paceaux

You are absolutely right on all points (and that's really what I was getting to).

The behavior makes sense once you break down what's actually going on (as is almost always the case with JavaScript)

And yeas, "implicitly undefined" and "explicitly undefined" could also be called "uninitialized" and "initialized". Though I like implicit/explicit because it suggests intent a bit more clearly.

Collapse
 
namick profile image
nathan amick • Edited

Wow, great write up. Made me actually laugh out loud.

If I didn't already love JavaScript, I would steer clear. It sounds like a language somebody conceived of and implemented in like ten days with no forethought or planning.

Reminds me how relevant Douglas Crockford's "JavaScript, The Good Parts" still is.

Collapse
 
paceaux profile image
Paceaux

Thank you!

I think JavaScript is brilliantly well done. Regardless of the whole, "10 days and no forethought", I think Brendan Eich did a lot right.

I think he understood way better than many other programmers how important it is in the browser to have a fault tolerant language that does as much as possible to prevent the UI from crashing on a user.

I think most of the "quirks" or "weirdnesses" in JavaScript can be explained as being quite intentional because Eich was trying to make a language that wouldn't punish the end user for the developer's mistakes.

Except Date.

and NaN !== NaN.

Those I think he screwed up on.

Collapse
 
namick profile image
nathan amick

Ha, indeed. I think you're right.

Collapse
 
steffennilsen profile image
Steffen Nilsen

I haven't done any testing, but the part where you go over array instantiation and c++, I wonder how smart the V8 engine is when it comes to allocate memory for an array. I know that you shouldn't use sparse arrays, inserting items at indexes so there's a gap, but I can't recall if it does optimizations based on setting a predefined array size

Collapse
 
paceaux profile image
Paceaux

If I were smart enough to work on the V8 engine, I'd have it optimized based on array size.

Granted, I only ever took one C++ class... and I have liberal arts degrees. So I'm far from qualified-enough to say for sure if that's the right way to go.

Collapse
 
budyk profile image
Budy

whooaa... dang you Array 😂😂