64

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]);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
MangoLato
  • 682
  • 7
  • 15
  • 6
    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. 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. – mmontoya Dec 03 '18 at 03:13
  • 6
    Simple answer is...because it's a loosely typed language. Everyone has made property name typos. Lick your wounds and move on – charlietfl Dec 03 '18 at 03:20
  • 48
    Because you aren't using TypeScript. – Ian Kemp Dec 03 '18 at 08:04
  • Yes, the main issue is that you're using a flavor of JavaScript without Strong Typing. As [any language without Strong Typing (and Explicit Nullability) will become archaic](https://twitter.com/NachoSoto/status/1062180478113898496), you should consider using [TypeScript](https://www.typescriptlang.org/) by Microsoft. Or consider a typechecker like [Flow](https://flow.org/) by Facebook, or [Tern](https://ternjs.net/), or a linter like [ESLint](https://eslint.org/). – Cœur Dec 03 '18 at 11:36
  • 6
    It also does not warn you about `window.sdjkhednrgj` for example. This just evaluates to undefined. (Is this good language design? It has up and downsides. Can certainly cause brittleness.) – usr Dec 03 '18 at 11:38
  • Thank you, I always enjoy reading JS WTFs. – Eric Duminil Dec 03 '18 at 12:35
  • 2
    If it would give a warning about these things, it would also give a warning every time you wrote `if (someObject.someProperty==undefined)` when the property was undefined... – Mr Lister Dec 03 '18 at 14:44
  • Not a direct answer but Javascript supports for loops for iterable objects. Would maybe be better to use such a "for in" or "for of loop" instead. Then you don't have to worry about checking the bounds of the array at all. – Benjamin Scherer Dec 04 '18 at 09:08
  • This is why you write unit tests, irregardless if strongly or loosely typed languages. Even compiled strong type languages can miss things “like” this. – Zach Leighton Dec 04 '18 at 18:37

6 Answers6

71

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
eag845
  • 1,013
  • 1
  • 11
  • 16
  • 4
    Setting the `.length` of an array is a very common idea and there's nothing wrong with it. – Bergi Dec 03 '18 at 08:27
  • 5
    PHP is also not a strongly typed language, yet it gives you a warning when you access a non-existing property. JavaScript, on the other hand, has no standard way to produce runtime warnings. – GOTO 0 Dec 03 '18 at 08:37
  • @Bergi what would be your goal with this, in JavaScript? – Jacob Raihle Dec 03 '18 at 10:16
  • 3
    @JacobRaihle To [empty](https://stackoverflow.com/q/1232040/1048572), otherwise truncate, or (rarely) preallocate it. – Bergi Dec 03 '18 at 10:33
  • @Bergi Thanks, I wouldn't have expected that to work. – Jacob Raihle Dec 03 '18 at 12:33
  • 8
    @GOTO0 Yep, and you just summed up for me the irony in the way the dev world has been fawning over JavaScript for the last few years while at the same time pouring hate on PHP. – Spudley Dec 03 '18 at 13:22
  • 3
    Whether you get an error on accessing a non-existent property or not, has nothing to do if the language is strongly typed or not. This is more a feature of prototype based OOP languages. – Voo Dec 03 '18 at 14:18
  • I see no warnings in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length about setting the length... It does say that it will truncate/extend the array with an example of the behaviour. While the answer is great, the "[...] you can even set arr.length=7 but it's not a good idea" bit seems innacurate. – Ismael Miguel Dec 03 '18 at 15:20
  • I wonder how often it's more useful to have `thing.memberName` syntax yield `undefined` when the name doesn't exist? IMHO, strict mode would have been more useful if an attempt to read `thing.memberName` would trap if the member name didn't exist, and code wanting the old semantics would use `thing["memberName"]`; I wonder if the people creating the "strict mode" rules considered that, and if so what reasons they had for not doing it. – supercat Dec 03 '18 at 19:51
  • can anything be said about deallocation / lifetimes between `array = undefined` and `array.length = 0`? `.length = 0` will likely not deallocate `array`? or is it completely implementation-defined? – cat Dec 03 '18 at 21:22
  • 1
    Prototyping has nothing to do with what you get when *reading* an undefined property (creating on assignment would be a fair point, though still not universal). – Michael Homer Dec 03 '18 at 21:48
14

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rohan Dhar
  • 1,827
  • 1
  • 10
  • 21
8

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)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
6

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 What does "use strict" do in JavaScript, and what is the reasoning behind it?.

mmontoya
  • 403
  • 3
  • 9
5

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.

gotnull
  • 26,454
  • 22
  • 137
  • 203
1

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.

Miral
  • 12,637
  • 4
  • 53
  • 93