Losing object "this" context in a method [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I am doing some excercise, and I have issue with losing "this" context in a pipe method:
function Main() {
// something
this.render = function () {
this.groups.forEach(function(g){
renderGroup(g);
});
return this;
};
// something
this.pipe = function () {
this.render(); // 1
requestAnimationFrame(this.pipe); // 2
return this;
};
// something
}
Ad.1: that cause "this is undefined, so it has no render function"
Ad.2: if above commented, still "this" context is undefined so pipe is not a function
initialization:
function init () {
var m = new Main();
requestAnimationFrame(m.pipe);
}
window.onload = function () {
init();
}
full object code:
function Main() {
this.canvas = document.createElement("canvas");
this.canvas.width = 1366;
this.canvas.height = 768;
this.canvas.style.width = this.canvas.width + "px";
this.canvas.style.height = this.canvas.height + "px";
this.groups = [];
window.app = {};
this.grain = 30 * 1000 * 60;
this.grainWidth = 30;
this.getGroups = function () {return this.groups;}
this.render = function () {
this.groups.forEach(function(g){
renderGroup(g);
});
return this;
};
this.ctx = this.canvas.getContext("2d");
this.pipe = function () {
this.render();
requestAnimationFrame(this.pipe);
return this;
};
document.body.appendChild(this.canvas);
registerEvents();
}
the renderGroup is plain forEach.
What causes the context lost?

Simply bind the context you want pipe to be called with
this.pipe = function () {
this.render(); // 1
requestAnimationFrame(this.pipe); // 2
return this;
}.bind(this)

Maybe something like this?
requestAnimationFrame(this.pipe.bind(this));
JavaScript functions get this defined when they're called. You use pipe as callback, so the context for it becomes window since it's called from window.requestAnimationFrame.

Related

Why can't I call a class function inside the same class function in Javascript? [duplicate]

This question already has answers here:
Using this inside an event handler
(2 answers)
Closed 3 years ago.
It says this.draw is not defined but I have defined draw in the same class. Why can't I call a class function inside another class function?
function Recorder() {
this.recording = false;
this.sideLength = 5;
this.currList = new SLinkedList(comparator);
this.curr = null;
}
Recorder.prototype = {
constructor:Recorder,
draw : function (xPos, yPos) {
if(recording) {
currList.addEnd([xPos,yPos]);
}
let context = document.getElementById("canvas").getContext("2d");
let getColorPickerByID = document.getElementById("colors");
let getValueOfColorPicker = getColorPickerByID.options[getColorPickerByID.selectedIndex].text;
context.fillStyle = getValueOfColorPicker;
context.fillRect(xPos,yPos,sideLength,sideLength);
},
processMousePosition : function (evt){
this.draw(evt.pageX, evt.pageY);
}
};
Give your class a method named handleEvent. Make this function check the evt.type and call the appropriate method for that event.
function Recorder() {
this.recording = false;
this.sideLength = 5;
this.currList = new SLinkedList(comparator);
this.curr = null;
}
Recorder.prototype = {
constructor:Recorder,
handleEvent : function(evt) {
switch (evt.type) {
case "mousemove":
this.processMousePosition(evt);
break;
}
},
draw : function (xPos, yPos) {
// your draw code
},
processMousePosition : function (evt){
this.draw(evt.pageX, evt.pageY);
}
};
Then when you go to add the listener to the element, pass your instance of Recorder instead of its methods. This will cause the handleEvent method to be invoked when the event occurs.
var r = new Recorder();
myElement.addEventListener("mousemove", r);

How to set the context of the `this` inside a then callback on a promise

When I call resolve() on a Promise, the scope which the function inside the then() is binded to window.
There's any way to set the context of the this like using the Function.apply method?
function Point(){
var that = this;
var _x = 0;
this.setX = function(x){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
_x = x;
resolve.apply(that); //<== set this
}, 1000);
});
}
this.getX = function(){
return _x;
}
}
var p = new Point();
p.setX(10).then(function(){
console.log(this.getX()); //this === `window`
});
EDIT:
Elaborating, with synchronous code you can do method chaining, by simply returning the same object over and over.
//this pattern
obj.method1();
obj.method2();
...
//becomes this pattern
obj.method1(10).method2(11) ...
Implementation of chaining
method1 = function(x){
return this;
}
When it comes to assync, you can still do the same thing with callbacks
obj.method1(10, function(){ this.method2(11, function(){ ...
Implementation with callbacks
method1 = function(x, cb){
cb.apply(this);
}
I don't get it why would someone bind the"receiver" function to window, it doesn't make sense to me, as promises are supposed to be analogous to synchronous calls.
Option 1:
You can pass the instance into the resolve function. Then reference it via the callback as the first argument.
function Point() {
var that = this;
var _x = 0;
this.setX = function(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
_x = x;
resolve(that); //<== set this
}, 1000);
});
}
this.getX = function() {
return _x;
}
}
var p = new Point();
p.setX(10).then(function(scope) {
console.log(scope.getX());
});
Option 2:
You can bind the scope of the callback:
var p = new Point();
p.setX(10).then(function () {
console.log(this.getX()); //this === `window`
}.bind(p)); // bind the scope here
function Point() {
var that = this;
var _x = 0;
this.setX = function(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
_x = x;
resolve.apply(that); //<== set this
}, 1000);
});
};
this.getX = function() {
return _x;
}
}
var p = new Point();
p.setX(10).then(function() {
console.log(this.getX()); //this === instance of Point
}.bind(p));

