Referring to Class instantiater in javascript [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 4 years ago.
I have this method of a JavaScript class that I've created:
resetRule() {
let sheetRules = Array.from(this.sheet.rules);
sheetRules.forEach(function(node, i) {
if(node.name != undefined) {
newRule.sheet.deleteRule(i);
}
});
}
when you instantiate a class, it essentially has to be set to a variable, like so:
const newRule = new NewRule(*params*);
and the methods/properties of said class can refer to the class object using this. like so:
this.method();
this.property;
What I'd like to know is: how does one refer back to the variable that instantiated the class within a function that is called by a method of said class?
To be even clearer: a function that is called within a method of a class alters the scope, which also means it alters the definition of this.. My question is: how do you get around this? How could you access the variable that instantiated the class when you are out the of scope of the methods within said class?
As I was composing this question, I realized that you can set the this value for a .forEach loop like this:
resetRule() {
let sheetRules = Array.from(this.sheet.rules);
sheetRules.forEach(function(node, i) {
if(node.name != undefined) {
this.sheet.deleteRule(i);
}
}, this);
}
However, the way this code works is something that--as far as I know--is just a benefit of the .forEach method, and I'd still like to know how it should be handled in general.

Hopefully this should help you out, using your example.
class Rule {
constructor(rules) {
this.sheet = {
rules: rules || []
}
}
log(){
console.log('rules:',this.sheet.rules)
}
resetRule() {
let sheetRules = Array.from(this.sheet.rules);
let self = this; // <-- here you can capture the instance
sheetRules.forEach(function(node, i) {
self.log() // <-- here you can use it in forEach
if (node.name != undefined)
this.sheet.deleteRule(i);
});
}
}
const fooRule = new Rule(['foo'])
const barRule = new Rule(['bar'])
fooRule.resetRule()
barRule.resetRule()
fooRule.log()
barRule.log()
Your forEach works because as you discovered, you passed this as an argument for the thisArg parameter. However, if you didn't, you could have just as easily set it to a variable in the outer scope and used it in the block scope.
Generally creating a variable called self or that and setting it to this is helpful, especially for arrow functions, which set this to the encapsulating scope and not the instance object.

You could make a temporary variable called self or something which would allow you to use both the object containing "this" instance as well as within that anonymous function passed to forEach, this which will refer to sheetRules when you don't specify another variable to use as this
resetRule() {
let sheetRules = Array.from(this.sheet.rules);
let self = this;
sheetRules.forEach(function(node, i) {
if(node.name != undefined) {
self.sheet.deleteRule(i);
}
});
}

If I've not misunderstood you're looking for a way to retain your scope? You have a few options here.
The immediate answers would be to use Function.prototype.bind() or Function.prototype.call() to specify the context of this when invoking those methods.
Alternatively you could just make the scope of your this available where needed:
var MyClass = function () {
// Here we bind the local scope to a variable that will give us context where necessary.
// While it's not needed here, it can give context and set a pattern of reability through repitition.
var vm = this;
vm.methodA = function () {
// We continue to set our 'vm' pointer variable when needed.
var vm = this;
globalMethod.then(function () {
// We're able to retain context of our `this` through having scope of our 'vm' variable.
vm.methodB();
});
};
vm.methodB = function () {
console.log('I did stuff!');
};
};

Related

What is happening when using the keyword this in lambdas vs functions with Typescript [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I am using Typescript for an Angular 2 project. I notice that when we use the keyword this inside a labmda expression vs a function, this refers to different things.
For example, let's say I have an Angular component like the following.
export class MyComponet {
isActive = true;
names = [ "john", "jeff", "jared" ];
doSomethingWithLambda() {
names.forEach( (value, index) => {
if(this.isActive) { //this.isActive refers to MyComponent.isActive
//do something...
}
});
}
doSomethingWithFunction() {
names.forEach( function(value, index) {
if(this.isActive) { //this.isActive is undefined, since this refers to the function
//do something
}
});
}
doSomethingWithFunction2() {
let isActive = this.isActive;
names.forEach( function(value, index) {
if(isActive) { //if we change isActive will this also change MyComponent.isActive?
//do something
}
});
}
}
What is really happening here (behind the scene, so to speak)? What's the magic behind this inside a lambda that makes it able to "correctly" refer to the outer class' fields? I understand this inside a function will refer to the function itself.
Also, I have a doSomethingWithFunction2 method that will reference MyComponent.isActive into a local variable. If I change that local variable, that should be like changing the one it references, right? (regardless of it being a "primitive" like integer/number or an "object" like JSON { })
The fat-arrow function syntax is shorthand for:
function () { }.bind(this);
bind is a Javascript ES5 method equivalent to this:
Function.prototype.bind = function bind(context) {
var func = this;
return function () {
return func.apply(context, arguments);
};
}
In regards to
Also, I have a doSomethingWithFunction2 method that will reference MyComponent.isActive into a local variable. If I change that local variable, that should be like changing the one it references, right? (regardless of it being a "primitive" like integer/number or an "object" like JSON { })
In Javascript, variables are like pointers and except for some limited cases (primitives and copy-on-write objects) will change the referenced value when mutated. Reassigning will not change the original value, e.g. isActive = false; but this.isActive = false would in fact re-assign the variable isActive on this which is now hopefully correctly assigned.
This has to do with how lambda function are implement in TS. this in arrow function is lexically inferred so it more in line with below code
function Person() {
var self = this; // Some choose that instead of self.
// Choose one and be consistent.
self.age = 0;
setInterval(function growUp() {
// The callback refers to the self variable of which
// the value is the expected object.
self.age++;
}, 1000);
}
So inside the lambda function it is not actually this but a context closer self. This may not be actual implementation but much closer to give you understanding of what is happening.
Now when you are outside the my impotent this refers to global var which would be window
The lambda function is similar to javascripts bind function.
Protip see your transpiled JS how your lambda function is transforming.

What is a best practice for ensuring "this" context in Javascript?

Here's a sample of a simple Javascript class with a public and private method (fiddle: http://jsfiddle.net/gY4mh/).
function Example() {
function privateFunction() {
// "this" is window when called.
console.log(this);
}
this.publicFunction = function() {
privateFunction();
}
}
ex = new Example;
ex.publicFunction();
Calling the private function from the public one results in "this" being the window object. How should I ensure my private methods are called with the class context and not window? Would this be undesirable?
Using closure. Basically any variable declared in function, remains available to functions inside that function :
var Example = (function() {
function Example() {
var self = this; // variable in function Example
function privateFunction() {
// The variable self is available to this function even after Example returns.
console.log(self);
}
self.publicFunction = function() {
privateFunction();
}
}
return Example;
})();
ex = new Example;
ex.publicFunction();
Another approach is to use "apply" to explicitly set what the methods "this" should be bound to.
function Test() {
this.name = 'test';
this.logName = function() {
console.log(this.name);
}
}
var foo = {name: 'foo'};
var test = new Test();
test.logName()
// => test
test.logName.apply(foo, null);
// => foo
Yet another approach is to use "call":
function Test() {
this.name = 'test';
this.logName = function() {
console.log(this.name);
}
}
var foo = {name: 'foo'};
var test = new Test();
test.logName()
// => test
test.logName.call(foo, null);
// => foo
both "apply" and "call" take the object that you want to bind "this" to as the first argument and an array of arguments to pass in to the method you are calling as the second arg.
It is worth understanding how the value of this in javascript is determined in addition to just having someone tell you a code fix. In javascript, this is determined the following ways:
If you call a function via an object property as in object.method(), then this will be set to the object inside the method.
If you call a function directly without any object reference such as function(), then this will be set to either the global object (window in a browser) or in strict mode, it will be set to undefined.
If you create a new object with the new operator, then the constructor function for that object will be called with the value of this set to the newly created object instance. You can think of this as the same as item 1 above, the object is created and then the constructor method on it is called.
If you call a function with .call() or .apply() as in function.call(xxx), then you can determine exactly what this is set to by what argument you pass to .call() or .apply(). You can read more about .call() here and .apply() here on MDN.
If you use function.bind(xxx) this creates a small stub function that makes sure your function is called with the desired value of this. Internally, this likely just uses .apply(), but it's a shortcut for when you want a single callback function that will have the right value of this when it's called (when you aren't the direct caller of the function).
In a callback function, the caller of the callback function is responsible for determining the desired value of this. For example, in an event handler callback function, the browser generally sets this to be the DOM object that is handling the event.
There's a nice summary of these various methods here on MDN.
So, in your case, you are making a normal function call when you call privateFunction(). So, as expected the value of this is set as in option 2 above.
If you want to explictly set it to the current value of this in your method, then you can do so like this:
var Example = (function() {
function Example() {
function privateFunction() {
// "this" is window when called.
console.log(this);
}
this.publicFunction = function() {
privateFunction.call(this);
}
}
return Example;
})();
ex = new Example;
ex.publicFunction();
Other methods such as using a closure and defined var that = this are best used for the case of callback functions when you are not the caller of the function and thus can't use 1-4. There is no reason to do it that way in your particular case. I would say that using .call() is a better practice. Then, your function can actually use this and can behave like a private method which appears to be the behavior you seek.
I guess most used way to get this done is by simply caching (storing) the value of this in a local context variable
function Example() {
var that = this;
// ...
function privateFunction() {
console.log(that);
}
this.publicFunction = function() {
privateFunction();
}
}
a more convenient way is to invoke Function.prototype.bind to bind a context to a function (forever). However, the only restriction here is that this requires a ES5-ready browser and bound functions are slightly slower.
var privateFunction = function() {
console.log(this);
}.bind(this);
I would say the proper way is to use prototyping since it was after all how Javascript was designed. So:
var Example = function(){
this.prop = 'whatever';
}
Example.prototype.fn_1 = function(){
console.log(this.prop);
return this
}
Example.prototype.fn_2 = function(){
this.prop = 'not whatever';
return this
}
var e = new Example();
e.fn_1() //whatever
e.fn_2().fn_1() //not whatever
Here's a fiddle http://jsfiddle.net/BFm2V/
If you're not using EcmaScript5, I'd recommend using Underscore's (or LoDash's) bind function.
In addition to the other answers given here, if you don't have an ES5-ready browser, you can create your own "permanently-bound function" quite simply with code like so:
function boundFn(thisobj, fn) {
return function() {
fn.apply(thisobj, arguments);
};
}
Then use it like this:
var Example = (function() {
function Example() {
var privateFunction = boundFn(this, function() {
// "this" inside here is the same "this" that was passed to boundFn.
console.log(this);
});
this.publicFunction = function() {
privateFunction();
}
}
return Example;
}()); // I prefer this order of parentheses
Voilà -- this is magically the outer context's this instead of the inner one!
You can even get ES5-like functionality if it's missing in your browser like so (this does nothing if you already have it):
if (!Function.prototype.bind) {
Function.prototype.bind = function (thisobj) {
var that = this;
return function() {
that.apply(thisobj, arguments);
};
}:
}
Then use var yourFunction = function() {}.bind(thisobj); exactly the same way.
ES5-like code that is fully compliant (as possible), checking parameter types and so on, can be found at mozilla Function.prototype.bind. There are some differences that could trip you up if you're doing a few different advanced things with functions, so read up on it at the link if you want to go that route.
I would say assigning self to this is a common technique:
function Example() {
var self = this;
function privateFunction() {
console.log(self);
}
self.publicFunction = function() {
privateFunction();
};
}
Using apply (as others have suggested) also works, though it's a bit more complex in my opinion.
It might be beyond the scope of this question, but I would also recommend considering a different approach to JavaScript where you actually don't use the this keyword at all. A former colleague of mine at ThoughtWorks, Pete Hodgson, wrote a really helpful article, Class-less JavaScript, explaining one way to do this.

The meaning of `this` in JS template method pattern

Why does the marked line fail to find protectedACMember?
var Module = (function (ns) {
function AbstractClass() {
this.protectedACMember = "abstract";
this.abstractPublicACMethod = function (input) {
this.methodToImplement();
}
}
ConcreteClass.prototype = new AbstractClass();
function ConcreteClass(){
var privateCCMember = "private CC";
var privateCCMethod = function(){
alert(this.protectedACMember); // cant find protectedACMember
}
this.methodToImplement = function(){
privateCCMethod();
console.log('Implemented method ');
}
}
ns.ConcreteClass = ConcreteClass;
return ns;
})(Module || {});
//somewhere later
var cc = new Module.ConcreteClass();
cc.abstractPublicACMethod();
are there any good patterns for simulating private, protected and public members? Static/non-static as well?
You should change that part of code like this:
var self = this;
var privateCCMethod = function(){
alert(self.protectedACMember); // this -> self
}
This way you get the reference in the closure.
The reason is, that "this" is a reserved word, and its value is set by the interpreter. Your privateCCMethod is an anonymous function, not the object property, so if you call it simply by privateCCMethod() syntax, this will be null.
If you'd like "this" to be bound to something specific you can always use .call syntax, like this:
privateCCMethod.call(this)
Another way to ensure that this means what you want is to use bind. Bind allows you to ensure a function is called with a specific value of this.
Most newer browsers support it (even IE9!) and there's a workaround for those that don't.
Bind - MDN Documentation
It fails to find protectedACMember because what the this keyword means changes when you enter the function privateCCMethod. A common practice is to store the outer this for use inside the functions:
function ConcreteClass(){
var privateCCMember = "private CC";
// store the outer this
var that = this;
var privateCCMethod = function(){
alert(that.protectedACMember);
}
...
The rest of your questions are fairly loaded and should probably be posted as a separate question.

JavaScript event methods inherited from prototype objects

I feel like what I have is correct code, but obviously I am missing something here.
What I am trying to do is create an event method in the prototype object of my constructor. Here is what I have so far:
function Controls(but) {
this.but = document.getElementById(but);
this.but.onclick = function() {
displayMessageTwo();
}
}
Controls.prototype.displayMessageTwo = function() {
alert("HELLO");
}
var Main = new Controls('testingTwo');
My logic here is that I am creating a constructor from which to build controls for something (let's say a slideshow).. this.but equals the html element of a link called whatever is passed as an argument to the constructor.
In my prototype object, I define my method and then create my object. However, this is not working as I had expected.
What am I doing wrong here?
I suspect that when the event handler fires, the context of the invocation is not the instance on which you registered the callback.
Try something like the following
function Controls(but) {
var that = this;
this.but = document.getElementById(but);
this.but.onclick = function() {
that.displayMessageTwo(); // that is closed-in
}
}
What am I doing wrong here?
You are calling displayMessageTwo(); as if it was a global function. It is not, it is a inherited property on your instance. Usually you would refer to the instance with the this keyword, but inside the event handler you can't. Create a variable referencing the object, and call that one's method like so:
function Controls(but) {
this.but = document.getElementById(but);
var that = this;
this.but.onclick = function() {
that.displayMessageTwo();
}
}
As your displayMessageTwo method does not care about its context (does not reference other properties via this), you even might assign it directly:
this.but.onclick = this.displayMessageTwo;
But I'd recommend to avoid that, methods should always be executed with correct thisValue. You also might use bind:
this.but.onclick = this.displayMessageTwo.bind(this);
but it needs additional code for older, non-supporting browsers.
I would think:
function Controls(but) {
this.but = document.getElementById(but);
this.but.onclick = this.displayMessageTwo;
}
Controls.prototype.displayMessageTwo = function() {
alert("HELLO");
}
var Main = new Controls('testingTwo');
is clearer (assigning the function assigned to prototype.displayMessageTwo). If this doesn't work it may be because
this.but.onclick = this.displayMessageTwo;
is evaluated before:
Controls.prototype.displayMessageTwo = function() {
making it null...

Binding "this" when passing an object's member function

I had a "class" defined and was making only one instance of it. The instance possessed a member function that would end up being passed around (it's a mouse handler, but that's not important). Since I would only ever make one instance of my "class", I decided to rewrite it as a singleton by using an object literal.
So I have
var mySingleton = {
theObjects : [];
}
mySingleton.mouseHandler = (function() {
var that = this;
return function (e) {
for (var indx = 0; indx < that.theObjects.length; indx++) {
// do something to that.theObjects[indx];
}
}
}());
mySingleton.addObject = function(newObj) {
this.theObjects.push(newObj);
}
However, when I try to use this code (after adding a few objects), I keep getting an that.theObjects is undefined error. It's referring to the line in the for loop.
Update for 2015 – Use Function.bind() to specify the value of this within the function. Then, instead of using that, you can use this.
mySingleton.mouseHandler = function (e) {
for (var indx = 0; indx < this.theObjects.length; indx++) {
// do something to this.theObjects[indx];
}
}.bind(mySingleton);
This doesn't work if you want mouseHandler to have the context of the 'moused' element. For that, use my original answer below.
If you need to support IE8 or (heaven forbid) earlier, you'll need to use a polyfill.
Since you are calling the function that creates mouseHandler immediately, it is run in the context of window, not mySingleton. So that refers to window. Instead of calling it immediately, just change it to a method so that it runs in the context of mySingleton:
mySingleton.getMouseHandler = function() {
var that = this;
return function() { ... };
};
myElement.onclick = mySingleton.getMouseHandler();
Of course, since you are already using a singleton, you can just reference it directly. In your click handler, instead of checking that.theObjects, check mySingleton.theObjects. Or, in mouseHandler change var that = this to var that = mySingleton.
Edit: Or, pass the context to your anonymous function when you call it:
mySingleton.mouseHandler = (function() {
var that = this;
return function (e) {
for (var indx = 0; indx < that.theObjects.length; indx++) {
// do something to that.theObjects[indx];
}
}
}).call(mySingleton);
There are a few popular ways to do this. First, super-simple solution is just reference mySingleton directly and bypass the confusion associated with this. Instead of that.theObjects just do mySingleton.theObjects and move on with your life and things will work fine.
However, there is a common pattern to do this binding. Here's how underscore.js does it
Check out the annoted source to underscore, where you will find this
_.bind = function(func, obj) {
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
var args = slice.call(arguments, 2);
return function() {
return func.apply(obj, args.concat(slice.call(arguments)));
};
};
The other answers here so far are also correct. Providing my viewpoint here in case it helps.
The key to understanding why the code doesn't behave as you expect requires understanding how this works in JavaScript. The problem is that this depends on how the function is called.
First, if you call the function in the method style, this is what you'd expect:
mySingleton.mouseHandler(); // this === mySingleton
If you attach the function to something esle, that works too.
var anotherSingleton = {};
anotherSingleton.foo = mySingleton.mouseHandler;
anotherSingleton.foo(); // this === anotherSingleton
If you detach the function, this becomes the global scope object (window)
var foo = mySingleton.mouseHandler;
foo(); // this === window
And finally, you can force this to be something else using call or apply:
var randomThingy = {};
mySingleton.mouseHandler.call(randomThingy); // this === randomThingy
The takeaway is that this is determined at runtime based on the context of how the function was called. Often, frameworks that allow you to make "classes" abstract these details from you by implicitly applying the bind pattern on your behalf. This is why it used to work, and no longer does.
As others have mentioned, you can change your handler to reference the variable by its scoped name (mySingleton) or otherwise bind it as discussed.
Here's an article I wrote on the subject a few years ago that goes into more detail: http://trephine.org/t/index.php?title=Understanding_JavaScript%27s_this_keyword
Hope this helps!

Categories

Resources