How to define a static variable inside a JS class - javascript

I'm building a static class to store data inside an a array and i want to declare a static variable but i don't know how to do it on javascript.
JS code:
class GCache {
// Define cache variable
static cache = {}; // <--- that is what i don't know how to do
/**
* Check if data is on cache
* #param {*} id
*/
static isOnCache(id){
return this.cache.hasOwnProperty(id);
}
/**
* Add data to cache
* #param {*} id
* #param {*} json
*/
static addToCache(id, json){
if(this.isOnCache(id)) return;
this.cache[id] = json;
}
/**
* Obtain data from cache
* #param {*} id
*/
static getFromCache(id){
if(this.isOnCache(id)) return;
return this.cache[id];
}
}
Thank you <3

In up-to-date JavaScript environments, you can use the static keyword when inside a class to assign properties to the instance itself - the code in your question works fine now.
class SomeClass {
static someStaticProperty = {}
}
console.log(SomeClass.someStaticProperty);
In older environments, and at the time the question was originally posted, non-function properties couldn't be added to a class itself inside the class definition. It looks ugly, but you'll have to assign the property outside of the class definition:
class GCache {
...
}
GCache.cache = {};
Also note that your getFromCache function probably has a bug: you probably want to return early if the id being searched for does not exist in the cache:
if(!this.isOnCache(id)) return;
class GCache {
/**
* Check if data is on cache
* #param {*} id
*/
static isOnCache(id){
return this.cache.hasOwnProperty(id);
}
/**
* Add data to cache
* #param {*} id
* #param {*} json
*/
static addToCache(id, json){
if(this.isOnCache(id)) return;
this.cache[id] = json;
}
/**
* Obtain data from cache
* #param {*} id
*/
static getFromCache(id){
if(!this.isOnCache(id)) return;
return this.cache[id];
}
}
GCache.cache = {};
GCache.addToCache('myid', 'data');
console.log(GCache.getFromCache('myid'));
But, in this case, it would probably be easier to use a plain object, rather than a class. The class isn't being used to instantiate anything, after all, and with an object, you can both define cache inside the object, and reduce the syntax noise by getting rid of all the statics:
const GCache = {
cache: {},
isOnCache(id) {
return this.cache.hasOwnProperty(id);
},
addToCache(id, json) {
if (this.isOnCache(id)) return;
this.cache[id] = json;
},
getFromCache(id) {
if (!this.isOnCache(id)) return;
return this.cache[id];
}
}
GCache.addToCache('myid', 'data');
console.log(GCache.getFromCache('myid'));
There is currently a proposal allowing you to set static non-method properties onto a class. It's currently at Stage 2, which means it's expected to eventually be implemented officially. Once it lands, the code:
class GCache {
...
}
GCache.cache = {};
can be replaced by:
class GCache {
static cache = {};
...
}

If you mean static variable for all instance of the class,
declare:
GCache.cache = {}
below your class declaration

Babel's plugin-proposal-class-properties is probably what you're looking for. It enables support for static variables/methods inside the class declaration.

Related

How to turn a string into class in typescript? [duplicate]

