Skip to content

Instantly share code, notes, and snippets.

@vlazzle
Forked from damienklinnert/approach.js
Created November 23, 2012 00:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vlazzle/513f7a71368835cbef2f to your computer and use it in GitHub Desktop.
Save vlazzle/513f7a71368835cbef2f to your computer and use it in GitHub Desktop.
A BETTER JAVASCRIPT OBJECT APPROACH
// A BETTER JAVASCRIPT OBJECT APPROACH
// 1. Introduction
// This code demonstrates how objects in javascript should be created. It shows
// best practices for constructor functions, attribute assignment and
// inheritance.
// NOTE: There are many different opinions and ways to deal with object sets and
// inheritance in JavaScript. We decided to use this approach, because it is a
// quite common style, its JavaScript's most native approach using classical
// prototypal inheritance, is pure ECMAScript5 without any external library and
// is the way the new class methods in ECMAScript6 will be handled (those are
// just syntactic sugar).
// The community uses JavaScript this way, the newest ECMAScript versions show
// that the language is developed this way and it's the most performant way.
// However, this way of dealing with objects is highly based on trust. It's very
// dynamic and extendable, but also insecure. There are no true private
// attributes and we trust in other developers to follow our guidelines. If they
// don't, they can do big damage to the whole system.
// Nevertheless, this is how JavaScript was defined (dynamically). We should
// utitlize this way of JavaScript and not force it into something it definitely
// is not (e.g. static typed).
// Note: To fully understand this article, you'll need a basic understanding of
// JavaScripts four ways to invoke a function and the internals of some
// JavaScript engines. Nonetheless, you can follow this guide anyway.
// 2. Creating Objects
// First of all start by defining a new type, in this case a car. Start with a
// capital letter to make clear that this function must be invoked with new.
var Car = function () {
this._velocity = 0;
this._speed = 0;
this._color = null;
};
// For performance reasons, you always have to set all attributes in the
// constructor function to some value, even if it is only null. By that way the
// JavaScript compiler/interpreter can optimize the memory usage of all objects
// as he can create them from the same scheme.
// Also, be aware that there are no private attributes in JavaScript. A common
// workaround is to prefix all attributes with an underscore to show that they
// should not be modified from the outside. This is only build on trust!
// 3. Adding Methods
// To add methods to instances of the Car object, we need to set them on the
// prototype key. That way we ensure optimal performance as all Car instances
// use the same function implementation only with a different environment. This
// saves tones of memory. Never ever define functions in the constructor
// function, if you do so, each object will have its own implementation.
Car.prototype.drive = function () {
// …
};
// To add static methods to the Car object, simply define them on the
// constructor function. All static methods will use the same implementation and
// are now simply namespaced.
Car.staticMethod = function () {
};
// 4. Getters and Setters
// Although all attributes can be accessed directly on such objects, that is not
// a good idea and should be prevented. To inform other developers it's a common
// practice to prefix these attributes with an underscore.
// Getters have the same name as the attributes. They take no params and always
// return this value.
Car.prototype.velocity = function () {
return this._velocity;
};
// Getters that return booleans should be prefixed with `is` or `has`. Example:
Car.isMotorActive = function () {
return this._isMotorActive;
};
// Setters are prefixed with `set` and have the same name as their attributes.
// They take on param and always return `this` for chaining setters. Be sure
// to check the value of the new variable in the getter (if neccessary).
Car.prototype.setVelocity = function (velocity) {
this._velocity = velocity;
return this;
};
// That way setters can be chained together like this:
var myCar = new Car();
myCar.setVelocity(0)
.setSpeed(0)
.setSomethingElse(0);
// 5. Inheritance
// First of all, we need to create a new constructor function.
var FireCar = function (arg1, arg2) {
Car.call(this, arg1, arg2);
this._color = '#ff0000';
this._hasLedge = true;
};
// The most important line here is the call to its parent's constructor via
// `Car.call`. This line calls the Car constructor with this bound to our new
// FireCar object. This way, the new FireCar object will be assigned all of its
// parents attributes. This is important for performance reasons again.
// We then add our own modifications and new attributes to our FireCar object in
// the constructor.
// Next, we need to ensure that whenever a function is called, which is not set
// on FireCar, the prototype chain can be followed completely and the parent
// implementation is used.
FireCar.prototype = new Car();
// This will override FireCars default prototype with a new Object made from
// Car.prototype. When an attribute on FireCar is accessed that is not defined
// there, the complete prototype chain will be followed (single-inheritance).
// To simply override a complete method in FireCar, all you have to do is to
// define it on FireCar.prototype like this:
FireCar.prototype.drive = function () {
// …
};
// If you want to override a method in FireCar, but still want to use the old
// implementation, use call like in the constructor function:
FireCar.prototype.drive = function (arg1, arg2) {
var result = Car.prototype.call(this, arg1, arg2);
// do some more stuff (and own implementation)
return result + 1;
};
// That way, all methods are only defined once. Even the FireCar.drive method
// uses the Car.drive implementation. That saves a lot of memory.
// 6. Mixins and other ways to extend objects
// As this way to define objects is highly flexible, you can use all ways to
// extend objects. You can use mixins, inheritance or something else. Just be
// sure to document what you do – and do it responsible.
// 7. Files and Namespaces
// Each object should solve a single purpose and should be implemented in a
// single file. Be sure to properly use namespaces and stop prefixing your
// objects with your project initials or something similar. That's very annoying
// when it comes to using this objectsc.
// 8. Log Objects
// When you want to log objects, you should be aware what you do, because
// `console.log` hides the prototype chain, but shows all the private
// attributes.
// this is wrong and only shows data
console.log(myCar);
// this is correct, hides data and only shows functions
console.log(myCar.__proto__);
// 9. Iterate and Typecheck
// To iterate over an object, be sure to utilize object.hasOwnProperty,
// otherwise you will iterate over the complete prototype chain. This is
// often unwanted behaviour.
// To typecheck an object, simple use `instanceof`. This works with the complete
// prototype chain and all of its objects.
if (myCar instanceof Car) {
// …
}
@damienklinnert
Copy link

thank you for this bugfix fork!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment