Fun with JavaScript Arrays

The first thing I usually run into when trying new programming languages are some weird corner case behaviors, so I decided to run into those on purpose and write one of my first blog posts about that.

Couple of notes before we begin:

  • I've tried all those examples in Google Chrome Console - command+option+I on Mac and hopefully similar keys on Win/Linux
  • When reading this article you might need to stop and think. Stop and Think!
  • Also keep in mind that sometimes I'm playing stupid, but sometimes I'm actually stupid.

// Let's say we have an array with three elements, this can't hurt
var [123];
a
[123]

// Ok, looks good
// Let's add something interesting
a[-14;
// Has this array changed?
a
[123]

// No?
// What about that element
a[-1]
4

// Hmm, it's there
// So what happens when we try something crazy
a[NaN5;
a
[123]

// Whoaaa

// Let's try couple of others
a[-Infinity"P";
a[-Infinity+3"NP";
// No error yet, so let's do a little check
a[-Infinity=== a[-Infinity+3]
true

// Did I just prove that P=NP? Never-mind, we shouldn't get distracted
// by unimportant problems. So how could I see what's inside a? console.log!
console.log(a);
[123-14NaN5-Infinity"NP"]

// Hmm, this kinda looks like it's messed up array with object
// So let's check
typeof a
"object"

// Type of array is object? Really?
typeof []
"object"

// Indeed it is, I almost forgot about this ~'Good Part'
// Now that I have entertained myself... inverting string bitwise, heh...
// Actually, what is the result?
~'Good Part'
-1

// Yea... 
// But let's get back to adding wrong things to array
a[function]='err';
SyntaxErrorUnexpected token ]

// Ok, that is actually wrong, so something else
a[typeof console.log"duh";
a['~''<';
a[a[typeof a]='huh'void a'puh';
a[{do'break'}'thanks';
a.continue ["-",+"you"+"","+"][3,2,1][2];
a.return function(p)return p;};

// I'm starting to feel bad about abusing this little array a so much. 
// Are you ok little fella?
a
[123]

// Like nothing had happened
// So what about operators?
a[a|a3;
a[-~a&-~a2;
a[-~-~!a1
a
[321]

// Good, good, nicely reversed
a['!'-1]
5
// Whoaa? Dude?
'!'-1
NaN

// Oh yeah, I forgot about that I already have NaN in there
// console.log will reveal other interesting things for sure
console.log(a);
[  
  -Infinity"NP",
  03,
  12,
  21,
  -14,
  NaN5,
  [object Object]"thanks",
  continue"N",
  function"duh",
  length3,
  object"huh",
  returnfunction (p)return p;},
  undefined"puh",
  ~"<"
]

// Hmm, there are couple of nice ones 
a.length 2;
a
[32]

// Sorry for that
a[{}];
"thanks"
a['[object Object]'"you're welcome";

// Also, where did that continue: "N" come from? Was that from example with N=NP?
// Not really... that actually reminds me of one episode from my famous 
// "JavaScript sucks" series:
// You should try this one. Just put the following into your favorite JS console:
// Or are you afraid of JavaScript? :)
(({}-1)+"")[2]+({}+[])[1]+" "+((~-1<~[0])+"")[1/(1/0)]+((/./>/^/)+"")[4]+(""+!!(3^3))[1]+(""+!!(_="$"))[1]+"!"
// ... result stripped ... I won't make it that easy for you.

// Let's reveal one of the sneaky ones 
// (that has actually nothing to do with arrays):
a[100"Never gonna \
give you up,"
;
a[101"Never gonna \ 
let you down";

SyntaxErrorUnexpected token ILLEGAL

// Wow ... I thing I have been just Rickrolled by Chrome interpreter
// and I thought he would never gonna run around and desert me
// Do you know how this happened? BroTip: Try to copypaste it into your console.

// Talking about Syntax Errors, let's say we need a function that performs
// this importing-like functionality for us. Let's call it import.
function import({}
SyntaxErrorUnexpected token import



// One thing I haven't tried yet is doing a bit of recursion in array
a.me a;
console.log(a);
[..meArray[3...// striped other previously messed up things

console.log(a.me.me.me)
[..meArray[3...]

// If you would run the same thing in Node.JS you would see more explanatory
console.log(a.me.me.me)
me[Circular]

// Let's run simple one liner to try to iterate through recursive object
for(var i=0c=ac=c.meconsole.log(i)i++);
0
1
...
26012391
...

// Ok, Node.JS is still running, no simple way to kill it. 
// What about Chrome console?
for(var i=0c=ac=c.meconsole.log(i)i++);
0
1
...
314
...
2679
...

// Chrome died. Goodbye a. See you in Silicon Heaven.

// That reminds me of good old times when I tried to view 5MB XML without 
// line breakings in Chrome, Opera, IE, Firefox...

// Let's start chrome console again with clean array
var [];
a.me a;
// Hmm I need to add some elem to array, so I can just copypaste that loop again
a[12
a
[undefined × 12]

// That's interesting - array is 'filled up' with undefineds and Chrome
// prints that out neatly

// Let's test how long this loop take
console.time('evt')for(var i=0c=ac=c.me10000console.log(i)i++)console.timeEnd('evt');
evt1552.587ms

// Let's try the same thing with object to see if there's any difference
var {};
o.me o;
console.time('evt')for(var i=0c=oc=c.me10000console.log(i)i++)console.timeEnd('evt');
evt1610.678ms

// Not really - so what to take from this: Arrays are Objects, essentially.
// And yes, proving that by executing console.log multiple times is just
// plain stupid.

// One last interesting thing
i
10000

There are not many languages where you can access index from for loop after the loop ends. JavaScript is one of them, since it uses function scopes rather than block scopes and variable declarations are hoisted to the top of a function, but about that later.

Let's stop making fun of languages that suck and let's continue with that next time. We will have a look at comparing things. Also note that I really enjoy programming in JavaScript, but that doesn't mean I cannot rant about it's bad parts. And it's good parts. And my job and everything. 

Do you like what you have just read? Do you hate it? Leave me a comment!

30 comments :

  1. OK, it's look like fun, but you should get some basics about javascript types:

    Matthias reuters articles about java-script types at united-coders.com : part1, part2, part3 and part4 are a good start or the book good parts of javascript ...

    For really fun look at WAT - A lightning talk by Gary Bernhardt from CodeMash 2012

    ReplyDelete
    Replies
    1. java-script?

      Delete
    2. Christian, thanks for links! Those are indeed really nice and well written articles and I haven't seen them before.
      And yes, Gary's WAT makes me laugh every single time :)

      Delete
  2. Very entertaining reading. I learned some things, and I was amazed (not entirely positively) by a few of your examples.

    ReplyDelete
  3. So, yeah. Javascript doesn't have many types of things: strings, numbers (which are `double`s), objects, functions, and undefined. Objects map strings to things.

    Javascript 'arrays' are just objects, but their internal setters and getters are a little different. If you want to meditate upon such things, meditate upon this:

    > function Arr() { this.length = 0; }; Arr.prototype = [];
    []
    > x = Array(); y = Arr(); [x, y]
    > x = new Array(); y = new Arr(); [x, y]
    [ [], { length: 0 } ]
    > x[1] = 'abc'; y[1] = 'abc'; [x, x.length, y, y.length]
    [ [ , 'abc' ], 2, { '1': 'abc', length: 0 }, 0 ]
    > [x.toString(), y.toString(), ({1: 'abc', length: 0}).toString()]
    [ ',abc', '', '[object Object]' ]
    > [x instanceof Array, y instanceof Array]
    [ true, true ]
    > y.length = 2
    2
    > [x.toString(), y.toString()]
    [ ',abc', ',abc' ]

    So you see that the toString() is passed over from Array to Arr in the prototype, but the special setter which notices, "oh, you're passing in a positive numerical index, I should update the length" is not passed over from Array to Arr; and the environment does not immediately recognize an Arr object as an array even though it does satisfy `instanceof Array` due to prototypical inheritance.

    Anyway, the reason why a[-1] and a[NaN] are not crazy is because they get .toString()'ed before they go in. You may have noticed that any lightweight object declared with `{}` has a toString which gives '[object Object]', which means you can do this:

    > a = {}; a[{}] = 1000; a
    { '[object Object]': 1000 }

    That's what was happening when you said a[-1]; it set a["-1"] to be a special value.

    ReplyDelete
  4. None of this proves that JavaScript sucks. All it proves is that you had preconceptions about how it should work that were wrong.

    ReplyDelete
    Replies
    1. Arguably, a programming language which does not conform to common and helpful preconceptions is a programming language that sucks, because it adds an extra cognitive layer between "this is what I want the program to do" and "this is the code I have to write"

      That said, I love JavaScript :)

      Delete
    2. Where is it claimed that this proves that javascript sucks?

      Delete
    3. If a language is more powerful in certain areas, it also means it differentiates from the norm.

      Every time you learn a new language, you have to *learn* it. A lot of people coming from PHP, Java or C# think "it's just a scripting language" and underestimates the power of JavaScript and they hit a brick wall. And that's not the fault of JavaScript, but pure laziness.

      Delete
  5. Most of this matches with WAT - A lightning talk by Gary Bernhardt from CodeMash 2012. And rest are mostly playing with properties in objects on JavaScript.

    Just that certain obvious preconceptions about the language needs to be changed.

    ReplyDelete
  6. Most of what you did was adding new properties to `a`. That's all. As long as you use for loops (not for-in) or foreach you will never iterate over those. [1]

    An array is an object just like any other, I'll give you that typeof [] is odd but, Array.isArray is what you are looking for.

    [1]: http://codepen.io/seraphzz/pen/pAHgJ

    ReplyDelete
  7. Aaaa ... javascript. Enjoyed this - looking forward to more!

    ReplyDelete
  8. You didn't prove that "P" equals "NP", in fact you just showed that, in Javascript, -Infinity == -Infinity-3.

    So, when you did:
    a[-Infinity+3] ='NP';

    a[-Infinty] were just replaced by 'NP'.

    ReplyDelete
    Replies
    1. I don't think he literally thought he solved the P=NP problem.

      Delete
    2. Of couse, I just thought it could be relevant to point it out.

      Delete
  9. Entertaining. :-)
    Why are some people being so defensive?

    ReplyDelete
    Replies
    1. I'm still happy about those responses, because they often come up with interesting sources. :)
      Maybe next time I should point out clearly that I know exactly (well, there are still things to learn) why are all those things happening.

      Delete
  10. "…sometimes I'm playing stupid, but sometimes I'm actually stupid."

    :D

    ReplyDelete
  11. This post reads like an obfuscation contest. There is only one JS oddity in here that is very simple and straightforward: that an array can be assigned arbitrary property names. The rest of the post just uses increasingly obfuscated code to create the property name.

    ReplyDelete
  12. An array is an object: there's no reason you can't assign arbitrary properties to it.

    ReplyDelete
  13. Ctrl + Shift + J for chrome

    ReplyDelete
  14. Being new to Javascript, I found this tutorial very helpful. Thank you for such a great article!

    ReplyDelete
    Replies
    1. Thanks! But please don't take this as a tutorial. Use it rather as a list of things you should avoid ;)
      Learn the real deal from books, like from Douglas Crockford's: "JavaScript: The Good Parts".

      Delete
  15. Lets complain when the browser throws errors when we use reserved words as variables! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words Look at that, import is on the list.

    ReplyDelete
    Replies
    1. And do you have a post where you complain that undefined can be redefined in some browsers? lol

      Delete
  16. You're silly and you'll realize that if you spend more time using JS! :)

    Quick tips:
    * Array is just an Object.
    * The setter will check if you pass a valid index (integer >= 0) and if you don't, it will just create a property with that name on the Objects instance.
    * console.log() will only log array keys + object properties that [].propertyIsEnumerable(prop). All properties set by you on Array Objects are enumerable.
    * Using reserved keywords like `function` or `import` as identifiers is forbidden. However, you may use them as Object properties.
    You can't set a[function] but you can set a['function'] and create the `function` property which you can read using `a.function`
    `a[typeof console.log]` is same as `a['function']`, not `a[function]` since `typeof` returns a String

    * `Infinity` + anything is still `Infinity`. That's why `a[Infinity]` === `a[Infinity + 3]` (`Infinity + 3` is evaluated as `Infinity`, so you basically accessed the same Object property)

    * You need to be extra careful with the variables' scope. JS constructs like `for`, `while`, `switch` don't have their own scope. However, the problem with that `i` is even bigger. Consider this:

    // in the global scope
    i = 'Free!';
    console.log('GLOBAL: ' + i);
    // GLOBAL: Free!

    function looping() {
    for (i = 0; i < 10; i++) {
    console.log('FUNCTION: ' + i);
    }
    }

    looping();
    // FUNCTION: 0
    // FUNCTION: 1
    // ...
    // FUNCTION: 9

    console.log('GLOBAL: ' + i);
    // GLOBAL: 10

    So, if you don't explicitly define the `i` variable inside the function using the `var` keyword, it will use the variable in the parent scope.

    One construct with it's own scope though is `with`:
    i = 'outside';
    a = {i : 'inside', j : 'extra'};

    with (a) {
    console.log(i); // inside
    console.log(j); // extra
    }

    console.log(i); // outside
    console.log(j); // ReferenceError: j is not defined


    Cheers! :)

    P.S.: Shame on me for not reading all the comments before writing all these... I missed the part with "Maybe next time I should point out clearly that I know exactly [...] why are all those things happening" :D

    ReplyDelete
  17. yes very funny and a good way to play with such a language

    ReplyDelete