I want create object factory using ES6 but old-style syntax doesn't work with new.
I have next code:
export class Column {}
export class Sequence {}
export class Checkbox {}
export class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new window[className](name); // this line throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
What do I do wrong?
Don't put class names on that object. Put the classes themselves there, so that you don't have to rely on them being global and accessible (in browsers) through window.
Btw, there's no good reason to make this factory a class, you would probably only instantiate it once (singleton). Just make it an object:
export class Column {}
export class Sequence {}
export class Checkbox {}
export const columnFactory = {
specColumn: {
__default: Column, // <--
__sequence: Sequence, // <--
__checkbox: Checkbox // <--
},
create(name, ...args) {
let cls = this.specColumn[name] || this.specColumn.__default;
return new cls(...args);
}
};
There is a small & dirty way to do that:
function createClassByName(name,...a) {
var c = eval(name);
return new c(...a);
}
You can now create a class like that:
let c = createClassByName( 'Person', x, y );
The problem is that the classes are not properties of the window object. You can have an object with properties "pointing" to your classes instead:
class Column {}
class Sequence {}
class Checkbox {}
let classes = {
Column,
Sequence,
Checkbox
}
class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new classes[className](name); // this line no longer throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
export {ColumnFactory, Column, Sequence, Checkbox};
For those of you that are not using ES6 and want to know how you can create classes by using a string here is what I have done to get this to work.
"use strict";
class Person {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
window.classes = {};
window.classes.Person = Person;
document.body.innerText = JSON.stringify(new window.classes["Person"](1, 2));
As you can see the easiest way to do this is to add the class to an object.
Here is the fiddle:
https://jsfiddle.net/zxg7dsng/1/
Here is an example project that uses this approach:
https://github.com/pdxjohnny/dist-rts-client-web
I prefer this method:
allThemClasses.js
export class A {}
export class B {}
export class C {}
script.js
import * as Classes from './allThemClasses';
const a = new Classes['A'];
const b = new Classes['B'];
const c = new Classes['C'];
I know this is an old post, but recently I've had the same question about how to instance a class dynamically
I'm using webpack so following the documentation there is a way to load a module dynamically using the import() function
js/classes/MyClass.js
class MyClass {
test = null;
constructor(param) {
console.log(param)
this.test = param;
}
}
js/app.js
var p = "example";
var className = "MyClass";
import('./classes/'+className).then(function(mod) {
let myClass = new mod[className](p);
console.log(myClass);
}, function(failMsg) {
console.error("Fail to load class"+className);
console.error(failMsg);
});
Beware: this method is asynchronous and I can't really tell the performance cost for it,
But it works perfectly on my simple program (worth a try ^^)
Ps: To be fare I'm new to Es6 (a couple of days) I'm more a C++ / PHP / Java developer.
I hope this helps anyone that come across this question and that is it not a bad practice ^^".
Clarification
There are similar questions to this, including this SO question that was closed, that are looking for proxy classes or factory functions in JavaScript; also called dynamic classes. This answer is a modern solution in case you landed on this answer looking for any of those things.
Answer / Solution
As of 2022 I think there is a more elegant solution for use in the browser. I made a class called Classes that self-registers the property Class (uppercase C) on the window; code below examples.
Now you can have classes that you want to be able to reference dynamically register themselves globally:
// Make a class:
class Handler {
handleIt() {
// Handling it...
}
}
// Have it register itself globally:
Class.add(Handler);
// OR if you want to be a little more clear:
window.Class.add(Handler);
Then later on in your code all you need is the name of the class you would like to get its original reference:
// Get class
const handler = Class.get('Handler');
// Instantiate class for use
const muscleMan = new (handler)();
Or, even easier, just instantiate it right away:
// Directly instantiate class for use
const muscleMan = Class.new('Handler', ...args);
Code
You can see the latest code on my gist. Add this script before all other scripts and all of your classes will be able to register with it.
/**
* Adds a global constant class that ES6 classes can register themselves with.
* This is useful for referencing dynamically named classes and instances
* where you may need to instantiate different extended classes.
*
* NOTE: This script should be called as soon as possible, preferably before all
* other scripts on a page.
*
* #class Classes
*/
class Classes {
#classes = {};
constructor() {
/**
* JavaScript Class' natively return themselves, we can take advantage
* of this to prevent duplicate setup calls from overwriting the global
* reference to this class.
*
* We need to do this since we are explicitly trying to keep a global
* reference on window. If we did not do this a developer could accidentally
* assign to window.Class again overwriting any classes previously registered.
*/
if (window.Class) {
// eslint-disable-next-line no-constructor-return
return window.Class;
}
// eslint-disable-next-line no-constructor-return
return this;
}
/**
* Add a class to the global constant.
*
* #method
* #param {Class} ref The class to add.
* #return {boolean} True if ths class was successfully registered.
*/
add(ref) {
if (typeof ref !== 'function') {
return false;
}
this.#classes[ref.prototype.constructor.name] = ref;
return true;
}
/**
* Checks if a class exists by name.
*
* #method
* #param {string} name The name of the class you would like to check.
* #return {boolean} True if this class exists, false otherwise.
*/
exists(name) {
if (this.#classes[name]) {
return true;
}
return false;
}
/**
* Retrieve a class by name.
*
* #method
* #param {string} name The name of the class you would like to retrieve.
* #return {Class|undefined} The class asked for or undefined if it was not found.
*/
get(name) {
return this.#classes[name];
}
/**
* Instantiate a new instance of a class by reference or name.
*
* #method
* #param {Class|name} name A reference to the class or the classes name.
* #param {...any} args Any arguments to pass to the classes constructor.
* #returns A new instance of the class otherwise an error is thrown.
* #throws {ReferenceError} If the class is not defined.
*/
new(name, ...args) {
// In case the dev passed the actual class reference.
if (typeof name === 'function') {
// eslint-disable-next-line new-cap
return new (name)(...args);
}
if (this.exists(name)) {
return new (this.#classes[name])(...args);
}
throw new ReferenceError(`${name} is not defined`);
}
/**
* An alias for the add method.
*
* #method
* #alias Classes.add
*/
register(ref) {
return this.add(ref);
}
}
/**
* Insure that Classes is available in the global scope as Class so other classes
* that wish to take advantage of Classes can rely on it being present.
*
* NOTE: This does not violate https://www.w3schools.com/js/js_reserved.asp
*/
const Class = new Classes();
window.Class = Class;
This is an old question but we can find three main approaches that are very clever and useful:
1. The Ugly
We can use eval to instantiate our class like this:
class Column {
constructor(c) {
this.c = c
console.log(`Column with ${this.c}`);
}
}
function instantiator(name, ...params) {
const c = eval(name)
return new c(...params)
}
const name = 'Column';
const column = instantiator(name, 'box')
console.log({column})
However, eval has a big caveat, if we don't sanitize and don't add some layers of security, then we will have a big security whole that can be expose.
2. The Good
If we know the class names that we will use, then we can create a lookup table like this:
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
// construct dict object that contains our mapping between strings and classes
const classMap = new Map([
['Column', Column],
['Sequence', Sequence],
['Checkbox', Checkbox],
])
function instantiator(name, ...p) {
return new(classMap.get(name))(...p)
}
// make a class from a string
let object = instantiator('Column', 'box')
object = instantiator('Sequence', 'box', 'index')
object = instantiator('Checkbox', 'box')
3. The Pattern
Finally, we can just create a Factory class that will safety handle the allowed classes and throw an error if it can load it.
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
class ClassFactory {
static class(name) {
switch (name) {
case 'Column':
return Column
case 'Sequence':
return Sequence
case 'Checkbox':
return Checkbox
default:
throw new Error(`Could not instantiate ${name}`);
}
}
static create(name, ...p) {
return new(ClassFactory.class(name))(...p)
}
}
// make a class from a string
let object
object = ClassFactory.create('Column', 'box')
object = ClassFactory.create('Sequence', 'box', 'index')
object = ClassFactory.create('Checkbox', 'box')
I recommend The Good method. It is is clean and safe. Also, it should be better than using global or window object:
class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (JavaScript trying to avoid adding more junk on top of prior design mistakes).
Therefore we will not pollute the global object because we are using a local classMap object to lookup the required class.

eslint no-unused-vars when using js classes

When I declare a class with ctor, and then use this class only as a function's input parameter, I get no-unused-vars from eslint.
Code:
/**
* A class
*/
class SampleClass{
/**
* Ctor
* #param {string} par - a parameter
*/
constructor(par) {
this.par = par;
}
}
/**
* Func
* #param {SampleClass} param - a sample class
* #returns {SampleClass} - the same class
*/
function fName(param) {
return param;
}
So as you can see, I use SampleClass both as input parameter and as return type of the function, but eslint shows error: 'A class "SampleClass" is defined but never used. eslint no-unused-vars'
How can i fix this? Of course, I need to leave this type of check workong.
I couldn't fully understand what you wanted to do with that function and class, but within my understanding, I think this would work
class SampleClass {
constructor(par){
this.par = par;
}
getInstance() {
return this.par
}
}
function fName(params) {
return params
}
const newClass = new SampleClass("param");
fName(newClass.getInstance())
I found that I need to use a cleverer eslint parser - #babel/eslintparser. It does not raise this error when I use classes.

Can You Implement Javascript Class Via Function Expression?

I was tackling this question in leet code:
Implement the MapSum class:
MapSum() Initializes the MapSum object. void insert(String key, int
val) Inserts the key-val pair into the map. If the key already
existed, the original key-value pair will be overridden to the new
one. int sum(string prefix) Returns the sum of all the pairs' value
whose key starts with the prefix.
In javascript, the template for solving the question is:
/**
* Initialize your data structure here.
*/
var MapSum = function() {
};
/**
* #param {string} key
* #param {number} val
* #return {void}
*/
MapSum.prototype.insert = function(key, val) {
};
/**
* #param {string} prefix
* #return {number}
*/
MapSum.prototype.sum = function(prefix) {
};
/**
* Your MapSum object will be instantiated and called as such:
* var obj = new MapSum()
* obj.insert(key,val)
* var param_2 = obj.sum(prefix)
*/
I was struck by the class template. I'm used to seeing javascript classes more similar to this:
class MapSum {
constructor() {
}
insert(key, value) {
}
sum(prefix) {
}
}
Is the template leetcode provided, considered a class? What kind of class is it? What is it called when you initialize an object via function expression (var MapSum = function() { //....}) ? What are the biggest differences/implications from writing a class that way vs the way I suggested?
The class keyword is actually just syntaxic sugar over prototypal inheritance
This code demonstrates that the two syntaxes are equivalent:
class MapSum {
constructor() {
}
insert(key, value) {
}
sum(prefix) {
}
}
console.log(typeof MapSum); // function (actually the constructor)
console.log(MapSum.prototype.insert); // function
console.log(MapSum.prototype.sum); // function
A class is actually just a constructor function, which has a special object named prototype attached to it. Every instance of the class has an internal link to the prototype of the constructor.

Create object from class name in JavasScript ECMAScript 6

I want create object factory using ES6 but old-style syntax doesn't work with new.
I have next code:
export class Column {}
export class Sequence {}
export class Checkbox {}
export class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new window[className](name); // this line throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
What do I do wrong?
Don't put class names on that object. Put the classes themselves there, so that you don't have to rely on them being global and accessible (in browsers) through window.
Btw, there's no good reason to make this factory a class, you would probably only instantiate it once (singleton). Just make it an object:
export class Column {}
export class Sequence {}
export class Checkbox {}
export const columnFactory = {
specColumn: {
__default: Column, // <--
__sequence: Sequence, // <--
__checkbox: Checkbox // <--
},
create(name, ...args) {
let cls = this.specColumn[name] || this.specColumn.__default;
return new cls(...args);
}
};
There is a small & dirty way to do that:
function createClassByName(name,...a) {
var c = eval(name);
return new c(...a);
}
You can now create a class like that:
let c = createClassByName( 'Person', x, y );
The problem is that the classes are not properties of the window object. You can have an object with properties "pointing" to your classes instead:
class Column {}
class Sequence {}
class Checkbox {}
let classes = {
Column,
Sequence,
Checkbox
}
class ColumnFactory {
constructor() {
this.specColumn = {
__default: 'Column',
__sequence: 'Sequence',
__checkbox: 'Checkbox'
};
}
create(name) {
let className = this.specColumn[name] ? this.specColumn[name] : this.specColumn['__default'];
return new classes[className](name); // this line no longer throw error
}
}
let factory = new ColumnFactory();
let column = factory.create('userName');
export {ColumnFactory, Column, Sequence, Checkbox};
For those of you that are not using ES6 and want to know how you can create classes by using a string here is what I have done to get this to work.
"use strict";
class Person {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
window.classes = {};
window.classes.Person = Person;
document.body.innerText = JSON.stringify(new window.classes["Person"](1, 2));
As you can see the easiest way to do this is to add the class to an object.
Here is the fiddle:
https://jsfiddle.net/zxg7dsng/1/
Here is an example project that uses this approach:
https://github.com/pdxjohnny/dist-rts-client-web
I prefer this method:
allThemClasses.js
export class A {}
export class B {}
export class C {}
script.js
import * as Classes from './allThemClasses';
const a = new Classes['A'];
const b = new Classes['B'];
const c = new Classes['C'];
I know this is an old post, but recently I've had the same question about how to instance a class dynamically
I'm using webpack so following the documentation there is a way to load a module dynamically using the import() function
js/classes/MyClass.js
class MyClass {
test = null;
constructor(param) {
console.log(param)
this.test = param;
}
}
js/app.js
var p = "example";
var className = "MyClass";
import('./classes/'+className).then(function(mod) {
let myClass = new mod[className](p);
console.log(myClass);
}, function(failMsg) {
console.error("Fail to load class"+className);
console.error(failMsg);
});
Beware: this method is asynchronous and I can't really tell the performance cost for it,
But it works perfectly on my simple program (worth a try ^^)
Ps: To be fare I'm new to Es6 (a couple of days) I'm more a C++ / PHP / Java developer.
I hope this helps anyone that come across this question and that is it not a bad practice ^^".
Clarification
There are similar questions to this, including this SO question that was closed, that are looking for proxy classes or factory functions in JavaScript; also called dynamic classes. This answer is a modern solution in case you landed on this answer looking for any of those things.
Answer / Solution
As of 2022 I think there is a more elegant solution for use in the browser. I made a class called Classes that self-registers the property Class (uppercase C) on the window; code below examples.
Now you can have classes that you want to be able to reference dynamically register themselves globally:
// Make a class:
class Handler {
handleIt() {
// Handling it...
}
}
// Have it register itself globally:
Class.add(Handler);
// OR if you want to be a little more clear:
window.Class.add(Handler);
Then later on in your code all you need is the name of the class you would like to get its original reference:
// Get class
const handler = Class.get('Handler');
// Instantiate class for use
const muscleMan = new (handler)();
Or, even easier, just instantiate it right away:
// Directly instantiate class for use
const muscleMan = Class.new('Handler', ...args);
Code
You can see the latest code on my gist. Add this script before all other scripts and all of your classes will be able to register with it.
/**
* Adds a global constant class that ES6 classes can register themselves with.
* This is useful for referencing dynamically named classes and instances
* where you may need to instantiate different extended classes.
*
* NOTE: This script should be called as soon as possible, preferably before all
* other scripts on a page.
*
* #class Classes
*/
class Classes {
#classes = {};
constructor() {
/**
* JavaScript Class' natively return themselves, we can take advantage
* of this to prevent duplicate setup calls from overwriting the global
* reference to this class.
*
* We need to do this since we are explicitly trying to keep a global
* reference on window. If we did not do this a developer could accidentally
* assign to window.Class again overwriting any classes previously registered.
*/
if (window.Class) {
// eslint-disable-next-line no-constructor-return
return window.Class;
}
// eslint-disable-next-line no-constructor-return
return this;
}
/**
* Add a class to the global constant.
*
* #method
* #param {Class} ref The class to add.
* #return {boolean} True if ths class was successfully registered.
*/
add(ref) {
if (typeof ref !== 'function') {
return false;
}
this.#classes[ref.prototype.constructor.name] = ref;
return true;
}
/**
* Checks if a class exists by name.
*
* #method
* #param {string} name The name of the class you would like to check.
* #return {boolean} True if this class exists, false otherwise.
*/
exists(name) {
if (this.#classes[name]) {
return true;
}
return false;
}
/**
* Retrieve a class by name.
*
* #method
* #param {string} name The name of the class you would like to retrieve.
* #return {Class|undefined} The class asked for or undefined if it was not found.
*/
get(name) {
return this.#classes[name];
}
/**
* Instantiate a new instance of a class by reference or name.
*
* #method
* #param {Class|name} name A reference to the class or the classes name.
* #param {...any} args Any arguments to pass to the classes constructor.
* #returns A new instance of the class otherwise an error is thrown.
* #throws {ReferenceError} If the class is not defined.
*/
new(name, ...args) {
// In case the dev passed the actual class reference.
if (typeof name === 'function') {
// eslint-disable-next-line new-cap
return new (name)(...args);
}
if (this.exists(name)) {
return new (this.#classes[name])(...args);
}
throw new ReferenceError(`${name} is not defined`);
}
/**
* An alias for the add method.
*
* #method
* #alias Classes.add
*/
register(ref) {
return this.add(ref);
}
}
/**
* Insure that Classes is available in the global scope as Class so other classes
* that wish to take advantage of Classes can rely on it being present.
*
* NOTE: This does not violate https://www.w3schools.com/js/js_reserved.asp
*/
const Class = new Classes();
window.Class = Class;
This is an old question but we can find three main approaches that are very clever and useful:
1. The Ugly
We can use eval to instantiate our class like this:
class Column {
constructor(c) {
this.c = c
console.log(`Column with ${this.c}`);
}
}
function instantiator(name, ...params) {
const c = eval(name)
return new c(...params)
}
const name = 'Column';
const column = instantiator(name, 'box')
console.log({column})
However, eval has a big caveat, if we don't sanitize and don't add some layers of security, then we will have a big security whole that can be expose.
2. The Good
If we know the class names that we will use, then we can create a lookup table like this:
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
// construct dict object that contains our mapping between strings and classes
const classMap = new Map([
['Column', Column],
['Sequence', Sequence],
['Checkbox', Checkbox],
])
function instantiator(name, ...p) {
return new(classMap.get(name))(...p)
}
// make a class from a string
let object = instantiator('Column', 'box')
object = instantiator('Sequence', 'box', 'index')
object = instantiator('Checkbox', 'box')
3. The Pattern
Finally, we can just create a Factory class that will safety handle the allowed classes and throw an error if it can load it.
class Column {
constructor(c) {
console.log(`Column with ${c}`)
}
}
class Sequence {
constructor(a, b) {
console.log(`Sequence with ${a} and ${b}`)
}
}
class Checkbox {
constructor(c) {
console.log(`Checkbox with ${c}`)
}
}
class ClassFactory {
static class(name) {
switch (name) {
case 'Column':
return Column
case 'Sequence':
return Sequence
case 'Checkbox':
return Checkbox
default:
throw new Error(`Could not instantiate ${name}`);
}
}
static create(name, ...p) {
return new(ClassFactory.class(name))(...p)
}
}
// make a class from a string
let object
object = ClassFactory.create('Column', 'box')
object = ClassFactory.create('Sequence', 'box', 'index')
object = ClassFactory.create('Checkbox', 'box')
I recommend The Good method. It is is clean and safe. Also, it should be better than using global or window object:
class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (JavaScript trying to avoid adding more junk on top of prior design mistakes).
Therefore we will not pollute the global object because we are using a local classMap object to lookup the required class.

Why was my javascript code not accepted at interview?

The interviewer told me that I have to follow javascript "patterns" and write "clean code". He also said that I should follow the prototype pattern. Here is my code sample:
//namespace declrations
var MyNamespace = {};
MyNamespace.UCs = {};
MyNamespace.Pages = {};
//function declarations
MyNamespace.UCs.test = function () { alert('this is a test function in user control namespace.'); }
MyNamespace.Pages.test = function () { alert('this is a test function in web page namespace.'); }
So can anybody point me to why this code is not ok? I mean, I have declared namespaces first and then added my members and functions like the above sample. So does it really have issues or am I missing something?
Well when you are writing code in a large environment, lots of problems can start happening. So it's important to separate your class definitions from how you use those classes. Which also means you have to make classes that can be unit tested to prove that they do what you say they do. Javascript is not a true object orientated language and as such there are several ways to "fake" it. But because the language has a lot of flexibility, we can duplicate some approaches.
One thing we want to stay away from is something called function scope simply because it can cause unintended "features" later down the road when 3 or 4 other programmers start making assumptions about what your code is doing. If they don't know a global variable was overwritten one or two function closures ago, it will make finding that problem more difficult. So I would suggest using a small class created by John Resig as it provides a very simple approach that gives you alot of the functionality you need.
So let's write some code.
var myNamespace = myNamespace || { }
/**
* Used to store a single entry in the log
*
* #class
*/
var myNamespace.LogEntry = Class.extend({
/**
* Used to track the beginning of the page load
*
* #private
* #static
*/
PAGE_LOAD_TIME = new Date().getTime(),
/**
* Used to store the current time
*
* #type int
*/
time : undefined,
/**
* The message of this log entry
*
* #type string
*/
msg : undefined,
/**
* #constructor
*
* #param {string} msg The message of this log entry
*/
init : function (msg) {
this.time = new Date().getTime() - this.PAGE_LOAD_TIME;
this.msg = msg
},
/**
* Displays this log entry in a single string
*
* #return {string} String representation of this log entry
*/
toString : function () {
return this.time + ": " + this.msg;
}
});
/**
* Used to store a log entry that has data associated with it.
*
* #class
* #extends myNamespace.LogEntry
*/
var myNamespace.DataEntry = myNamespace.LogEntry.extend({
/**
* Used to store data associated with this log entry
*
* #type object
*/
data : undefined,
/**
* #constructor
*
* #param {string} msg The message that describes this log entry
* #param {object} data The data associated with this entry
*/
init : function (msg, data) {
this._super(msg);
this.data = data;
},
/**
* #return {string} The string representation of this log entry
*/
toString : function () {
// Uses a JSON library to stringify the data into a json string.
return this._super() + JSON.stringify(this.data);
}
});
/**
* Provides an interface to log messages
*
* #class
*/
var myNamespace.Log = Class.extend({
/**
* Stores log entries
*
* #type myNamespace.LogEntry[]
*/
log : undefined,
/**
* #constructor
*/
init : function () {
this.log = [ ];
},
/**
* Logs a message into the log
*
* #param {string} msg The message you want to log
*/
msg : function (msg) {
this.log.push(new myNamespace.LogEntry(msg));
},
/**
* Log a message and data into the log
*
* #param {string} msg The message of this log entry
* #param {object} data The data associated with this log entry
*/
data : function(msg, data) {
this.log.push(new myNamespace.DataEntry(msg, data));
}
});
Ok, there is lots of stuff going on here. The main part is that this is all definitions of classes. I don't actually use anything up there. The program only stores the current time in LogEntry.PAGE_LOAD_START which is declared #static so the behavior will be expected. I've used lots of jsDocs here to make everything clear about what the intentions are. A program like intelliJ can use those to give your code feedback if you aren't using the classes the way you've documentated them.
This program will let you create and store log entries with possibly logging data. There are alot of other ways to do this. I declare everything before the constructor instead of inside the constructor so that I can document the types and whether they are private.
A programmer that has to use the log will know exactly how to use it and if they want to create or extend these classes, they can do so without unintended effects from function closure.
Here's how to use it:
var anotherNamespace = anotherNamespace || {};
var anotherNamespace = new myNamespace.Log();
...
anotherNamespace.log.msg("This is a test");
...
anotherNamespace.log.data("Test msg with data", data);
Of course the obvious thing missing here is a way to display all the data. But that could be in another class that iterates through the Log.log array and spits out the toString() into a web page or file. The point here is that the classes and their functions are simple, unit testable, and definition only.
FIrst of all, make use of object literals, all those assignments are a waste lines.
Second, you don't have the prototype "pattern" implemented anywhere, also I'd go for some encapsulation if you have full control over the namespaces:
(function() { // anonymous wrapper
function Foo(a) { // i can haz prototype "Pattern"
this.memberVal = a;
}
Foo.prototype = {
someMethod: function(value) {
console.log(value, this.memberVal);
},
yam: function() {
}
};
// Let's assume we have full control here, otherwise Alnitak's answer is fine too.
window.namespace = {
test: {
Foo: Foo
}
};
})();
var test = new namespace.test.Foo(123);
test.someMethod('bla');
Maybe the interviewer literally meant you should be using the 'namespace' method?
Tutorial with example of usage:
http://elegantcode.com/2011/01/26/basic-javascript-part-8-namespaces/
Your code looks (mostly) fine, if the intent was to just put utility functions in a shared namespace, without any object orientation or data encapsulation.
I would have made one change, on the initial name space declarations:
var MyNamespace = MyNamespace || {};
MyNamespace.UCs = MyNamespace.UCs || {};
MyNamespace.Pages = Mynamespace.Pages || {};
to ensure that the code didn't obliterate any existing methods in those namespaces.

Categories