compare code 1 and code 2, which one is correct?
function Rectangle(height, width) {
this.height = height;
this.width = width;
this.calcArea = function() { // why use this here?
return this.height * this.width;
};
}
code 2
I thought it's fine with this :
function Rectangle(height, width) {
this.height = height;
this.width = width;
calcArea = function() {
return this.height * this.width;
};
}
which one is correct?
This depends on how you view "correct":
Will either declaration fail to be parse correctly?
No, both are valid JavaScript.
Which one will calculate calcArea?
Code 1 will calculate it correctly and Code 2 does not create a member function of the Rectangle class but you can make it calculate correctly with a bit of difficulty ad redirection. See below.
Is either one good practice for creating classes?
No, neither of them. See at the bottom.
Code 1 - calcArea()
If you create a new instance of the Rectangle in code 1 then:
function Rectangle(height, width) {
this.height = height;
this.width = width;
this.calcArea = function() { // why use this here?
return this.height * this.width;
};
}
var rect = new Rectangle( 3, 4 );
console.log( rect.calcArea() );
Will correctly output 12
Code 2 - calcArea()
If you create a new instance of the Rectangle in code 2 then:
function Rectangle(height, width) {
this.height = height;
this.width = width;
calcArea = function() {
return this.height * this.width;
};
}
var rect = new Rectangle( 3, 4 );
console.log( rect.calcArea() );
Will throw an error: TypeError: rect.calcArea is not a function
calcArea is, instead, attached to the global scope so we can do:
console.log( calcArea() );
Will output NaN as calcArea in part of the global scope so has no knowledge of any instance of a Rectangle class and the global scope does not have a height or a width attribute.
If we do:
var rect = new Rectangle( 3, 4 );
width = 7; // Set in the global scope.
height = 10; // Set in the global scope.
console.log( calcArea() );
Then it will return 70 (and not 12 since, within calcArea(), this references the global scope and not the rect object).
If we change what this refers using .call() to invoke the function:
var rect = new Rectangle( 3, 4 );
width = 7; // Set in the global scope.
height = 10; // Set in the global scope.
console.log( calcArea.call( rect ) );
Then it will output 12 (since this now refers to the rect object and not to the global scope).
You probably don't want to have to do this every time you want to use calcArea().
Why Code 1 is not optimal
Code 1 will work but is not the optimal solution because each time you create a new Rectangle object it will create an calcArea attribute of that object which is a different function to any calcArea attributes of any other Rectangle object.
You can see this if you do:
function Rectangle(height, width) {
this.height = height;
this.width = width;
this.calcArea = function() { // why use this here?
return this.height * this.width;
};
}
var r1 = new Rectangle( 3, 4 ),
r2 = new Rectangle( 6, 7 );
console.log( r1.calcArea.toString() === r2.calcArea.toString() ); // Line 1
console.log( r1.calcArea === r2.calcArea ); // Line 2
Which will output true when testing the string representation of the functions are identical but false when testing whether the functions are identical.
What does this mean? If you create 10,000 instances of Rectangle then you will have 10,000 different instances of the calcArea attribute as well and each copy will require additional memory (plus time to allocate that memory and to garbage collect it at the end).
What is better practice?
function Rectangle(height, width) {
this.setHeight( height );
this.setWidth( width );
}
Rectangle.prototype.setHeight = function( height ){ this.height = height; }
Rectangle.prototype.setWidth = function( width ){ this.width = width; }
Rectangle.prototype.calcArea = function(){ return this.height * this.width; }
Then if you do:
var r1 = new Rectangle( 3, 4 ),
r2 = new Rectangle( 6, 7 );
console.log( r1.calcArea.toString() === r2.calcArea.toString() ); // Line 1
console.log( r1.calcArea === r2.calcArea ); // Line 2
It will return true for both - meaning that r1.calcArea and r2.calcArea refer to the identical function and regardless of how many instances of Rectangle there are.
If you don't use this, calcArea will not be accessible through the object of Rectangle. When you say,
this.calcArea = function () ...
you create a new variable in the current object (this) and the function will be assigned to it, so that the object will have access to it.
Try these statements, with and without this. You ll understand better.
var a = new Rectangle(1, 2);
console.log(a.calcArea());
The second version will set the global variable calcArea to do stuff specific to your object whenever an instance of your object is constructed. Use of this is required to set properties of your particular object.
When you preface your methods and properties with 'this' in your constructor they allow any new objects that get created with that constructor to use those properties and methods and have those properties and methods point to the newly created object.
If you create a new object based on your version of the Rectangle constructor that doesn't use 'this' as a preface to calcArea and look at the chrome debugger you get the following error:
Object #<Rectangle> has no method 'calcArea'
In short it simply isn't recognized.
The other aspects of not using "this" is that the method becomes 'global'.
Here is a code example to demonstrate:
function Rectangle(height, width) {
this.height = height;
this.width = width;
calcArea = function() { // Not prefaced with 'this'
return 'hello world';
};
}
var aNewObj = new Rectangle(10,10);
console.log(calcArea()) // Notice this is not aNewObj.calcArea() ...just calcArea() but its still accessible.
Related
I am initializing classes like so (Character is a class in my program):
character = new Character();
I want two instances of this class/object, so I tried this:
character2 = new Character();
Yet character2 simply replaces character; therefore, there is only one object. Is it possible to create another instance, or would I need to make another Character class (a lot of code duplication!).
I tried adding a second draw function (named draw2, for the second object), but that didn't help.
You need to move the definition of the Character.prototype.draw method out of the constructor function. Otherwise, each time you create a new Character(), you also override the Character.prototype.draw method.
You also need to replace references to local constructor function variables within the Character.prototype.draw method such as x, y or size with object properties such as this.X, this.Y and this.Size.
Also, you need to make the img a (possibly static) property of your Character.
Object creation in modern JS
Modern JS includes some shortcuts when defining objects. There is no real need to use prototype unless you are creating many instances of the Object. Accessing the prototype adds some overhead when using objects
You can create the object inside the creating function that allows you to define private properties via closure.
Closure creates private properties
function Character() {
var x = 0;
var y = 0;
var size = 25;
var vx = 4;
var vy = 4;
var width = 45;
var height = 45;
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
get x() { return x },
get y() { return y },
get vx() { return vx },
get vy() { return vy },
get size() { return size },
get width() { return width },
get height() { return height },
set x(v) { x = v },
set y(v) { y = v },
set vx(v) { vx = v },
set vy(v) { vy = v },
set size(v) { size = v },
set width(v) { width = v },
set height(v) { height = v },
draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
usage
var char = Character();
var char1 = Character();
// or
var char = new Character();
var char1 = new Character();
Performance considerations
If the object is required in performance code you may want to create more performant setters and getters, or avoid the getter setter overhead and include the properties in the object itself.
function Character() {
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
x : 0,
y : 0,
size : 25,
vx : 4,
vy : 4,
width : 45,
height : 45,
draw(ctx) {
const size = API.size;
ctx.save();
ctx.translate(API.x, API.y); // Note that I use API.x rather than this.x
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
Notes.
I noticed you have way too many comments.
eg
//public property for VX
Object.defineProperty(this, 'width',
and
//function public draw method
Character.prototype.draw = function (ctx) {
//save the ctx
ctx.save();
//set x and y
ctx.translate(x, y);
//set the line width
ctx.lineWidth = 2;
You are stating the obvious in a comment, no machine will read it, no human needs to read it, so why is it there. Comments add noise, and source code noise is dangerous, avoid all unnecessary noise in your code.
You create the pattern each time the draw function is called, this is unneeded overhead. Create it only once.
There is a time when calling the draw function may not work as the image will not have loaded yet. Maybe you should manage images outside the object where you can ensure that media objects are loaded and ready to be used befor you try to use them.
Each instance of Character will load the image, create 100 of them and there will be 100 copies of the same image. This will affect performance and memory negatively.
Say I had a class defined within a string like the following:
`class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
}`
Is it possible to turn the string into a javascript class so I could run the following? Or similar..
const square = new Rectangle(10, 10);
console.log(square.area);
Looks like a duplicate of this : Using eval method to get class from string in Firefox
Don't forget to put class between parentheses.
var class_str = `(class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
})`;
var a = eval(class_str);
console.log(new a(10, 10));
A working demo here: http://jsbin.com/netipuluki/edit?js,console
You can turn a string into a class if you pass it to eval, like this:
eval("function MyClass(params) {/*Some code*/}");
var foo = new MyClass({a: 1, b: 2});
EDIT:
Based on comments I found out that the syntax shown in the question is ok, but it seems to be incompatible with eval as it is. However, doing some experiments I found the following trick:
eval("window['Rectangle'] = class Rectangle {constructor(height, width) {this.height = height;this.width = width;}get area() {return this.calcArea();}calcArea() {return this.height * this.width;}}");
var r = new Rectangle(50, 40);
I have a code in below, as you see when console.log the prototype of the class first time, it return empty, but the object new from this class actually can response those method, then I add function in prototype and bring new object successful, how can explain it?
codebase
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea()
}
calcArea() {
return this.height * this.width;
}
}
console.log(Polygon.prototype)
polygon = new Polygon(222,122)
console.log(polygon.area)
console.log(polygon.calcArea())
Polygon.prototype.test = function(){ return "test"}
console.log(Polygon.prototype)
console.log(polygon.test())
output
Polygon {}
27084
27084
Polygon { test: [Function] }
test
how can explain it?
Methods/properties created through the class syntax are non-enumerable and it seems that the environment you log the value in doesn't show non-enumerable properties. console.log is not standardized, so different outputs in different environments have to be expected.
Creating a property through assignment always creates an enumerable property.
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea()
}
calcArea() {
return this.height * this.width;
}
}
Polygon.prototype.test = function(){ return "test"}
// Note the different values for `enumerable`
console.log(Object.getOwnPropertyDescriptor(Polygon.prototype, 'calcArea'));
console.log(Object.getOwnPropertyDescriptor(Polygon.prototype, 'test'));
sample function declaration in function :
function Rectangle(height, width) {
this.height = height;
this.width = width;
this.calcArea = function() {
return this.height * this.width;
};
// put our perimeter function here!
this.calcPerimeter = function() {
return 2 * this.height + 2 * this.width;
};
sample new function declaration :
var actions = new function() {
console.log("this is to do something");
}
Why are we using new keyword while declaring a new function but not using new keyword while declaring it in a constructor??
new is an operator. It allocates an object instance (and assigns it a prototype). The functions you create aren't instances of any sort of class, and are not going to be created in multiple copies. The static declaration defines the only instance that will exist.
I am doing some reading about class creation in Javascript. I know the concept does not exist in Javascript and that one can work with prototype.
I am trying to translate the following piece of code from Java to Javascript. Specifically, I want to have two constructors, one parameterless and one with two parameters:
public class MyClass {
int width = 10;
int height = 20;
public MyClass() { };
public MyClass(int w, int h) {
this.width = w;
this.height = h;
};
...
}
As far as I understand, I need to define my 'class' as following in Javascript:
function MyClass() {
this.width = 10;
this.height = 20;
};
But, how do I define my second constructor? I want to be able to create instances of my class two ways:
var Instance1 = new MyClass();
var Instance2 = new MyClass(33,45);
Update:
Ok, I understand my constructors cannot have the same name, because Javascript cannot recognize the different parameter types. So, if I use different names for my constructors, how am I supposed to declare them? Is the following correct?
function MyClass() {
this.width = 10;
this.height = 20;
};
MyClass.prototype.New2 = function(w,h) {
var result = new MyClass();
result.width = w,
result.height = h,
return result;
};
Javascript has no multimethods, therefore your only option is to parse arguments and act accordingly. A common idiom is to use || to check if an argument is "empty" (undefined or 0):
function MyClass(w, h) {
this.width = w || 10;
this.height = h || 20;
};
If 0 is a valid value in your context, check for undefined explicitly:
function MyClass(w, h) {
this.width = typeof w != 'undefined' ? w : 10;
this.height = typeof h != 'undefined' ? h : 20;
};
Another option is to provide arguments as an object and merge it with the "defaults" object. This is a common pattern in jquery:
function MyClass(options) {
// set up default options
var defaults = {
width: 10,
height: 20
};
var options = $.extend({}, defaults, options);