Adding new properties to constructor function without .prototype
Adding a clog.alert
function would simply a attach static function to the clog
object. It will not be inherited and will not have access to the instance created with new clog();
in the alert function.
Adding clog.prototype.alert
will make the new clog();
object you create inherit the function, and you will also have access to the instance inside using the this
keyword.
function John() {
this.id = 1;
}
John.doe = function() {
console.log(this);
console.log(this.id); // undefined
}
John.prototype.doe = function() {
console.log(this);
};
John.doe(); // the John object
var me = new John();
me.doe(); // the instance, inherited from prototype
console.log(me.id); // 1
Instances created by a constructor function (clog
in your case) inherit a reference to the clog.prototype
object. So if you add a property to clog.prototype
, it will show up on instances. If you add a property to clog
itself, it will not show up on instances.
There are some issues with your quoted code, so let's look at an abstract example:
function Foo() {
}
Foo.prototype.bar = "I'm bar on Foo.prototype";
Foo.bar = "I'm bar on Foo";
var f = new Foo();
console.log(f.bar); // "I'm bar on Foo.prototype"
// E.g., `f` inherits from `Foo.prototype`, not `Foo`
// And this link is live, so:
Foo.prototype.charlie = "I'm charlie on Foo.prototype";
console.log(f.charlie); // "I'm charlie on Foo.prototype";
From your comment below:
I don't understand why new properties added directly to
Foo
would be ignored by the prototype chain?
Because it's Foo.prototype
, not Foo
, that is the prototype for objects created via new Foo()
.
isn't
prototype
simply points to the constructor object?
No, Foo
and Foo.prototype
are completely distinct objects. Foo
is a function object, which like all function objects can have properties. One of Foo
's properties is prototype
, which is a non-function object that's initially blank other than a constructor
property that points back to Foo
. It's Foo.prototype
, not Foo
, that instances created via new Foo
get as their prototype. Foo
's only role is to create objects that use Foo.prototype
as their prototype. (Actually, in Foo
's case, it just initializes those objects; they're created by the new
operator. With a traditional function like Foo
, new
creates the object. If this code were using ES2015+ class
syntax, new
wouldn't create the object, it would leave that to Foo
[if Foo
were a base class constructor] or Foo
's ultimate base class [if Foo
were a subclass constructor].)
If I do
Foo.newProp = "new addition"
why isf.newProp => undefined
?
(To avoid confusion, I've changed Foo.new = ...
to Foo.newProp = ...
above, since new
is a keyword. While you can use it like you did as of ES5, it's best not to.)
Because Foo.newProp
has virtually nothing to do with f
. You can find it, on f.constructor.newProp
, since f.constructor
is Foo
.
Some ASCII-art:
Given this code:
function Foo() {
}
Foo.prototype.bar = "I'm bar on Foo.prototype";
Foo.bar = "I'm bar on Foo";
we have these objects with these properties (some omitted for clarity):
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | | V +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ +−−>| [String] | | | Foo [Function] | | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ | | "I'm bar on Foo" | | | bar |−−−−+ +−−−−−−−−−−−−−−−−−−+ | | prototype |−−−−+ | +−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−+ | | | V | +−−−−−−−−−−−−−+ | | [Object] | | +−−−−−−−−−−−−−+ | | constructor |−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | bar |−−−−−>| [String] | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | "I'm bar on Foo.prototype" | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
Now if we do
var f = new Foo();
we have (new stuff in bold):
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | | V +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ +−−>| [String] | | | Foo [Function] | | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ | | "I'm bar on Foo" | | | bar |−−−−+ +−−−−−−−−−−−−−−−−−−+ | | prototype |−−−−+ | +−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+ | | | V | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+ | | f [Object] | +−−−−−>| [Object] | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | [[Prototype]] |−−−−−−−−−−+ | constructor |−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ | bar |−−−−>| [String] | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | "I'm bar on Foo.prototype" | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
([[Prototype]] is an object's internal field referring to its prototype. This is accessible via Object.getPrototypeOf
[or __proto__
on JavaScript engines on web browsers, but don't use __proto__
, it's just for backward compatibility with old SpiderMonkey-specific code.)
Now suppose we do this:
f.charlie = "I'm charlie on f";
All that changes is the f
object (new stuff in bold):
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | | V +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ +−−>| [String] | | | Foo [Function] | | +−−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+ | | "I'm bar on Foo" | | | bar |−−−−+ +−−−−−−−−−−−−−−−−−−+ | | prototype |−−−−+ | +−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+ | | | V | +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+ | | f [Object] | +−−−−−>| [Object] | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ | | [[Prototype]] |−−−−−−−−−−+ | constructor |−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | charlie |−−−−−−−−−−+ | bar |−−−−−>| [String] | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | | "I'm bar on Foo.prototype" | | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−−−+ +−−−−−>| [String] | +−−−−−−−−−−−−−−−−−−−−+ | "I'm charlie on f" | +−−−−−−−−−−−−−−−−−−−−+
f
now has its own property, called charlie
. This means that these two statements:
console.log(f.charlie); // "I'm charlie on f"
console.log(f.bar); // "I'm bar on Foo.prototype"
Get processed slightly differently.
Let's look at f.charlie
first. Here's what the engine does with f.charlie
:
- Does
f
have its own property called"charlie"
? - Yes; use the value of that property.
Simple enough. Now let's look at how the engine handles f.bar
:
- Does
f
have its own property called"bar"
? - No; does
f
have a prototype? - Yes; does
f
's prototype have a property called"bar"
? - Yes; use the value of that property.
So there's a big difference between f.charlie
and f.bar
: f
has its own property called charlie
, but an inherited property called bar
. And if f
's prototype object hadn't had a property called bar
, its prototype object (in this case, Object.prototype
) would be checked, and so on up the chain until we run out of prototypes.
You can test whether a property is an "own" property, btw, using the hasOwnProperty
function that all objects have:
console.log(f.hasOwnProperty("charlie")); // true
console.log(f.hasOwnProperty("bar")); // false
Answering your question from the comments:
I make
function Person(first_name, last_name) {this.first_name = first_name; this.last_name = last_name;}
and thenvar ilya = new Person('ilya', 'D')
how does it resolves the innername
properties?
Within the call to Person
that's part of the new Person(...)
expression, this
refers to the newly-generated object that will be returned by the new
expression. So when you do this.prop = "value";
, you're putting a property directly on that object, nothing to do with the prototype.
Putting that another way, these two examples result in exactly the same p
object:
// Example 1:
function Person(name) {
this.name = name;
}
var p = new Person("Fred");
// Example 2:
function Person() {
}
var p = new Person();
p.name = "Fred";
Here are the issues with the quoted code I mentioned:
Issue 1: Returning something from a constructor function:
function clog(x){
var text = x;
return console.log(text ); // <=== here
}
99.9999% of the time, you don't want to return anything out of a constructor function. The way the new
operation works is:
- A new blank object is created.
- It gets assigned a prototype from the constructor's
prototype
property. - The constructor is called such that
this
refers to the new object. - If the constructor doesn't return anything, or returns something other than an object, the result of the
new
expression is the object created in step 1. - If the constructor function returns an object, the result of the
new
operation is that object instead.
So in your case, since console.log
doesn't return anything, you just remove the return
keyword from your code. But if you used that return xyz();
construct with a function that returned an object, you'd mess up your constructor function.
Issue 2: Calling functions rather than referring to them
In this code:
clog.prototype.alert = alert(text);
you're calling the alert
function and assigning the result of it to a property called alert
on clog.prototype
. Since alert
doesn't return anything, it's exactly equivalent to:
alert(text);
clog.prototype.alert = undefined;
...which probably isn't what you meant. Perhaps:
clog.prototype.alert = function(text) {
alert(text);
};
There we create a function and assign a reference to it to the alert
property on the prototype. When the function is called, it will call the standard alert
.
Issue 3: Constructor functions should be initially capped
This is just style, but it's overwhelmingly standard: Constructor functions (functions meant to be used with new
) should start with an upper case letter, so Clog
rather than clog
. Again, though, this is just style.
Any property added to the constructor will act as a static property that can only be accessed by referring to the constructor object (ie, function) and not using any instance object of it. Its like a Class property and not an instance property.