Understanding functions and constructors in JavaScript is essential, as all objects in the language are essentially objects. However, there are certain distinctions that make some objects more special than others, especially built-in objects. The key differences revolve around:
- General treatment of objects. For example:
- Numbers and Strings are immutable constants, meaning they cannot be changed internally. Any attempts to alter them will result in new objects being created. While these types have inherent methods, you cannot modify or add new ones.
null
and undefined
are particularly unique objects. Using methods on them or trying to define new methods will lead to exceptions.
- Applicable operators. In JavaScript, operators cannot be redefined, so you're limited to what's available.
- Numbers interact with arithmetic operators (
+
, -
, *
, /
) in a specific manner.
- Strings handle the concatenation operator (
+
) differently.
- Functions have their own way of dealing with operations like "call" (
()
) and the new
operator. The latter has an intrinsic understanding of utilizing the constructor function's prototype
property to create an object with correct internal links to the prototype and correctly setting up this
.
The ECMAScript standard defines additional functionality such as methods and properties, many of which may not be directly accessible by programmers. Some features might be revealed in the upcoming revision ES3.1 (draft dated 15 Dec 2008), with one property (__proto__
) already exposed in Firefox.
To address your query directly - yes, function objects possess properties that can be added or removed at will:
var fun = function() { /* ... */ };
fun.foo = 2;
console.log(fun.foo); // 2
fun.bar = "Ha!";
console.log(fun.bar); // Ha!
Irrespective of the function's actual implementation, adding properties to it doesn't impact its execution because it isn't invoked. To exemplify this:
fun = function() { this.life = 42; };
When called without the new
operator, the function operates within the context provided:
var context = { ford: "perfect" };
// invoking our function within the context
fun.call(context);
// no new object was generated; instead, the context got updated:
console.log(context.ford); // perfect
console.log(context.life); // 42
console.log(context instanceof fun); // false
In order to employ the function as a constructor, the new
operator needs to be used:
var baz = new fun();
// creates a new empty object and executes fun() on it:
console.log(baz.life); // 42
console.log(baz instanceof fun); // true
The new
operator transforms our function into a constructor, executing the following steps:
- A new empty object (
{}
) is created.
- The object's internal prototype property is set to
fun.prototype
. If unaltered, it remains an empty object ({}
).
fun()
is run within the context of this fresh object.
It's the responsibility of the function to manipulate the new object, commonly by defining its properties but also having the freedom for other actions as needed.
Interesting tidbits:
Constructors being mere objects allows for nifty calculations:
var A = function(val) { this.a = val; };
var B = function(val) { this.b = val; };
var C = function(flag) { return flag ? A : B; };
// creating an object using C(true)
var x = new (C(true))(42);
// inspecting x
console.log(x instanceof C); // false
console.log(x instanceof B); // false
console.log(x instanceof A); // true
// examining properties
console.log(x.a); // 42
console.log(x.b); // undefined
// creating another object using C(false)
var y = new (C(false))(33);
// examining y
console.log(y instanceof C); // false
console.log(y instanceof B); // true
console.log(y instanceof A); // false
// checking properties
console.log(y.a); // undefined
console.log(y.b); // 33
A constructor can override the newly created object by returning a distinct value:
var A = function(flag) {
if (flag) {
return { ford: "perfect" };
}
this.life = 42;
};
// creating two instances
var x = new A(false);
var y = new A(true);
// inspecting x
console.log(x instanceof A); // true
console.log(x.ford); // undefined
console.log(x.life); // 42
// inspecting y
console.log(y instanceof A); // false
console.log(y.ford); // perfect
console.log(y.life); // undefined
We observe that x
maintains the prototype chain while y
represents the returned object from the constructor.