How to access object properties from prototype in javascript? [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
I have class below when I call printData I get this.collection is undefined.
How do I access this.collection from the prototype inside printData()? Or do i need to change the class structure. Actually the object returns function which intern returns object in hierarchy.
Thanks in advance!
Sample Class:
var DbProvider = (function () {
function DbProvider(db) {
var that = this; // create a reference to "this" object
that.collection = db;
}
DbProvider.prototype.create = function () {
return {
action: function () {
var y = {
printData: function () {
alert('Hello ' + this.collection.Name);
}
};
return y;
}
};
};
return DbProvider;
})();
Usage:
var a = new DbProvider({ "Name": "John" });
a.create().action().printData();
You could save the this reference and bind it to the printData function
var DbProvider = (function () {
function DbProvider(db) {
var that = this; // create a reference to "this" object
that.collection = db;
}
DbProvider.prototype.create = function () {
var self = this;
return {
action: function () {
var y = {
printData: function () {
alert('Hello ' + this.collection.Name);
}.bind(self)
};
return y;
}
};
};
return DbProvider;
})();
var a = new DbProvider({ "Name": "John" });
a.create().action().printData();
Or you could refactor a bit and move that to the outer scope of DbProvider and use that in printData
var DbProvider = (function () {
var that;
function DbProvider(db) {
that = this; // create a reference to "this" object
that.collection = db;
}
DbProvider.prototype.create = function () {
return {
action: function () {
var y = {
printData: function () {
alert('Hello ' + that.collection.Name);
}
};
return y;
}
};
};
return DbProvider;
})();
var a = new DbProvider({ "Name": "John" });
a.create().action().printData();
just need to keep track of the this pointer correctly, like this
var DbProvider = (function() {
function DbProvider(db) {
this.collection = db;
}
DbProvider.prototype.create = function() {
var self = this;
return {
action: function() {
var y = {
printData: function() {
alert('Hello ' + self.collection.Name);
}
};
return y;
}
};
};
return DbProvider;
})();
let dbProvider = new DbProvider({
Name: "test"
});
dbProvider.create().action().printData();
Keeping ES5 syntax and the call structure a solution would be:
var DbProvider = (function () {
function DbProvider(db) {
var that = this; // create a reference to "this" object
that.collection = db;
}
DbProvider.prototype.create = function () {
var that = this;
return {
action: function() {
var y = {
printData: function () {
console.log('Hello ' + that.collection.Name);
}
};
return y;
}
};
};
return DbProvider;
})();
Definitely not elegant but it works :)
If you do not want to change your structure, you can achieve this behavior if you change you functions to arrow functions.
var DbProvider = (function () {
function DbProvider(db) {
var that = this; // create a reference to "this" object
that.collection = db;
}
DbProvider.prototype.create = function() {
return {
action: () => {
var y = {
printData: () => {
alert('Hello ' + this.collection.Name);
}
};
return y;
}
};
};
return DbProvider;
})();
The way you are creating this "class" is definitely non standard. Let me know if you want an example of how to better structure it.

