Using 'This' within an IIFE constructor - javascript

I'm working on a small retro-style side-scrolling space shooter game (or, that's the theory anyway) and I've recently moved over to using IIFEs for managing my separate 'classes'.
However, most of the examples I've seen tend to use var when declaring variables, E.g, var x = 0. I'm wondering though, is it possible to use this.x = 0 and if so, are there any benefits or drawbacks?
I've tried googling it, and can't find much on the subject, which leads me to think it's a non-issue.
My classes are as follows;
var Player = function () {
// ------------------------------------------------------------------------------------------------
// PLAYER VARIABLES
// ------------------------------------------------------------------------------------------------
var w = 50;
var h = 50;
var x = 0;
var y = 0;
var color = 'white';
var projectiles = [];
// ------------------------------------------------------------------------------------------------
// BIND EVENTS TO THE GLOBAL CANVAS
// ------------------------------------------------------------------------------------------------
Canvas.bindEvent('mousemove', function(e){
y = (e.pageY - Canvas.element.getBoundingClientRect().top) - (h / 2);
});
Canvas.bindEvent('click', function(e){
createProjectile(50, (y + (h / 2)) - 10);
});
// ------------------------------------------------------------------------------------------------
// FUNCTIONS
// ------------------------------------------------------------------------------------------------
var createProjectile = function(x, y){
projectiles.push({
x: x,
y: y
})
};
var update = function(){
for(var p = projectiles.length - 1; p >= 0; p--){
projectiles[p].x += 10;
if(projectiles[p].x > Canvas.element.width)projectiles.splice(p, 1);
}
};
var render = function () {
Canvas.context.fillStyle = color;
Canvas.context.fillRect(x, y, w, h);
console.log(projectiles.length);
for(var p = 0; p < projectiles.length; p++){
Canvas.context.fillStyle = 'red';
Canvas.context.fillRect(projectiles[p].x, projectiles[p].y, 20, 20);
}
};
// ------------------------------------------------------------------------------------------------
// Exposed Variables and Functions
// ------------------------------------------------------------------------------------------------
return{
update: update,
render: render
}
}();

are there any benefits or drawbacks?
The drawbacks are that in strict mode, you will get a runtime error (because this is undefined).
In non-strict mode, this will refer to window, so this.x = ... creates a global variable (which is what you want to avoid with the IIFE in the first place I guess).
There are no benefits.

Related

Class method is not a function?

I'm getting "Uncaught TypeError: this.time_to_x is not a function" when incorporating some open source ES5 code into my ES6 Class. Here is the class (I've removed some of the bulk, but most of the essential stuff is there). Assume Diamond() is called. It's this line that gets the error: x = this.time_to_x(frame.time);
Why is time_to_x() not being considered a function?
export default class TimelinePanel {
constructor(ctx) {
this.ctx = ctx;
this.ctx_wrap = ctx;
}
create (ctx) {
this.rect({ctx, x: 20, y: 15, width: 130, height: 10}); // ***
this.drawLayerContents();
}
Diamond(frame, y) {
var x, y2;
x = this.time_to_x(frame.time);
y2 = y + LINE_HEIGHT * 0.5 - DIAMOND_SIZE / 2;
var self = this;
var isOver = false;
this.path = function() {
this.ctx_wrap
.beginPath()
.moveTo(x, y2)
.lineTo(x + DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2)
.lineTo(x, y2 + DIAMOND_SIZE)
.lineTo(x - DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2)
.closePath();
};
}
drawLayerContents() {
// ...
for (i = 0; i < il; i++) {
// ...
for (j = 0; j < values.length; j++) {
// Dimonds
frame = values[j];
renderItems.push(new this.Diamond(frame, y));
}
}
}
y_to_track(y) {
if (y - MARKER_TRACK_HEIGHT < 0) return -1;
return (y - MARKER_TRACK_HEIGHT + scrollTop) / LINE_HEIGHT | 0;
}
x_to_time(x) {
var units = time_scale / tickMark3;
return frame_start + ((x - LEFT_GUTTER) / units | 0) / tickMark3;
}
time_to_x(s) {
var ds = s - frame_start;
ds *= time_scale;
ds += LEFT_GUTTER;
return ds;
}
}
You are creating an instance of this.Diamond class when you do new this.Diamond(frame, y). As a result, inside the function, this is this new instance, not the instance of TimelinePanel where it has originally been created from. Hence, this does not have the members of TimelinePanel.
Because it seems y_to_track and x_to_time does not make use of this, you could make them static (add the keyword static before them) and call them as follow: TimelinePanel.y_to_track.
If you need to access methods bound to a particular instance of TimelinePanel, then I don't see any other solution than passing this instance to the Diamond constructor or refactoring TimelinePanel and use closure around the Diamond constructor.
In any case it seems you are trying to replicate the behavior of Java-like internal classes (e.g. where you can access the container class instance with ClassName.this or just access the container class members), there is no such things in JS (at least with class).
EDIT: I just noticed that you are accessing TimelinePanel's ctx_wrap member that you will not be able to put as class member. The easiest seem to pass the TimelinePanel to the Diamond constructor: Diamond(frame, y, panel) and new this.Diamond(frame, y, this). It puts into question the usefulness of adding Diamond as a member of TimelinePanel.
Because the way you have it it's supposed time_to_x from closure, not from this. In this there is no such function, so this.time_to_x name returns undefined which is not a function indeed.
I suggest smth like this:
put var self = this; inside the class but outside of the Diamond method.
Then call self.time_to_x() inside Diamond.

