I am currently learning to program JavaScript/ECMAScript, and I'm reading the book "Professional JavaScript for Web Developers, 3rd Edition". I'm enjoying the book, and at the same time I look for some support on the Internet with materials that describe the same subject. Like all OO programmers coming out from one language to learn another, I am having difficulties. I am currently in Chapter 6, studying about inheritance (PAG. 201, 202 - in case anyone have the book, and would like to see this part of it), and I am confused, getting unfocused.
I quite understand the mechanics behind the subject ... But I did not understand why. For example, the author of the book shows the following case (some lines were slightly adjusted with comments and separation):
// SUPERTYPE
function SuperType()
{
this.property = true;
}
SuperType.prototype.getSuperValue = function()
{
return this.property;
};
// SUBTYPE
function SubType()
{
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function ()
{
return this.subproperty;
};
// TEST
var instance = new SubType();
alert(instance.getSuperValue()); //true
I understand that in ECMAScript, instances of a type inherit properties and methods configured on its prototype, and it is through prototype property that inheritance is possible. Therefore, changing the prototype of one type to another type will cause behaviors and attributes to be reused.
What's driving me headless, is the fact assign a new instance of a type A as a prototype of a type B, where it is understood that B may have instances, as well as A too (not that this should happen, but it is allowed to happen).
What I mean by that? Well ... Imagine that I have a supertype named "Vehicle" and a subtype named "Car". If I use an instance of "Vehicle" to serve as supertype of "Car" (after all, a subtype does not know his supertype, but only its prototype - as PAG. 185 from the book says), I'm creating something totally abstract to serve as something concrete. Also, I'm using what is just one of perhaps many possible instances of a abstract supertype (?), since nothing prevents the supertype in future to have more instances of itself in this type of code, adding more code/methods/properties/etc.
My real question is ... This is it anyway? Kind ... Developers should worry about associating the correct instance, and only one of them?
Also, I have some doubts about the prototype chain. For example, I know that when a property/method is requested/used, it is first sought in the object, and then its prototype (if its not found). What happens visibly to the contexts in this story? I mean, the search is made only in the prototype chain of objects without running any context? I read earlier that when a variable is used, it is sought in stack of contexts. What is the relationship between the stack of contexts and the prototype chain?
I know I may be being a little boring/annoying to ask this, but can anyone explain me this in a simple and detailed way, please?
Thank you in advance for your patience, time, and attention.
As some people are really not understanding what I'm wondering, I apologize. Here is the summary of all:
Create new instances of A, where A is supertype of B, is something allowed and possible, even if A follows certain abstraction? And if this is allowed, what are its consequences? And where is the stack of contexts in between when something (property or method) is used by instances?
To summarize more: Although I am asking questions, realize that I am asking the implications of inheritance. Just that...
JavaScript has two big concepts that you seem to be touching on here, Closure/Scope and Prototyping/inheritance. These work differently most other modern languages so can cause confusion for people crossing over to JavaScript from a more classical language.
Basically everything in JavaScript is an Object. In JavaScript, Objects have something called a prototype which is a reference to a parent Object which may have properties inherited by the descendant Objects. The prototype may also be null
, which happens at the top end of the prototype chain.
The usual structure in prototypes is to have more specific objects inherit from more abstract objects, all the way back up to Object.prototype
or null
When looking up a property on an object, you start from that object and if it doesn't have the property, you go up one level in the prototype chain and look again. If you reach null
then there is no such property.
This also means you can shadow inherited properties by having properties of the same name lower down the prototype chain.
function Foo() {
}
Foo.prototype = {};
Foo.prototype.foobar = 'foo';
function Bar() {
// if required
// Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype); // inhert
Bar.prototype.foobar = 'bar';
var baz = new Bar();
baz.foobar; // "bar"
baz instanceof Bar; // true
baz instanceof Foo; // true
A prototype can be any object, so there is nothing illegal about using an instance of another object to set up a prototype chain, however it's often considered bad practice if you're trying to extend or create a subtype
This is how variables and identifiers are looked up. It's unrelated to the prototype and is entirely dependent on where something was defined. Identifiers are "trapped" inside a scope and other scopes can "close over" them.
In JavaScript the main providers of scope are functions
var a = 1;
function foo() {
var b = 2;
// foo can see `a` and `b` as well as `fizz`, `foo`, `bar` and `baz`
function bar() {
var c = 3;
// bar can see `a`, `b` and `c` as well as `fizz`, `foo`, `bar` and `baz`
}
function baz() {
var d = 4;
// baz can see `a`, `b` and `d` as well as `fizz`, `foo`, `bar` and `baz`
}
}
function fizz() {
var e = 1;
// fizz can see `a` and `e` as well as `fizz` and `foo`
}
However, if you were to create a property on foo
that tried to lookup bar
, you would get a ReferenceError (or undefined), similarly if you tried to inherit foo
foo.meth = function () {return bar;};
foo.meth(); // undefined
function Hello() {
this.bar; // undefined
}
Hello.prototype = foo;
(new Hello()).bar; // undefined
The only way to access bar
is to have foo
do it for you, e.g. by return bar;
at the end of foo
If bar
was returned by foo
, then the variables foo
could see "live on" in bar
. A smart garbage collector may still clean up unreachable ones if there is no eval-style code in bar
.
Just as variables within a function are not shared across multiple invocations, these variables are not shared across multiple foo
invocations, however they will be shared across multiple bar
invocations
function foo() {
var a = 0,
b = -1;
function bar() {
return ++a;
}
return bar; // pass `bar` out of `foo`
}
var fn1 = foo(), fn2 = foo();
// by this point, the `b` created by `foo()` may already be cleaned up
fn1(); // 1
fn1(); // 2
fn2(); // 1, it's a different `a` to the `a` in `fn1`
fn2(); // 2