Why doesn't JavaScript warn me when I use arr.lenght (misspelt) instead of arr.length in a loop? I also use strict mode
JavascriptJavascript 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.