Define a unique method to an instance via prototype

I have an Object which called Rectangle :
function Rectangle(x, y) {
this.x = x;
this.y = y ;
this.surface = function(x, y) {
return x*y;
}
}
Rectangle.prototype.couleur = "Rouge";
and I have two instances of this Object :
r1 = new Rectangle(3, 5);
r2 = new Rectangle(4, 7);
Then I declared a third instance :
r3 = new Rectangle(6, 7);
and I want this instance to have a unique method :
afficheCouleur = function() {
return this.couleur;
}
I tried as this :
r3.prototype.afficheCouleur = function() {
return this.couleur;
}
But I got this error :
[11:32:40.848] TypeError: r3.prototype is undefined # file:///media/tpw/760F-F396/vv:24
Just declare the method directly:
r3.afficheCouleur = function() {
return this.couleur;
}
This happens because prototype is a property of constructors (functions) and r3 is an object.
If you do that you will end up with 2 Rectangles with a different implementation.
Create a second class that inherits from the first:
function Rectangle(x, y) {
this.x = x;
this.y = y ;
this.surface = function(x, y) {
return x*y;
}
}
Rectangle.prototype.couleur = "Rouge";
function ColourRectangle(x, y) {
Rectangle.apply(this, arguments);
this.afficheCouleur = function() {
return this.couleur;
}
}
ColourRectangle.prototype = new Rectangle();
ColourRectangle.prototype.constructor = Rectangle;
var a = new Rectangle(1, 2);
console.log(a.y); //2
console.log(a.afficheCouleur); //undef
var b = new ColourRectangle(3, 4);
console.log(b.y); //2
console.log(b.afficheCouleur()); // Rouge
Assuming that you might re factor your service function to use the Rectangle's x and y instead of passing them (why would you pass them if you want to know the Rectangle's surface?).
One problem you may have declaring service in the body like this is that it doesn't update x and y when you change x and y of your Rectangle instance. This because x and y are remembered in the closure scope.
You may end up with code like this:
function Rectangle(x, y) {
this.x = x;
this.y = y ;
this.surface = function() {
return x*y;
}
}
var r = new Rectangle(5,5);
console.log(r.surface());//=25
r.y =500;
console.log(r.surface());//=25/
console.log(r.x*r.y);//=2500
you should return this.x * this.y in the surface method but since you are not using closures to simulate private members you may as well put the function on the prototype.
function Rectangle(x, y) {
this.x = x;
this.y = y ;
}
Rectangle.prototype.surface = function() {
return this.x*this.y;
}
var r = new Rectangle(5,5);
console.log(r.surface());//=25
r.y =500;
console.log(r.surface());//=2500
console.log(r.x*r.y);//=2500
I know this doesn't answer your question but you are asking to add a method that will be shared among instances (prototype) uniquely on an instance. This can't be done as Danilo and Basha pointed out already.
The link posted in my comment to your question may help you understand better what prototype is, how the members there are used and what instance specific members are.

Why's this object not working?