Cannot reference method on Javascript object

I'm writing a version of Pong in Javascript. I have a Game object and I'm using the prototype property to define methods on it. I'm getting the following error: "Undefined is not a function". It is being thrown in the Game.prototype.step function so this.update is undefined in there. Here's the code for the Game object:
(function(root) {
var Pong = root.Pong = (root.Pong || {});
var Game = Pong.Game = function() {
this.canvas = document.getElementById('canvas');
this.canvas.width = 800;
this.canvas.height = 400;
this.context = canvas.getContext('2d');
this.maxStartSpeed = 10;
this.keysDown = {};
this.player2 = new Pong.Player({'player': 2});
this.player1 = new Pong.Player({'player': 1});
this.ball = new Pong.Ball(400, 200);
}
Game.prototype.update = function() {
this.player1.update();
this.player2.update();
this.ball.update(player1.paddle, player2.paddle);
};
Game.prototype.render = function() {
this.context.fillStyle = "#bdc3c7";
this.context.fillRect(0, 0, width, height);
this.player1.render();
this.player2.render();
this.ball.render();
};
Game.prototype.animate = function(callback) {
window.setTimeout(callback, 1000/60)
};
Game.prototype.step = function() {
this.update();
this.animate(this.step);
};
window.addEventListener("keydown", function (event) {
Game.keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete Game.keysDown[event.keyCode];
});
window.onload = function() {
document.getElementById('canvas-container').appendChild(canvas);
game = new Game();
game.animate(game.step);
};
})(this);
The setTimeout is going to change the scope. To maintain the proper scope, you need to use bind
Change
this.animate(this.step);
to
this.animate(this.step.bind(this));
You need to do the same thing with the other animate calls.

How to define a repeating function in a Javascript prototype?

I'm trying to define a Javascript class with a repeating function but I can't get it to work:
var Repeater = function() {
this.init.apply(this, arguments);
};
Repeater.prototype = {
run: 0, // how many runs
interval: 5, // seconds
init: function() {
this.repeat();
},
repeat: function() {
console.log(++this.run);
setTimeout(this.repeat, this.interval * 1000);
}
};
var repeater = new Repeater();
How should this be done?
Try this code:
var Repeater = function() {
this.run = 0; // how many runs
this.interval = 5; // seconds
this.init.apply(this, arguments);
};
Repeater.prototype.init = function() {
this.repeat();
}
Repeater.prototype.repeat = function() {
var _this = this;
console.log(++this.run);
setTimeout(function () { _this.repeat() }, this.interval * 1000);
};
var repeater = new Repeater();
I've moved run and interval into constructor, because if you add this to prototype then this will be spread over all instances.
Your problem lies into seTimeout - in your code this timer set new scope for repeater and this was no longer pointing to Repeater instance but for Timeout instance. You need to cache this (I've called this cache _this) and call it into new function passed to setTimeout.
Try like that:
var Repeater = function() {
this.init.apply(this, arguments);
};
Repeater.prototype = {
run: 0, // how many runs
interval: 5, // seconds
init: function() {
this.repeat();
},
repeat: function() {
console.log(++this.run);
var that = this;
setTimeout(function() {that.repeat()}, this.interval * 1000);
}
};
var repeater = new Repeater();
You can read more on how this behaves in this question : How does the "this" keyword work?
Change your repeat function to use a closure in the setTimeout call like so:
repeat: function() {
var ctx = this;
console.log(++this.run);
setTimeout(function(){ctx.repeat()}, this.interval * 1000);
}
You need to set the context explicitly in these kinds of scenarios- that's what the ctx variable is for

Categories

Resources