Eric Faust typed up a Circle constructor function in the following article about ES6 classes: https://hacks.mozilla.org/2015/07/es6-in-depth-classes/
I was wondering:
Why does he use defineProperty? Can't we just implement the behavior straight into the constructor. For example: Circle.draw = function draw() {..}
Why use get / set instead of just having the state in normal properties: Circle.circleCount?
Which properties should be implemented directly on new instance objects, via this in constructor, vs on Constructor.prototype (given how both make the properties available to new instances)?
Eric's code:
function Circle(radius) {
this.radius = radius;
Circle.circlesMade++;
}
Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ }
Object.defineProperty(Circle, "circlesMade", {
get: function() {
return !this._count ? 0 : this._count;
},
set: function(val) {
this._count = val;
}
});
Circle.prototype = {
area: function area() {
return Math.pow(this.radius, 2) * Math.PI;
}
};
Object.defineProperty(Circle.prototype, "radius", {
get: function() {
return this._radius;
},
set: function(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
}
});
let c1 = new Circle(10);
console.log(c1.area());
console.log(Circle.circlesMade);
My version:
function Circle(_radius) {
this.radius = _radius;
Circle.draw = function draw(circle, canvas) {
/* Canvas drawing code */
};
!Circle.circleCount ?
(Circle.circleCount = 1) //First construction
:
(Circle.circleCount = Circle.circleCount + 1);
this.area = function area() {
return Math.pow(this.radius, 2) * Math.PI;
};
}
let c1 = new Circle(10);
console.log(Circle.circleCount);
console.log(c1.area());
let c2 = new Circle(20);
console.log(Circle.circleCount);
console.log(c2.area());
I prefer using the constructor's prototype object for functions that will be shared with instances rather than defining a new function on each instance. This can save memory because you only have one instance of the function rather than a new copy for each instance you create.
You can also define circleCount on the prototype since all instances need the same number. You just need to be a little careful changing it to make sure you don't create a shadowed property on each instance. Then each instance can directly provide the count via the prototype chain.
Doing that complicates the function, but simplifies the rest of you code:
function Circle(_radius) {
this.radius = _radius;
// creating an instance increments the count for everyone
Circle.prototype.circleCount++ // not this.circleCount++ which will create a new property on instance
}
Circle.prototype.draw = function draw(circle, canvas) {
/* Canvas drawing code */
};
Circle.prototype.area = function() {
return Math.pow(this.radius, 2) * Math.PI;
}
Circle.prototype.circleCount = 0
let c1 = new Circle(10);
console.log(c1.circleCount);
console.log(c1.area());
let c2 = new Circle(20);
console.log(c2.circleCount);
console.log(c2.area());
Also, regarding the question about Object.defineProperty. It looks like he's using that so he can set getter and setter functions rather than just returning the property. You could do that with area with something like:
Object.defineProperty(Circle.prototype, "area", {
get: function() {
return Math.pow(this.radius, 2) * Math.PI;
}
});
Which would allow you to access area as if ti were a property on each instance:
c2.area // instead of a function c2.area()
You could set area as a property directly, but then if you change the radius you need to also change the area. I guess which is best depends on whether radius will ever change.
Related
I am trying to create a class using the class foo {} syntax, and want a private variable inside of it. Right now, I have something like this:
'use strict';
class foo {
constructor(x1,y1) {
var x = x1;
var y = y1;
}
get x() {
return x;
}
get y() {
return y;
}
}
Whenever I try to access foo.x and foo.y, I get a x is not found error. However, when I tried putting the variable declarations outside of the constructor, it stopped working. I'm also using static methods, so I can't abandon the class syntax without some work. How can I fix this? How can I declare a variable inside of the object, that is global and can't be found outside?
Those getters are declared on the prototype, where they cannot access the private (scoped) variables in the constructor. You'll need to use Object.defineProperty for creating instance-specific getters:
class Foo {
constructor(x, y) {
Object.defineProperties(this, {
x: {get() { return x; }, configurable: true},
y: {get() { return y; }, configurable: true}
});
}
}
This is no different than in ES5. Btw, given you don't assign to the variables, you might as well make them non-writable properties.
Well people are assuming you want to use getters and setters. If that's not the case, this will suffice:
'use strict';
class foo {
constructor(x1,y1) {
this.x = x1;
this.y = y1;
}
}
var f = new foo(10, 20);
console.log(f.x)
console.log(f.y)
JSFiddle: https://jsfiddle.net/gLxnqfrv/
Taken from es6-features this is the correct syntax for a class and it's getters and setters.
class Rectangle {
constructor (width, height) {
this._width = width
this._height = height
}
set width (width) { this._width = width }
get width () { return this._width }
set height (height) { this._height = height }
get height () { return this._height }
get area () { return this._width * this._height }
}
var r = new Rectangle(50, 20)
r.area === 1000
Important to note that there is no requirement to damage the prototype in ES6.
I read in a book that we can extend functionality of existing objects using the following code:
var Point = function(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.moveBy = function(deltaX, deltaY) {
this.x += deltaX;
this.y += deltaY;
}
Point.prototype.moveTo = function(otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint.y;
}
var p1= new Point(100, 200);
p1.moveBy(10, 20);
var p2= new Point(25, 50);
p2.moveTo(p1);
alert("p2.x: " + p2.x + " p2.y: " + p2.y);
I just found that I can get the same results with prototype as follows:
var Point = function(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
moveBy: function(deltaX, deltaY) {
this.x += deltaX;
this.y += deltaY;
},
moveTo: function(otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint.y;
}
};
var p1= new Point(100, 200);
p1.moveBy(10, 20);
var p2= new Point(25, 50);
p2.moveTo(p1);
alert("p2.x: " + p2.x + " p2.y: " + p2.y);
So what is the difference? It did not make sense to me.
What Object are we adding functionality to?
What is the difference if we created the functions as methods in the Point object?
Why don't we do it using Prototype only?
Members bound to Prototype are shared across instances. There is only one instance of that member.
function Point(value) { this.value = value; }
Point.prototype.moveBy = function() { console.log('from the prototype', this.value);};
var p1 = new Point('p1');
var p2 = new Point('p2');
p1.moveBy === p2.moveBy; //true. Same instance of moveBy function
Members set directly to this inside the constructor or set to a specific instance of Point is available to that specific instance of Point only. If you want to expose logic shared across all instances of Point, it's best to set it on the Prototype.
var p3 = new Point('p3');
p3.moveBy = function() { console.log('from the instance', this.value); };
p3.moveBy === p2.moveBy; //false
var p4 = new Point('p4');
p4.moveBy = function() { console.log('from the instance', this.value) };
p4.moveBy === p3.moveBy; //false
The cool part is that you can always access the prototype even if you have declared it on the instance. If so, pass the instance as context:
Point.prototype.moveBy.call(p3); //will call moveBy set on the prototype. Output: "from the prototype p3"
p3.moveBy(); //will call moveBy set on the instance. Output: from the instance p3
The above snippet illustrates fundamental concepts of JavaScript (the context) and object-oriented JavaScript (prototype). If the subject is of interest to you, take a look at N. Zackas' book on the subject: https://www.amazon.com/Principles-Object-Oriented-JavaScript-Nicholas-Zakas/dp/1593275404
Well the difference is clear. With the first peace of code your are declaring new members for Point.prototype. However, in the second one, you are providing an entire implementantion for Point.prototype.
All objects in JavaScript are descended from Object; all objects
inherit methods and properties from Object.prototype, although they
may be overridden (except an Object with a null prototype, i.e.
Object.create(null)). For example, other constructors' prototypes
override the constructor property and provide their own toString()
methods.
Changes to the Object prototype object are seen by all objects through
prototype chaining, unless the properties and methods subject to those
changes are overridden further along the prototype chain. This
provides a very powerful although potentially dangerous mechanism to
override or extend object behavior.
Object.prototype on MDN
I just started fiddling around with JavaScript. Coming from Java and OO PHP things are getting weirder with every step :)
This is my introduction project to javascript in which I've set out to program multiplayer working version of Settlers of Catan. Code below is an attempt to store cube coordinates of N sized hexagonal map tiles in an array.
I've read you declare object in javascript by assigning functions to variables.
var Tile = function (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
};
var Map = function () {
var grid = [];
function generate_map(radius) {
for (width = -radius; width <= radius; width++) {
var r1 = Math.max(-radius, -width - radius);
var r2 = Math.min(radius, -width + radius);
for (r = r1; r <= r2; r++) {
grid.push(new Tile(width, r, -width - r));
}
}
}
};
I've tried instantiating new Map object, calling its only function and outprinting the resulting values stores in grid[] array. But for each loop is not playing nice :( I get the unexpected identifier.
var main = function () {
var basic_map = new Map();
basic_map.generate_map(3);
for each (var tile in basic_map.grid) {
console.log(tile.x, tile.y, tile.z);
}
};
main();
I am fully aware this is one of those face palm errors, but help would nevertheless be appreciated, cheers!
Change this:
function generate_map(radius) {
...to this:
this.generate_map = function(radius) {
Edit: there are actually more issues than I at first realized.... :)
A few other tips:
First, I would recommend changing:
var Tile = function (x, y, z) {
...to simply be:
function Tile(x, y, z) {
(the same goes for Map). Your current solution works fine, but it's not very idiomatic, and until ES6 there was nothing in the spec that would cause var Tile = function to cause the resulting function's 'name' property to be set to "Tile", which is useful when it comes to debugging. I recently wrote another answer that delves a bit more into the differences between, e.g., function Foo() {} and var Foo = function() {}.
Second, you probably want to rename Map to something else. Map is a core part of ES6 now (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
Third, even though you can create your generate_map function using this.generate_map, you may want to move it to the Map's prototype. Also, since you need to expose the grid value, you want to store it as a property, rather than a local variable scoped to the NewMapName constructor. E.g.,:
function NewMapName() {
this.grid = [];
}
NewMapName.prototype.generateMap = function(radius) {
// you can access the grid here via `this.grid`
...
};
By moving it to the prototype, that means all instances of NewMapName will share the same function reference, rather than it being created over-and-over-and-over (although maybe you really only create it once? Either way, it's more idiomatic, at a minimum). Note that I took some liberties with the "camelCasing" here (see the last point).
Fourth, your generateMap implementation is leaking some global variables (width and r, since you don't declare them with var). I would change that to this:
NewMapName.prototype.generateMap = function(radius) {
for (var width = -radius; width <= radius; width++) {
var r1 = Math.max(-radius, -width - radius);
var r2 = Math.min(radius, -width + radius);
for (var r = r1; r <= r2; r++) {
grid.push(new Tile(width, r, -width - r));
}
}
};
Fifth, your loop is kind of broken. I would refactor that as follows:
var main = function () {
var basicMap = new NewMapName();
basicMap.generateMap(3);
basicMap.grid.forEach(function(tile) {
console.log(tile.x, tile.y, tile.z);
});
};
main();
Lastly, and probably most minor, is that in JavaScript-land, camelCase is far more dominant that snake_case, so generate_map might be "better" as generateMap.
I am trying to learn how to work with javascripts prototype, I am only getting into it now. Please Excuse me if I ask ridiculously stupid questions
I just have a few pre-questions:
Is it worth learning? I mean it looks like a structured/clean
approach to me?
Do/should you use this with jQuery this?
is there any major problems or reason not to use it and why isn't it commonly used or am i just slow?
Actual Question:
I have the following code:
var BudgetSection = function BudgetSection(name ) {
this.id = "";
this.name = name;
this.monthlyTotal = 0.00;
this.yearlyTotal = 0.00;
this.subTotal = 0.00;
this.lineItems = [];
};
BudgetSection.prototype.calculateSubTotal = function() {
this.subTotal = ((12 * this.monthlyTotal) + this.yearlyTotal);
};
function BudgetLineItem(name) {
this.id = "";
this.name = name;
this.monthlyAmount = 0.00;
this.yearlyAmount = 0.00;
}
BudgetLineItem.prototype = {
totalAmount : function() {
var result = ((12 * this.monthlyAmount) + this.yearlyAmount);
return result;
}
};
var budgetSections = [];
section = new BudgetSection("test1");
section.lineItems.push(new BudgetLineItem('sub'));
section.lineItems.push(new BudgetLineItem('sub2'));
section.lineItems.push(new BudgetLineItem('sub3'));
budgetSections.push(section);
section = new BudgetSection("test2");
section.lineItems.push(new BudgetLineItem('sub'));
section.lineItems.push(new BudgetLineItem('sub2'));
section.lineItems.push(new BudgetLineItem('sub3'));
budgetSections.push(section);
section = new BudgetSection("test3");
section.lineItems.push(new BudgetLineItem('sub'));
section.lineItems.push(new BudgetLineItem('sub2'));
section.lineItems.push(new BudgetLineItem('sub3'));
budgetSections.push(section);
// first iterate through budgetSections
for ( var t = 0; t < budgetSections.length; t++) {
var sec = budgetSections[t];
console.log(sec);
// iterate through each section's lineItems
for (var q = 0; q< budgetSections[t].lineItems.length ; q++) {
var li = budgetSections[t].lineItems[q];
console.log(li);
}
}
the first BudgetSection "test1" is at index 0 in the budgetSections array. how can i assign the id to "section_".
And then also how can i set the id of BudgetLineItem like so: lineItemRow_<section_index><lineitem_index>
Also finally n the for loop what would be the best way to generate html?
I personally never use the new keyword if I can avoid it and do pure prototype-based programming with Object.create. Here's a simple example. I create a prototype-object called rectangle and then create an object called myRectangle which inherits from rectangle.
var rectangle = {
init: function( x, y, width, height ) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
},
move: function( x, y ) {
this.x += x;
this.y += y;
}
};
var myRectangle = Object.create( rectangle );
myRectangle.init( 0, 0, 2, 4 );
myRectangle.move( 3, 5 );
To explain in more depth what happens here, Object.create makes a new object with a specified prototype. When we access a property on an object (like init or move), it first checks the object itself. If it can't find it there, it moves up to the object's prototype and checks there. If it's not there, it checks the prototype's prototype, and keeps going up the prototype chain until it finds it.
When we call a function on an object (myRectangle.init()), this inside the function refers to that object, even if the function definition is actually on the prototype. This is called delegation - an object can delegate its responsibilities to its prototype.
A more class-like way to do this is:
function Rectangle( x, y, width, height ) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
Rectangle.prototype.move = function( x, y ) {
this.x +=x;
this.y +=y;
};
var myRectangle = new Rectangle( 0, 0, 2, 4 );
myRectangle.move( 3, 5 );
The problem is when we need to do a deeper inheritance hierarchy:
function Parent() {
/* expensive and possibly side-effect inducing initialization */
}
Parent.prototype.parentMethod = function() {};
function Child() {}
Child.prototype = new Parent();
We have to initialize a Parent object when all we really want is to set the Child prototype to an object based on Parent.prototype. Another option is:
Child.prototype = Object.create( Parent.prototype );
But now we've got this confusing, convoluted mess of prototype-based and class-based code. Personally, I like this instead:
var parent = {
parentMethod: function() {}
};
// Using underscore for stylistic reasons
var child = _.extend( Object.create( parent ), {
childMethod: function() {}
});
var instance = Object.create( child );
instance.parentMethod();
instance.childMethod();
No new keyword needed. No fake class system. "Objects inherit from objects. What could be more object-oriented than that?"
So what's the catch? Object.create is slow. If you're creating lots of objects, it's better to use new. You can still use Object.create to set up the prototype chain, but we'll have to wait a bit for browsers to optimize it enough for lots of instantiation.
Have you tried budgetSections[0].id = 'yourID';?
I'm inheriting an object from the EASELJS library.
To simplify the problem, I'm reducing the code into the minimal form.
I have a class:
this.TESTProg = this.TESTProg || {};
(function() {
var _jsbutton = function(x, y, text, icon) {
p.init(x, y, text, icon);
};
var p = _jsbutton.prototype = new createjs.Container();
p.x = 0;
p.y = 0;
p.text = null;
p.icon = null;
p.init = function(x, y, text, icon) {
this.x = 0 + x;
this.y = 0 + y;
this.text = "" + text;
this.icon = null;
};
TESTProg._jsbutton = _jsbutton;
})();
Then I use it in another js object:
var buttoncancel = new SJSGame._jsbutton(
profileselConfig.cancelx, //this is defined in another jsfile:
profileselConfig.cancely,
"cancel", "_cancel.png");
console.log( buttoncancel.y ); //this gives 240
var buttoncancel2 = new SJSGame._jsbutton(
profileselConfig.cancelx,
profileselConfig.cancely - 40,
"cancel", "_cancel.png");
console.log( buttoncancel.y ); //this gives 200
console.log( buttoncancel2.y ); //this gives 200
buttoncancel2.y = 100;
console.log( buttoncancel.y ); //this now gives 200 (not changed by the second object)
console.log( buttoncancel2.y ); //this now gives 100
The config file:
var _profileselConfig = function(){
this.cancelx = 0;
this.cancely = 240;
};
profileselConfig = new _profileselConfig();
And what am i doing wrong?
I'm already using 0 + to avoid passing the reference and it's not working. What should I do now? Any suggestions? Thanks.
You should probably be calling this.init rather than p.init in your constructor.
When you call p.init, the this inside of init refers to the prototype. Thus, whenever you create an instance, your p.init call modifies the prototype for all _jsbutton objects.
That's why both buttons have the same x/y values: they both get their position from the same prototype, and the last-run constructor set the prototype values. When you set buttoncancel2.y outside of the constructor, you gave that instance its own y property, so it no longer used the shared prototype value.
If you call this.init in your constructor, then the this in init will refer to your newly-created instance. The instances will no longer use the shared prototype values for x, y, text, and icon.
Side note: "I'm already using 0 + to avoid passing the reference" -- this is not necessary, because primitive types are always copied.