Why doesn't JavaScript warn me when I use arr.lenght (misspelt) instead of arr.length in a loop? I also use strict mode

Javascript

Javascript Problem Overview


I spent hours just to find out that I misspelt the word .length as .lenght. It can run normally with no warning at all. Why...?

I use 'use strict' and run on Node.js 10.13.0.

Code:

'use strict';
let arr = [1, 2, 3, 4];
for(let i = 0; i < arr.lenght; i++) {
  console.log(arr[i]);
}

Javascript Solutions


Solution 1 - Javascript

Because when you try to get a property that doesn't exist, it returns undefined, and 0 < undefined is false.

let arr = [1, 2, 3, 4];
console.log(arr.lenght) // undefined
console.log(arr.qwerty) // undefined
console.log(arr.lenght < 9999) // false
console.log(arr.lenght > 9999) // false

arr.length = 7 // <-- it's not a good idea
for(let i = 0; i < arr.length; i++) {console.log(arr[i])}

EDIT

I said 'javascript is not a strongly typed language' and it is true. But this way of adding new properties it is a feature of prototype-based programming, as @Voo said.

I also said .length=7 it's a bad idea. After reading a little more, in this case I still think it's a little weird to increase the length property after adding elements. Maybe it's fine to truncate, delete elements or empty an array, although in the latter case I would prefer arr=[] instead of arr.length=0.

There are some interesting examples about length property in the Mozilla documentation.

> A JavaScript array's length property and numerical properties are > connected. Several of the built-in array methods (e.g., join(), > slice(), indexOf(), etc.) take into account the value of an array's > length property when they're called. Other methods (e.g., push(), > splice(), etc.) also result in updates to an array's length property. > > var fruits = []; > fruits.push('banana', 'apple', 'peach'); > console.log(fruits.length); // 3 > > When setting a property on a JavaScript array when the property is a > valid array index and that index is outside the current bounds of the > array, the engine will update the array's length property accordingly: > > fruits[5] = 'mango'; > console.log(fruits[5]); // 'mango' > console.log(Object.keys(fruits)); // ['0', '1', '2', '5'] > console.log(fruits.length); // 6 > > Increasing the length. > > fruits.length = 10; > console.log(Object.keys(fruits)); // ['0', '1', '2', '5'] > console.log(fruits.length); // 10 > > Decreasing the length property does, however, delete elements. > > fruits.length = 2; > console.log(Object.keys(fruits)); // ['0', '1'] > console.log(fruits.length); // 2

Solution 2 - Javascript

JavaScript arrays are treated as objects (though they are instances of Array). Hence, when you write arr.lenght, it treats lenght as a property of an object that is undefined. Hence, you don't get an error.

It simply tries to get a property that is undefined. Also, in your case, the loop just does not execute as the condition of the loop is never satisfied.

Solution 3 - Javascript

Why

Standard JavaScript arrays aren't really arrays at all¹, they're objects, and if you read an object property that doesn't exist (like lenght), you get the value undefined (even in strict mode):

console.log(({}).foo); // undefined

When you use undefined in a relational operation like < or > with a number, it gets converted to a number, but the number value it gets is the special number NaN, which has the bizarre property of always causing comparisons to be false:

console.log(NaN < 0);     // false
console.log(NaN > 0);     // false
console.log(NaN === 0);   // false
console.log(NaN === NaN); // false!!

What you can do about it

Linter tools will often pick these things up in simple cases.

Alternately, TypeScript provides a full static typing layer on top of JavaScript which can catch these sorts of errors.

If you wanted (and this would probably be overkill), you could wrap a Proxy around your objects that threw a proactive error when you tried to read a property that didn't exist:

function proactive(obj) {
  return new Proxy(obj, {
    get(target, propName, receiver) {
      if (!Reflect.has(target, propName)) {
        throw new TypeError(`Property '${propName}' not found on object`);
      }
      return Reflect.get(target, propName, receiver);
    }
  });
}

const a = proactive(["a", "b"]);
a.push("c");
for (let i = 0; i < a.length; ++i) {
  console.log(a[i]);
}
console.log(`Length is: ${a.lenght}`); // Note the typo

.as-console-wrapper {
  max-height: 100% !important;
}

There's a significant runtime penalty, though.


¹ (that's a post on my anemic little blog)

Solution 4 - Javascript

You could easily add new properties to arr object, JavaScript won't warn you about it, instead it will try to find the property you're calling, and if it didn't find anything such result will be undefined, so the comparison is actually i < undefined everytime because you're calling a property that hasn't been created on the object. I'll suggest you to read https://stackoverflow.com/questions/1335851/what-does-use-strict-do-in-javascript-and-what-is-the-reasoning-behind-it.

Solution 5 - Javascript

The upper bound of the loop is specified as lenght, a typo for the local variable length. At runtime, lenght will evaluate to undefined, so the check 0 < undefined is false. Therefore the loop body is never executed.

Solution 6 - Javascript

By default, all objects in JavaScript are extensible, which means that you can add additional properties to them at any time simply by assigning a value to them.

Arrays are no different; they're simply objects that are instances of the Array type (at least for the purposes of extensibility).

In this case, had you added:

Object.preventExtensions(arr);

after creating the array, then in combination with 'use strict' this would have raised an error -- had you tried to write to a typo'd property. But for a read usage like this, there is still no error at all; you just get undefined.

This is just one of the things you have to live with in a loosely-typed language; with the added flexibility comes added risk of bugs if you're not careful.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMangoLatoView Question on Stackoverflow
Solution 1 - Javascripteag845View Answer on Stackoverflow
Solution 2 - JavascriptRohan DharView Answer on Stackoverflow
Solution 3 - JavascriptT.J. CrowderView Answer on Stackoverflow
Solution 4 - JavascriptmmontoyaView Answer on Stackoverflow
Solution 5 - JavascriptfulvioView Answer on Stackoverflow
Solution 6 - JavascriptMiralView Answer on Stackoverflow