Why to avoid creating objects of primitives in JavaScript?
Solution 1:
The statement "avoid creating objects" on its own is absurd in JavaScript, which has objects everywhere and is one of the most object-oriented languages in existence. But "avoid creating object versions of primitives," which is what the code you quote does, is valid. That is, avoid new String
, new Number
, and new Boolean
.
JavaScript has both primitive and object versions of strings, numbers, and booleans. There's almost never any reason to create the object version of any of them explicitly, and doing so can indeed lead to confusion; see inline comments:
var s1, s2, n1, n2;
// These are false because with ===, an object is never equal to a non-object
s1 = new String("hi");
s2 = "hi";
console.log(s1 === s2); // false
n1 = new Number(42);
n2 = 42;
console.log(n1 === n2); // also false
// These are false because even with ==, two *different* objects are never equal
// (even if they're equivalent)
s1 = new String("what the...");
s2 = new String("what the...");
console.log(s1 == s2); // also false
n1 = new Number(42);
n2 = new Number(42);
console.log(n1 == n2); // also false
The object versions of strings, numbers, and booleans largely exist to enable methods on primitives to be provided using the same mechanism that provides methods to object types. When you do
console.log("foo".toUpperCase()); // "FOO"
a temporary object is created for the primitive string "foo"
, and then the toUpperCase
property is read from that object. Since the object inherits from String.prototype
, it has toUpperCase
and all is well. Once the operation is done, the temporary object is thrown away (unless something keeps a reference to it, but nothing does and nothing can with toUpperCase
, you'd have to add a method to String.prototype
that returned the object in order for it to be kept around).
Solution 2:
It changes the intuitive way the operators behave with numbers, strings and booleans:
- the strict comparison (
===
) breaks when any of the numbers is constructed, so42 === 42
is true, while42 === new Number(42)
is not, - the abstract comparison (
==
) breaks when both numbers are objects, so42 == new Number(42)
is true, whilenew Number(42) == new Number(42)
is not, - the
typeof
operator gives different result when a number is constructed, sotypeof(42)
isnumber
, buttypeof(new Number(42))
isobject
, - when converted to a boolean,
0
is false, butnew Number(0)
is true, so the following two will have different behavior:
var a = 0;
if (a)
console.log("not zero");
else
console.log("zero!"); // "zero!"
var b = new Number(0);
if (b)
console.log("not zero"); // "not zero"
else
console.log("zero!");
So, avoid new Number
, new String
and new Boolean
.
Apart from that, there is the issue of using / not using new
with constructors. It stems from several facts:
- in JS, a constructor is a regular function, using
this.foo
syntax to add new properties and methods; - when invoked without the
new
keyword,this
becomes the global object, leading to side effects.
As a result, a tiny mistake can have catastrophic effects:
color = "blue";
var Fruit = function(color) {
this.color = color;
return this;
};
var apple = new Fruit("green");
console.log(apple.color); // "green" -- okay
console.log(color); // "blue" -- okay
var banana = Fruit("yellow");
console.log(banana.color); // "yellow" -- okay
console.log(color); // "yellow" -- wait, what?
console.log(banana.apple); // "{ color: 'green' }" -- what??
console.log(banana.document); // "{ location: [Getter/Setter] }" -- what???
(That's why some people resort to adding explicit checks in the constructor, or using closures instead. But that's for another story.)