box = new Object();
box.height = 30;
box.length = 20;
box.both = function(box.height, box.length) {
return box.height * box.length;
}
document.write(box.both(10, 20));
Well as the title says.
First off I'd created an object.
Made to properties, height and length.
Assigned a value to each.
Made a method BOTH
In function I'd put 2 arguments which are object properties.
Returned their product.
And finally called the function giving it numerical value..
Why's this not working :(
The problem is:
box.both=function(box.height,box.length){
box.height and box.length are not valid names for function parameters. This should be:
box.both=function(h, l) {
return h * l;
}
However, it seems you might be looking to get the area of the current box instance. In that case, you don't need any parameters:
box.both=function() {
return this.height * this.length;
}
document.write(box.both());
I think you probably want it this way:
box = new Object();
box.height = 30;
box.length = 20;
box.both = function(height,length){
this.height = height;
this.length = length;
return height*length;
}
document.write(box.both(10,20));
box = new Object();
box.height = 30;
box.length = 20;
box.both = function() {
return box.height * box.length;
}

When two objects are dependent on each other? How to de-couple them?

Bit of a generic question but non the less I am in a situation where I do not know what to do and google has failed me!
I am trying to re-write a grid array collision with canvas that I built.
Now there is a grid object and a block object. The grid cellSize is dependent on being the same size of the block size and vice versa. The reason being is that to work out the grid array to store the blocks into I must first work out how to build it and that is dependent on the size of the block. Example,
var grid = new grid();
function grid() {
this.cellSize = 50;
this.cellsX = canvas.width / this.cellSize;
this.cellsY = canvas.height / this.cellSize;
this.buildGrid = function() {
var arr = new Array(this.cellsX);
for(var i = 0; i < arr.length; ++i) {
arr[i] = new Array(this.cellsY);
}
return arr;
};
this.arr = this.buildGrid();
this.getGridCoords = function(i) {
return Math.floor(i / this.cellSize);
};
}
function block() {
this.size = grid.cellSize; // size of the block is same as cellSize
this.x = 0;
this.y = 0 - (this.size * 1.5);
this.baseVelocity = 1;
this.velocity = this.baseVelocity;
this.update = function() {
this.y += this.velocity;
};
}
Doing it the way I have done it couples the two objects together and from what I have percieved that is a bad thing. How can I make sure that the two variables are the same size without coupling the objects if that makes sense?
The real issue is that your block() function is taking a value directly from an instance of grid().
If you want your block() function to be reusable and decoupled, its as easy as changing block() to take the size during construction.
arr[i] = new block({size: this.cellSize});

Creating Mouse Event Handlers For Canvas Shapes

I'm coding a tile based game in javascript using canvas and was wondering how I could create a simple event handler for when the mouse enters the dimensions of a tile.
I've used jquery's http://api.jquery.com/mousemove/ in the past but for a very simple application but can't seem to wrap my head around how I'll do it in this case (quickly).
Hmm..
I started writing this post without a clue of how to do it, but I just tried using the jquery mousemove like I started above. I have a working version, but it seems 'slow' and very clunky. It's doesn't seem smooth or accurate.
I put all mode code into a js fiddle to share easily:
http://jsfiddle.net/Robodude/6bS6r/1/
so what's happening is:
1) jquery's mousemove event handler fires
2) Sends the mouse object info to the GameBoard
3) Sends the mouse object info to the Map
4) Loops through all the tiles and sends each one the mouse object
5) the individual tile then determines if the mouse coords are within its boundaries. (and does something - in this case, I just change the tiles properties to white)
but here are the sections I'm most concerned about.
$("#canvas").mousemove(function (e) {
mouse.X = e.pageX;
mouse.Y = e.pageY;
game.MouseMove(mouse);
Draw();
});
function GameBoard() {
this.Map = new Map();
this.Units = new Units();
this.MouseMove = function (Mouse) {
this.Map.MouseMove(Mouse);
};
}
function Map() {
this.LevelData = Level_1(); // array
this.Level = [];
this.BuildLevel = function () {
var t = new Tile();
for (var i = 0; i < this.LevelData.length; i++) {
this.Level.push([]);
for (var a = 0; a < this.LevelData[i].length; a++) {
var terrain;
if (this.LevelData[i][a] == "w") {
terrain = new Water({ X: a * t.Width, Y: i * t.Height });
}
else if (this.LevelData[i][a] == "g") {
terrain = new Grass({ X: a * t.Width, Y: i * t.Height });
}
this.Level[i].push(terrain);
}
}
};
this.Draw = function () {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].Draw();
}
}
};
this.MouseMove = function (Mouse) {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].MouseMove(Mouse);
}
}
};
this.BuildLevel();
}
function Tile(obj) {
//defaults
var X = 0;
var Y = 0;
var Height = 40;
var Width = 40;
var Image = "Placeholder.png";
var Red = 0;
var Green = 0;
var Blue = 0;
var Opacity = 1;
// ...
this.Draw = function () {
ctx.fillStyle = "rgba(" + this.Red + "," + this.Green + "," + this.Blue + "," + this.Opacity + ")";
ctx.fillRect(this.X, this.Y, this.Width, this.Height);
};
this.MouseMove = function (Mouse) {
if ((Mouse.X >= this.X) && (Mouse.X <= this.Xmax) && (Mouse.Y >= this.Y) && (Mouse.Y <= this.Ymax)) {
this.Red = 255;
this.Green = 255;
this.Blue = 255;
}
};
}
If you have a grid of tiles, then given a mouse position, you can retrieve the X and Y index of the tile by dividing the X mouse position by the width of a tile and Y position with the height and flooring both.
That would make Map's MouseMove:
this.MouseMove = function (Mouse) {
var t = new Tile();
var tileX = Math.floor(mouse.X / t.Width);
var tileY = Math.floor(mouse.Y / t.Height);
this.Level[tileY][tileX].MouseMove(Mouse);
};
Edit: You asked for some general suggestions. Here you go:
It's more common to use initial uppercase letters for only classes in JavaScript.
Mouse is a simple structure; I don't think it needs to have its own class. Perhaps use object literals. (like {x: 1, y: 2})
You may want to use JavaScript's prototype objects, rather than using this.method = function() { ... } for every method. This may increase performance, since it only has to create the functions once, and not whenever a new object of that class is made.

Categories