I am trying to understand the way that javascript passes functions around and am having a bit of a problem groking why a prototype function can NOT access a var defined in a function constructor while a function defined in the constructor can access the var. Here is code that works:
var model = function model() {
this.state = 1;
this.GetState = (function(scope){
return function(){ return scope.state;};
})(this);
}
var othermodel = function othermodel(mdl) {
this.GetStateFn = mdl.GetState;
}
othermodel.prototype.WriteState = function() {
console.log(this.GetStateFn.call());
};
var m = new model();
var o = new othermodel(m)
o.WriteState();
This works and makes sense - the GetState() function can access this.state.
However, if I create GetState as follows:
model.prototype.GetState = (function(scope){
return function(){ return scope.state;};
})(this);
The result will be an error that scope is not defined.
I would prefer to have this work with the prototype method as I do not want a copy of the function in ever model, but it would seem that prototype can't work because it can't access the specific instance of the model.
So, can someone provide me with a good explanation of a) what I need to do to get this to work with prototype (assuming I can) and b) if I can't get it to work with prototype, what is the reason so I can understand better the underpinnings of the issue.
Why not simply write the function this way
model.prototype.GetState = function() { return this.state; }
var othermodel = function othermodel(mdl) {
this.GetStateFn = mdl.GetState.bind(mdl);
}
othermodel.prototype.WriteState = function() {
console.log(this.GetStateFn.call());
};
The above code will work, as in most cases you will execute code like m.GetState()
. That is an example of invoking a function as an object method. In that case, this
is guaranteed to point to the object m
. You seem to know how the prototype chains work, so I won't go there.
When assigning the function reference to the other model, we use the .bind
to ensure that within GetState
, this
points to mdl
. Reference for bind
: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Your original IIFE's were in effect your implementation of bind. The issue was the value of this
was wrong. Currently, every time you need to assign models function to some other function, you will need to use bind
at all those times. You have tagged your question as node.js, bind
is available on the Function
prototype in node.js and any ES5 compatible browser. If you need to run the above code on older browsers or environments that do not support bind
, replace bind
with your IIFE.
As for why your code isn't working,
model.prototype.GetState = (function(scope){
return function(){ return scope.state;};
})(this);
Here, this
doesn't refer to the eventual model object (m
). this
can refer to any one of 5 options in javascript. Refer: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/this
Lets assume the above code is in an html file inside some script tag. Then this
will refer to the window object. window
doesn't have any property called state
, hence the undefined
. If you were to console.log(this.m, this.o)
at the end of you script, you would see the respective m
and o
objects.
When defined like this:
var model = function model() {
this.state = 1;
this.GetState = (function(scope){
return function(){ return scope.state;};
})(this);
}
the anonymous function is declared and immediately executed. this
is passed as a parameter to that self-executing function. As a result - a new function is returned, but this function has scope
parameter in its closure - so that it is accessible after the scope has exited. As a result - the function, when invoked, can still access state
property of that "enclosed" this
(the one that became scope
and was closed upon).
If you define it like this:
model.prototype.GetState = (function(scope){
return function(){ return scope.state;};
})(this);
the mechanism is the same, it's just this
is not. It is now the context of the scope you execute the above code in. Assuming it's done in global scope - it would be window
object.
If you don't want to use bind
because of its support with older browsers, you could try this:
var model = function (state) {
this.state = state || new Date().getTime();
};
model.prototype.GetState = function () {
return this.state;
};
model.prototype.WriteState = function () {
console.log("model WriteState: " + this.GetState());
};
var othermodel = function othermodel (mdl) {
this.GetStateFn = function () {
return mdl.GetState.call(mdl);
};
};
othermodel.prototype.WriteState = function () {
console.log("othermodel WriteState: " + this.GetStateFn());
};
var model1 = new model();
model1.WriteState();
var othermodel1 = new othermodel(model1);
othermodel1.WriteState();
var model2 = new model();
model2.WriteState();
var othermodel2 = new othermodel(model2);
othermodel2.WriteState();
Seems to do what you want without bind
. I created the model.prototype.WriteState
for testing purposes.
It depends on where it is called. If it's in global scope, this
will not refer the the model. If it's running in a browser it will refer to the global window
object instead.