I wonder about what the best way is to create an JavaScript object that has properties and methods.
I have seen examples where the person used var self = this<
When one uses the trick of closing on "this" during a constructor invocation, it's in order to write a function that can be used as a callback by some other object that doesn't want to invoke a method on an object. It's not related to "making the scope correct".
Here's a vanilla JavaScript object:
function MyThing(aParam) {
var myPrivateVariable = "squizzitch";
this.someProperty = aParam;
this.useMeAsACallback = function() {
console.log("Look, I have access to " + myPrivateVariable + "!");
}
}
// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
console.log(this.someProperty);
};
You might get a lot out of reading what Douglas Crockford has to say about JavaScript. John Resig is also brilliant. Good luck!
You can also do it this way, using structures :
function createCounter () {
var count = 0;
return {
increaseBy: function(nb) {
count += nb;
},
reset: function {
count = 0;
}
}
}
Then :
var counter1 = createCounter();
counter1.increaseBy(4);
The easiest way to create an object in JavaScript is to use the following syntax :
var test = {
a : 5,
b : 10,
f : function(c) {
return this.a + this.b + c;
}
}
console.log(test);
console.log(test.f(3));
This works great for storing data in a structured way.
For more complex use cases, however, it's often better to create instances of functions :
function Test(a, b) {
this.a = a;
this.b = b;
this.f = function(c) {
return this.a + this.b + c;
};
}
var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));
This allows you to create multiple objects that share the same "blueprint", similar to how you use classes in eg. Java.
This can still be done more efficiently, however, by using a prototype.
Whenever different instances of a function share the same methods or properties, you can move them to that object's prototype. That way, every instance of a function has access to that method or property, but it doesn't need to be duplicated for every instance.
In our case, it makes sense to move the method f
to the prototype :
function Test(a, b) {
this.a = a;
this.b = b;
}
Test.prototype.f = function(c) {
return this.a + this.b + c;
};
var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));
A simple but effective way to do inheritance in JavaScript, is to use the following two-liner :
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
That is similar to doing this :
B.prototype = new A();
The main difference between both is that the constructor of A
is not run when using Object.create, which is more intuitive and more similar to class based inheritance.
You can always choose to optionally run the constructor of A
when creating a new instance of B
by adding adding it to the constructor of B
:
function B(arg1, arg2) {
A(arg1, arg2); // This is optional
}
If you want to pass all arguments of B
to A
, you can also use Function.prototype.apply() :
function B() {
A.apply(this, arguments); // This is optional
}
If you want to mixin another object into the constructor chain of B
, you can combine Object.create
with Object.assign :
B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;
function A(name) {
this.name = name;
}
A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;
function B() {
A.apply(this, arguments);
this.street = "Downing Street 10";
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
function mixin() {
}
mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;
mixin.prototype.getProperties = function() {
return {
name: this.name,
address: this.street,
year: this.year
};
};
function C() {
B.apply(this, arguments);
this.year = "2018"
}
C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;
var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());
Object.create
can be safely used in every modern browser, including IE9+. Object.assign
does not work in any version of IE nor some mobile browsers. It is recommended to polyfill Object.create
and/or Object.assign
if you want to use them and support browsers that do not implement them.
You can find a polyfill for Object.create
here
and one for Object.assign
here.
I use this pattern fairly frequently - I've found that it gives me a pretty huge amount of flexibility when I need it. In use it's rather similar to Java-style classes.
var Foo = function()
{
var privateStaticMethod = function() {};
var privateStaticVariable = "foo";
var constructor = function Foo(foo, bar)
{
var privateMethod = function() {};
this.publicMethod = function() {};
};
constructor.publicStaticMethod = function() {};
return constructor;
}();
This uses an anonymous function that is called upon creation, returning a new constructor function. Because the anonymous function is called only once, you can create private static variables in it (they're inside the closure, visible to the other members of the class). The constructor function is basically a standard Javascript object - you define private attributes inside of it, and public attributes are attached to the this
variable.
Basically, this approach combines the Crockfordian approach with standard Javascript objects to create a more powerful class.
You can use it just like you would any other Javascript object:
Foo.publicStaticMethod(); //calling a static method
var test = new Foo(); //instantiation
test.publicMethod(); //calling a method
Douglas Crockford discusses that topic extensively in The Good Parts. He recommends to avoid the new operator to create new objects. Instead he proposes to create customized constructors. For instance:
var mammal = function (spec) {
var that = {};
that.get_name = function ( ) {
return spec.name;
};
that.says = function ( ) {
return spec.saying || '';
};
return that;
};
var myMammal = mammal({name: 'Herb'});
In Javascript a function is an object, and can be used to construct objects out of together with the new operator. By convention, functions intended to be used as constructors start with a capital letter. You often see things like:
function Person() {
this.name = "John";
return this;
}
var person = new Person();
alert("name: " + person.name);**
In case you forget to use the new operator while instantiating a new object, what you get is an ordinary function call, and this is bound to the global object instead to the new object.
var Klass = function Klass() {
var thus = this;
var somePublicVariable = x
, somePublicVariable2 = x
;
var somePrivateVariable = x
, somePrivateVariable2 = x
;
var privateMethod = (function p() {...}).bind(this);
function publicMethod() {...}
// export precepts
this.var1 = somePublicVariable;
this.method = publicMethod;
return this;
};
First, you may change your preference of adding methods to the instance instead of the constructor's prototype
object. I almost always declare methods inside of the constructor because I use Constructor Hijacking very often for purposes regarding Inheritance & Decorators.
Here's how I decide where which declarations are writ:
this
)var
declarations take precedence over function
declarations{}
and []
)public
declarations take precedence over private
declarationsFunction.prototype.bind
over thus
, self
, vm
, etc
this
from within the Lexical Scope of the Closure Space.Here's why these help:
Constructor Hijackingvar Super = function Super() {
...
this.inherited = true;
...
};
var Klass = function Klass() {
...
// export precepts
Super.apply(this); // extends this with property `inherited`
...
};
Model Design
var Model = function Model(options) {
var options = options || {};
this.id = options.id || this.id || -1;
this.string = options.string || this.string || "";
// ...
return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true); // > true
Design Patterns
var Singleton = new (function Singleton() {
var INSTANCE = null;
return function Klass() {
...
// export precepts
...
if (!INSTANCE) INSTANCE = this;
return INSTANCE;
};
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true); // > true
As you can see, I really have no need for thus
since I prefer Function.prototype.bind
(or .call
or .apply
) over thus
. In our Singleton
class, we don't even name it thus
because INSTANCE
conveys more information. For Model
, we return this
so that we can invoke the Constructor using .call
to return the instance we passed into it. Redundantly, we assigned it to the variable updated
, though it is useful in other scenarios.
Alongside, I prefer constructing object-literals using the new
keyword over {brackets}:
var klass = new (function Klass(Base) {
...
// export precepts
Base.apply(this); //
this.override = x;
...
})(Super);
Not Preferred
var klass = Super.apply({
override: x
});
As you can see, the latter has no ability to override its Superclass's "override" property.
If I do add methods to the Class's prototype
object, I prefer an object literal -- with or without using the new
keyword:
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
...
// export precepts
Base.apply(this);
...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
method: function m() {...}
};
Not Preferred
Klass.prototype.method = function m() {...};