Say I have this code:
function test() {
this._units = {};
}
test.prototype = {
get units() { return this._units; },
set units(val) {
this._units = val;
for (var unit in this._units) {
if (this._units[unit] === 0)
delete this._units[unit];
}
}
};
Now, I can assign a unit via the following:
x = new test();
x.units['foo'] = 1;
This all works. However, when I do this,
x.units['foo'] = 0;
// x.units = { 'foo' : 0 }
It doesn't remove foo from the units as it should. How can I change this?
Thanks!
You cannot intercept when a property is created, you'd need a Proxy for that. Unfortunately, it is only a harmony draft and currently only supported in Firefox' Javascript 1.8.5.
Your setter only detects an assignment to x.units = {foo:1}, but not to x.units.foo. You could create setters for every property on the units objects that you know of, but you need an extra method to make them known in the first place (assuming you're not only assigning objects to x.units).
However, you might better not do this at all. A property that deletes itself when being set to 0 is very counterintuitive. Think of
x.units.foo = -1; x.units.foo += 2;
Would you expect this to do the same as
x.units.foo = -1; x.units.foo++; x.units.foo++;
? The second would yield NaN instead of 1.
Related
I have some code:
//Main class
class Tools {
//Subclass implemented by static getter
static get SUPPORTED_UNITS () {
return class {
//Another one
static get Size () {
return class {
//Some data (constants in my case)
};
}
};
}
}
I want to use it like Tools.SUPPORTED_UNITS.Size and I want to receive some data as a result of it.
I use WebStorm for writing JavaScript code, so it shows me this when I tried to use it.
I don't know exactly, maybe I just haven't done some setup to my WebStorm, but.. What I need to do that I can use construction like Class.Subclass.SubClassMethod/SubSubclass.
I need that:
It be not a method
It be static
It be immutable (without Object.freeze or something like that) - so I can't change my Class.Subclass by typing Class.Subclass = foo;
It be indexing by IDE - so I have some help from IDE (like I type Class.Su and she suggest Class.Subclass)
Is it real in ES6 and WebStorm? Or maybe I have to use ES7 and Babel or someting like that?
P.S. That code is working, but I need suggestions from IDE or it will be like a hell.
Following #estus and #Felix Kling suggestion... (likely being misrepresented here)
const TOOLS = {
get SUPPORTED_UNITS() {
return {
get SIZE() {
return {
get Bar() {
return 1;
}
}
}
}
}
};
console.log(TOOLS.SUPPORTED_UNITS.SIZE.Bar) // 1;
TOOLS.SUPPORTED_UNITS.SIZE = "Hello";
// TOOLS.SUPPORTED_UNITS.SIZE = "Hello";
// ^
// TypeError: Cannot set property SIZE of #<Object> which has only a getter
That works but as remarked by #bergi, it creates a new object in every lookup:
console.log(TOOLS.SUPPORTED_UNITS === TOOLS.SUPPORTED_UNITS); // false
Yep...
An alternative still using getters would be encapsulate all those objects in an IIFE:
const TOOLS = (function () {
const SIZE = {
get Bar() { return 1; }
}
const SUPPORTED_UNITS = {
get SIZE() {
return SIZE;
}
}
return {
get SUPPORTED_UNITS() {
return SUPPORTED_UNITS;
}
}
}());
console.log(TOOLS.SUPPORTED_UNITS.SIZE.Bar === TOOLS.SUPPORTED_UNITS.SIZE.Bar); // true
console.log(TOOLS.SUPPORTED_UNITS.SIZE === TOOLS.SUPPORTED_UNITS.SIZE); // true
console.log(TOOLS === TOOLS); // true
TOOLS.SUPPORTED_UNITS = "hello"; // still throws an error
Third update:
The second example solves the issue related to creation of a new object every lookup but it also does not take in consideration that the access to every nested property results in a function call to reevaluate something that is not supposed to change:
//A naive measurement:
for(let counter = 0; counter < 5; counter++) {
let start = Date.now();
for(let i = 0; i < 200000; i++){
let somevar = Tools.SUPPORT_UNITS.SIZE;
}
let end = Date.now();
console.log(`Elapsed using nested getters: ${end - start}ms.`);
}
// Something like:
/** Elapsed using nested getters: 20ms.
* Elapsed using nested getters: 14ms.
* Elapsed using nested getters: 9ms.
* Elapsed using nested getters: 10ms.
* Elapsed using nested getters: 10ms.
*
// While the same as ordinary properties:
* Elapsed ordinary properties: 2ms.
* Elapsed ordinary properties: 5ms.
* Elapsed ordinary properties: 1ms.
* Elapsed ordinary properties: 0ms.
* Elapsed ordinary properties: 0ms.
*/
So it is also better to check alternatives before using this approach if running 200000 times loops is in the foreseeable future.
I have a tree structure of objects, and their properties have very complicated dependencies on surrounding objects determined by where they are in the tree. I have hard coded a lot of these dependencies, and tried to create some sort of update loop (where if a property gets updated, based on the design, all of the properties that depend on it get updated, and in the correct order), but I want to handle it in a more generic/abstract way, instead of hard coding a bunch of update calls to different objects.
Let's say, for example, I have 1 superclass, and 3 subclasses, and then a separate container object.
Shape
properties: parentContainer, index, left, top, width, height
methods: updateLeft(), updateTop(), updateWidth(), updateHeight()
Square inherits from Shape
Triangle inherits from Shape
Circle inherits from Shape
ShapeContainer
properties: shapes
methods: addShape(shape, index), removeShape(index)
I'll give a pseudocode example update method to illustrate how these dependencies crop up:
Square.updateTop() {
var prevShape = null;
if (this.index != 0) {
prevShape = this.parentContainer.shapes[this.index - 1];
}
var nextSquareInContainer = null;
for (var i = this.index; i < this.parentContainer.shapes.length; i++) {
var shape = this.parentContainer.shapes[i];
if(shape instanceof Square) {
nextSquareInContainer = shape;
break;
}
}
var top = 0;
if (prevShape != null && nextSquareInContainer != null) {
top = prevShape.top + nextSquareInContainer.width;
} else {
top = 22;
}
this.top = top;
}
So, any square objects added to the shapeConatiner will depend on the previous shape's top value and the next square found in the container's width value for its top value.
Here is some code to set up an example shape container:
var shapeContainer = new ShapeContainer();
var triangle = new Triangle();
var circle = new Circle();
var square1 = new Square();
var square2 = new Square();
shapeContainer.addShape(triangle, 0);
shapeContainer.addShape(circle, 1);
shapeContainer.addShape(square1, 2);
shapeContainer.addShape(square2, 3);
So, I guess the crux of the issue is, if I update the above circle's top value, I want the top value of square1 to be automatically updated (because there is a one way dependency between square1's top value, and circle's top value). So one way I can do this (the way I've been doing it, in combination with some other specific knowledge of my problem domain to simplify the calls), is to add the code similar to the following to Circle's updateTop method (really it would have to be added to each shape's updateTop method):
Circle.updateTop() {
// Code to actually calculate and update Circle's top value, note this
// may depend on its own set of dependencies
var nextShape = this.parentContainer.shapes[this.index + 1];
if (nextShape instanceof Square) {
nextShape.updateTop();
}
}
This type of design is fine for a few simple dependencies between objects, but my project has dozens of types of objects with probably hundreds of dependencies between their properties. I've coded it this way, but it is very difficult to reason about when trying to add new features, or troubleshoot a bug.
Is there some sort of design pattern out there to set up dependencies between object properties, and then when one property is updated, it updates all of the properties on other objects that depend on it (which may then trigger further updating of properties that depend on the now newly updated properties)? Some sort of declarative syntax for specifying these dependencies would probably be best for readability/maintainability.
Another issue is, a property may have several dependencies, that ALL must be updated before I want that property to update itself.
I've been looking into a pub/sub type of solution, but I thought this was a complicated enough problem to reach out for help. As a side note, I'm working in javascript.
Here is the hackish solution I came up with. I create a wrapper class, that you pass in anonymous functions for getter/setter/updaters. Then you make a call of prop1.dependsOn(prop2) to declaratively set up dependencies. It involves setting up a directed acyclic graph of the dependencies between object properties, and then when a property value is updated, explicitly making a call to resolve the related dependencies using a topological sort. I didn't put much thought into efficiency, and I bet somebody could come up with a much more robust/performant solution, but I think this will do for now. Sorry for the code dump, but I thought it could be of some help to somebody trying to solve a similar problem down the road. If somebody wants to make this syntactically cleaner, be my guest.
// This is a class that will act as a wrapper for all properties
// that we want to tie to our dependency graph.
function Property(initialValue, ctx) {
// Each property will get a unique id.
this.id = (++Property.id).toString();
this.value = initialValue;
this.isUpdated = false;
this.context = ctx;
Property.dependsOn[this.id] = [];
Property.isDependedOnBy[this.id] = [];
Property.idMapping[this.id] = this;
}
// Static properties on Property function.
Property.id = 0;
Property.dependsOn = {};
Property.isDependedOnBy = {};
Property.idMapping = {};
// Calling this updates all dependencies from the node outward.
Property.resolveDependencies = function (node) {
node = node.id;
var visible = [];
// Using Depth First Search to mark visibility (only want to update dependencies that are visible).
var depthFirst = function (node) {
visible.push(node);
for (var i = 0; i < Property.isDependedOnBy[node].length; i++) {
depthFirst(Property.isDependedOnBy[node][i]);
}
};
depthFirst(node);
// Topological sort to make sure updates are done in the correct order.
var generateOrder = function (inbound) {
var noIncomingEdges = [];
for (var key in inbound) {
if (inbound.hasOwnProperty(key)) {
if (inbound[key].length === 0) {
// Only call update if visible.
if (_.indexOf(visible, key) !== -1) {
Property.idMapping[key].computeValue();
}
noIncomingEdges.push(key);
delete inbound[key];
}
}
}
for (var key in inbound) {
if (inbound.hasOwnProperty(key)) {
for (var i = 0; i < noIncomingEdges.length; i++) {
inbound[key] = _.without(inbound[key], noIncomingEdges[i]);
}
}
}
// Check if the object has anymore nodes.
for (var prop in inbound) {
if (Object.prototype.hasOwnProperty.call(inbound, prop)) {
generateOrder(inbound);
}
}
};
generateOrder(_.clone(Property.dependsOn));
};
Property.prototype.get = function () {
return this.value;
}
Property.prototype.set = function (value) {
this.value = value;
}
Property.prototype.computeValue = function () {
// Call code that updates this.value.
};
Property.prototype.dependsOn = function (prop) {
Property.dependsOn[this.id].push(prop.id);
Property.isDependedOnBy[prop.id].push(this.id);
}
function PropertyFactory(methodObject) {
var self = this;
var PropType = function (initialValue) {
Property.call(this, initialValue, self);
}
PropType.prototype = Object.create(Property.prototype);
PropType.prototype.constructor = PropType;
if (methodObject.get !== null) {
PropType.prototype.get = methodObject.get;
}
if (methodObject.set !== null) {
PropType.prototype.set = methodObject.set;
}
if (methodObject.computeValue !== null) {
PropType.prototype.computeValue = methodObject.computeValue;
}
return new PropType(methodObject.initialValue);
}
And here is an example of what setting up a property looks like:
function MyClassContainer() {
this.children = [];
this.prop = PropertyFactory.call(this, {
initialValue: 0,
get: null,
set: null,
computeValue: function () {
var self = this.context;
var updatedVal = self.children[0].prop.get() + self.children[1].prop.get();
this.set(updatedVal);
}
});
}
MyClassContainer.prototype.addChildren = function (child) {
if (this.children.length === 0 || this.children.length === 1) {
// Here is the key line. This line is setting up the dependency between
// object properties.
this.prop.dependsOn(child.prop);
}
this.children.push(child);
}
function MyClass() {
this.prop = PropertyFactory.call(this, {
initialValue: 5,
get: null,
set: null,
computeValue: null
});
}
var c = new MyClassContainer();
var c1 = new MyClass();
var c2 = new MyClass();
c.addChildren(c1);
c.addChildren(c2);
And here is an example of actually updating a property once all of this infrastructure is set up:
c1.prop.set(3);
Property.resolveDependencies(c1.prop);
I feel like this is a pretty powerful pattern for programs that require really complicated dependencies. Knockout JS has something similar, with computedObservables (and they use a wrapper in a similar fashion), but you can only tie the computed property to other properties on the same object from what I can tell. The above pattern allows you to arbitrarily associate object properties as dependencies.
I created this Object with 3 properties:
Node = {
name : "",
isOkay : true,
rotation : 0.0
};
How would i go creating an array of these objects, in size of 100.
So later i could do something like this:
nodeList[74].name = "Peter";
nodeList[74].isOkay = false;
nodeList[74].rotation = 1.3;
or similar...
I'm really new to this, i found couple of topics about this, but it never compiles properly.
I would be really grateful if anyone could show the proper syntax, Thanks!
I would use this way:
var Node = function() {
this.name = "";
this.isOkay = true;
this.rotation = 0.0
}
var nodeList = [];
for (var i = 0; i < 10; i++)
{
nodeList.push(new Node());
}
nodeList[0].name = "test";
So you could create a new object(really new) in order to manage it later. Look here.
EDIT:
What I have done is created an object with a constructor method, you can check it on MDN here.
Creating an object like you have done:
var Node = { /* ... */ }
Is like having one object initiated. To have another, you'll have to write another one and so on. With that contructor you may create any instances you want based on that model.
You might want to do this lazily
Depending on the situation might be helpful to do this lazily
var Node = function(name, isOkay,rotation){
if(!(this instanceof Node)) return new Node(name,isOkay,rotation);
else {
this.name = name;
this.isOkay = isOkay;
this.rotation = rotation;
}
}
var NodeCollective = function(numberOfNodes){
if(!(this instanceof NodeCollective)) return new NodeCollective(numberOfNodes);
else{
var _collective={};
var _defaultName = "", _defaultIsOkay = true, _defaultRotation=0.0;
this.length = numberOfNodes;
this.getNode=function(nodeNumber){
if(!_collective.hasOwnProperty(nodeNumber) && nodeNumber < numberOfNodes){
_collective[nodeNumber]=
Node(_defaultName,_defaultIsOkay,_defaultRotation);
}
//I am just assuming I am not going to get garbage
//you can put in checks to make sure everything is kosher
//if you really want to
return _collective[nodeNumber];
};
}
}
but it also depends on what you are trying to do... if you might not be getting all of the nodes in your program then implementing them in some way that avoids greedily creating them could save you a lot of time if the code is executed often, but if the piece of code isn't executed often this might be over kill.
var nodeList = []; // create empty array
nodeList.push(Node); // add the object to the end of the array
nodeList[0].rotation = 1.3; // set rotation property of the object
console.log(nodeList[0]); // prints the object to console
Writing some code, and when creating an instance of a class, something strange happens with an integer variable I have:
function Mat(x, y, spawner) {
this.x = x;
this.y = y;
this.val = 1;
this._spawner = spawner;
this.newborn = true;
this.bornTime = 0;
this.spawnTimer = setInterval("this.bornTime++; console.log(this.bornTime);", 1000);
}
Pretty cut and clear code; every second after an instance of the variable is created, it should increment the bornTime variable by 1 and log it.
Mat.prototype.update = function() {
if (this.bornTime >= 5) {
this.bornTime = null;
clearInterval(this.spawnTimer);
this.newborn = false;
console.log("Grown!");
}
}
This additional code would cause this instance to be "grown" after 5 seconds, however when I check the console, it reads that bornTime is not a number(NaN).
Why is this, and is there a solution that I am not seeing?
this inside the setTimeout code is not the same as outside (more info on MDN), so your code is actually calculating undefined++, which is NaN.
You have to create another variable, and pass a function to setTimeout instead of letting it eval a string (by the way, passing a function is supposed to be faster, and looks better):
var that = this;
this.spawnTimer = setInterval(function(){
that.bornTime++;
console.log(that.bornTime);
}, 1000);
I know this is 5 years old question but its 2018 and heres an Es6 syntax solution to avoid extra step of binding key word this.
this.spawnTimer = setInterval(() => {
this.bornTime++;
console.log(this.bornTime);
}, 1000);
I have a variable in a JavaScript constructor that appears to be set to the correct value when stepped through using breakpoints. However, when run without breakpoints, the variable (supposed to be an array that I give it), comes up as an empty array in the console. I don't know whether or not using the get/set property of prototype, as described here. Also-- I'm working in webkit, so if someone could help explain to me why it isn't working there, I'd appreciate it. Thanks!
function Box(inElement){
var self = this;
this.element = inElement;
this.boxes = (function () {
var boxes = [];
for (var i = 0; i < inElement.childNodes.length; ++i) {
if (3 !== inElement.childNodes[i].nodeType) {
boxes.push(inElement.childNodes[i]);
}
}
return boxes;
})();
this.rotation = [-40,-20,0,20,40];
}
Box.prototype =
{
get rotation(){
return this._rotation;
},
set rotation(rotArray){
console.log('rotArray');
console.log(rotArray);
var thisrot;
this._rotation = rotArray;
for(var i=0; i<this.boxes.length; i++){
thisrot = rotArray.shift();
this.boxes[i].style.webkitTransform = 'rotateY(' + thisrot + 'deg) translateZ(170px)';
}
}
}
function loaded()
{
new Box(document.getElementById('area'));
}
window.addEventListener('load',loaded, true);
So, after some fiddling, I discovered that boxes.push(inElement.childnodes[i] is the problematic line. When commented out, the value comes out as expected.
You are removing all elements from your array in the loop inside of set rotation using shift. Arrays are passed by reference in JavaScript, not by value. If you want to create a copy of your array, you will have to use Array.slice:
this._rotation = rotArray.slice();