Reputation: 5708
There must be something I don't understand about the JS object model.
From these resources:
I have gathered what I think, or thought, was an accurate mental representation of the object model. Here it is:
All objects have a property, which the docs refer to as [[Prototype]]
. [[Prototype]]
can be thought of as a reference to the object's parent. More accurately:
The reference to the [parent's] prototype object is copied to the internal
[[Prototype]]
property of the new instance. (source 1)
You can get access to the [[Prototype]]
property of the child with Object.getPrototypeOf(child)
The value returned here will be a reference to the parent's prototype (not its internal [[Prototype]]
property, but its actual prototype)
obj.prototype
is different from the object's internal [[Prototype]]
property. It acts like the blueprints used to make instances of this exact object, while its [[Prototype]]
property points to the blueprints used to make instances of its parent.
Parent.prototype === Object.getPrototypeOf(child); //true
To elaborate:
If you add a function to child.prototype
the function will be available to child
and any of it's children.
If you add a function to parent.prototype
, which is equivalent to adding a function to Object.getPrototypeOf(child)
, then the function will be available to parent
and all of it's children, which includes child
and all of its siblings
.
You can use Object.create()
to create a new object with whatever [[Protoype]]
property you want. So you can use it as a way to implement inheritance. See source 2 for an example.
With this in mind, I wanted to get a working example of my own going. My plan was to create a parent 'class' and then make a child 'class' that inherited from it.
I wanted the child class to implement a method, which overloaded a method from the parent. The caveat is that I want the child's version of the method to call the parent's version of the method and then do some extra stuff.
Here is what I came up with, see below for the issues associated with it:
var Parent = function() {};
Parent.prototype.myF = function() {
console.log('derp');
};
function Child() {
Parent.call(this);
};
//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);
//need to explicitly set the constructor
Child.prototype.constructor = Child;
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
childInstance = new Child();
childInstance.myF();
It appears to be the case that when I attempt to overload Parent.myF()
, while I am overloading it, I am actually modifying the original function at the same time. This appears to be the case because the logged results are:
'derp'
'did I derp??'
'did I derp??'
presumably the first occurance of 'did I derp??'
is coming from a modified version of the parent's function, which I don't mean to do, then the second version is coming from the child's function.
Can anyone elaborate on why this is happening?
Upvotes: 13
Views: 8567
Reputation: 462
If you have to work with inheritance in ES5 I have made a wrapper that makes it easy to extend and call parent methods. No frameworks required.
fiddle to working code here: https://jsfiddle.net/wcrwLmrk/13/
Here is how the code works:
/* extend the base class to the new parent class and pass
an object with the new properties and methods */
var ParentClass = Class.extend(
{
// reset the constructor to the correct constructor
constructor: function()
{
},
callName: function(arg)
{
console.log('parent ' + arg);
}
});
/* extend the parent class to the new child class and pass
an object with the new properties and methods */
var ChildClass = ParentClass.extend(
{
// reset the constructor to the correct constructor
constructor: function()
{
ParentClass.call(this);
},
callName: function(arg)
{
// child method code
console.log('child ' + arg);
/* call parent method by passing the method name and any params */
this.super('callName', arg);
}
});
Now we can create a new child class that will call the super class method:
var child = new ChildClass();
child.callName('name');
Here is the base class that needs to be included in the project:
var Class = function()
{
};
Class.prototype =
{
constructor: Class,
/* this is an alias for callParent. this will
allow child classes to call super methods.
@param (string) method name
@param [(mixed)] addasmany args as the super
method needs
@return (mixed) the super method return value */
super: function()
{
var parent = this.parent;
if(parent)
{
var args = [].slice.call(arguments),
// this will remove the first arg as the method
method = args.shift();
if(method)
{
var func = parent[method];
if(typeof func === 'function')
{
return func.apply(this, args);
}
}
}
return false;
}
};
/* this will return a new object and extend it if an object it supplied.
@param [(object)] object = the extending object
@return (object) this will return a new object with the
inherited object */
var createObject = function(object)
{
if(!Object.create)
{
var obj = function(){};
obj.prototype = object;
return new obj();
}
else
{
return Object.create(object);
}
};
/* this will extend an object and return the extedned
object or false.
@param (object) sourceObj = the original object
@param (object) targetObj = the target object */
var extendObject = function(sourceObj, targetObj)
{
if(typeof sourceObj !== 'undefined' && typeof targetObj !== 'undefined')
{
for(var property in sourceObj)
{
if(sourceObj.hasOwnProperty(property) && typeof targetObj[property] === 'undefined')
{
targetObj[property] = sourceObj[property];
}
}
return targetObj;
}
return false;
};
var extendClass = function(sourceClass, targetClass)
{
/* if we are using a class constructor function
we want to get the class prototype object */
var source = (typeof sourceClass === 'function')? sourceClass.prototype : sourceClass,
target = (typeof targetClass === 'function')? targetClass.prototype : targetClass;
if(typeof source === 'object' && typeof target === 'object')
{
/* we want to create a new object and add the source
prototype to the new object */
var obj = createObject(source);
/* we need to add any settings from the source that
are not on the prototype */
extendObject(source, obj);
/* we want to add any additional properties from the
target class to the new object */
for(var prop in target)
{
obj[prop] = target[prop];
}
return obj;
}
return false;
};
/* this will allow the classes to be extened.
@param (object) child = the child object to extend
@return (mixed) the new child prototype or false */
Class.extend = function(child)
{
if(!child)
{
return false;
}
var parent = this.prototype;
/* the child constructor must be set to set
the parent static methods on the child */
var constructor = child && child.constructor? child.constructor : false;
if(child.hasOwnProperty('constructor') === false)
{
constructor = function()
{
var args = arguments;
parent.constructor.apply(this, args);
};
}
constructor.prototype = extendClass(parent, child);
constructor.prototype.parent = parent;
/* this will add the static methods from the parent to
the child constructor. */
extendObject(this, constructor);
return constructor;
};
Upvotes: 0
Reputation: 27174
Great question, it took a bit of testing and researching to find it out.
I changed your code a little bit to find out which function is called when:
var Parent = function() {};
Parent.prototype.myF = function() {
console.log('derp');
};
function Child() {
Parent.call(this);
this.name = 'Test'; // this is a new test property
};
//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);
//need to explicitly set the constructor
Child.prototype.constructor = Child;
Child.prototype.myF = function() {
console.log(this); // here I want to find out the context, because you use it in the next line
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
childInstance = new Child();
childInstance.myF();
You can check out the JSFiddle and try it for yourself: http://jsfiddle.net/Lpxq78bs/
The crucial line in your code is this:
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF(); // this one
console.log("did I derp??");
};
After doing a console.log(this);
to find out what this
refers to, I saw that it changes between the first and the second output of did I derp??
.
I got the following output:
Object { name: "Test" }
Object { constructor: Child(), myF: window.onload/Child.prototype.myF() }
"derp"
"did I derp??"
"did I derp??"
Since I added a 'name' property to the Child
constructor, it would only be around if I am looking at an instance of Child
, not at its .prototype
.
So the first line of the Output means that the current this
context is indeed the childInstance
. But the second one is neither the childInstance
, nor the Parent.prototype
:
Call (myF
of childInstance
): this
refers to the childInstance
. Object.getPrototypeOf(this).myF();
then looks for the [[Prototype]]
of the childInstance
, which is the Child.prototype
, not the Parent.prototype
.
Output: 'did I derp??'
Call (myF
of Child.prototype
): this
refers to the Child.prototype
, which is the childInstances
[[Prototype]] Property. So the second call of Object.getPrototypeOf(this).myF();
finally returns the Parent.prototype
(sort of). Output: 'did I derp??'
Call (myF
of Parent.prototype
instance created by Object.create
): Finally, the myF
on the parent is called. Output: 'derp'
Since your console.log("did I derp??")
comes after the myF
function call, the output is in reverse order. The following graphic illustrates how the code is traversed:
So your assumption about what Object.getPrototypeOf(this).myF();
refers to, was wrong.
By @LukeP: https://jsfiddle.net/Lpxq78bs/28/
To avoid this confusion, and since you are working with a classical inheritance pattern, you could have a look at ES6 Classes. The following would be a rough example of what you are trying to do:
class Parent {
constructor() {
}
myF() {
console.log('derp');
}
}
class Child extends Parent {
constructor() {
super();
}
myF() {
super.myF();
console.log("did I derp??");
}
}
var childInstance = new Child();
childInstance.myF();
I hope this helps in understanding what happens.
Upvotes: 6
Reputation: 31920
Your code is working as expected , the output you are getting is because of Object.getPrototypeOf
and can be explained by
Step 1 : When you cal childInstance.myF();
then it goes to below code where this
refers to childInstance itself
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
then Object.getPrototypeOf
returns childInstance.[[Prototype]]
which is Child.prototype
and again call myF
method(leaving second line to printed later after the execution of the method) but next time this
will reference to childInstance.[[Prototype]]
.
Step 2 : In this call this
points to childInstance.[[Prototype]]
( or Child.prototype
object itself)
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
Now Object.getPrototypeOf
returns childInstance.[[Prototype]].[[Prototype]]
( which is Child.prototype.[[Prototype]]
) which is Parent.prototype
and again call myF method but this time myF method will print derp because myF method of Parent.prototype
is being called. After that, second line will print did i derp.
Step 3 : Then the function comes back to step 1 to execute second line which again prints did i derp
Upvotes: 6