Skip to content

Additional Examples

mbrowne edited this page Nov 29, 2012 · 6 revisions

Mixins / multiple inheritance with properties in addition to methods

var mixin = simpleoo.mixin;

function Person() {}

Person.prototype.addRoles = function(role1 /*, role2, ... */) {
	if (this.initRole) {
		throw new Error("addRoles method doesn't work if an initRole method exists on the Person prototype");
	}
	for(var i in arguments) {
		var role = arguments[i];
		if (typeof role.initRole=='function') {
			role.initRole.call(this);
		}		
		mixin(this, role);
	}
	delete this.initRole;
};

var studentRole = {
	initRole: function() {
		console.log('init student');
		
		//It's important to define properties in some sort of init function rather than directly on the
		//trait or role object, for the same reason that default values for object properties should be
		//initialized in the constructor for regular "classes." See "Another tip" above.
		
		this.school = null;
		this.numCourses = null;
	},
	study: function() {}
}

var employeeRole = {
	initRole: function() {
		console.log('init employee');
		this.employer = null;
	},
	work: function() {}
}

var fred = new Person();
fred.addRoles(studentRole, employeeRole);

fred.school = fred.employer = "Cyberland College";
fred.numCourses = 4;

console.log(fred);

Static properties and methods

A common convention to emulate static members in Javascript is to put them on the constructor function:

function Animal() {}
Animal.BIOLOGICAL_CLASSES = {
	Mammal: 1,
	Bird: 2,
	Fish: 3
	//...
};

Animal.someStaticMethod = function() { /* do something */ };

Static members can easily be added all in one go by simply using the mixin function:

mixin(Animal, {
	BIOLOGICAL_CLASSES: {
		Mammal: 1,
		Bird: 2,
		Fish: 3
		//...
	},
	someStaticMethod = function() { /* do something */ }
});

It should be noted that the differentiation between static and non-static methods (note that I did not say "properties") is less important in Javascript than it is in languages with classical (class-based) inheritance and basically comes down to your preferred method of code organization. Let me be clear: I'm saying that static methods may be set either on the prototype or on the constructor; obviously, the same can't be said for instance methods (at least not without some alternative techniques, like the one used below in the "Private Methods" example).

For properties, the distinction matters more, but you still have more than one option: it's also possible to put static properties on the prototype, e.g.:

Animal.prototype.BIOLOGICAL_CLASSES = ...

However, keep in mind that if you do that, it's possible for individual instances to override those properties.

Private methods

Option 1: Naming Convention - leading underscore (doesn't truly prevent outside access, just discourages it)

Animal.prototype = makePrototype(Animal, {
	_isPreferredFood: function(food) {
		return food == this.preferredFood;
	},
	
	eat: function(food) {
		if (! this._isPreferredFood(food) ) console.log('Yuck!');
	}
});

return Animal;  //assumes that this code sample is wrapped inside a closure of some kind (e.g. an AMD or CommonJS module)

Option 2: Declare the method outside the prototype and avoid using the this keyword. This example assumes that all the code for the "class" is inside a closure so that the private method is only available internally.

//private method
function isPreferredFood(self, food) {
	return food == self.preferredFood;
}

Animal.prototype = makePrototype(Animal, {
	eat: function(food) {
		if (! isPreferredFood(this, food) ) console.log('Yuck!');
	}
});

return Animal;

Remember that one of the cool features of Javascript is that you can override functions with new functions, even from outside a "class" (since there aren't truly any classes, only objects). Private methods prevent you and others who may want to extend your code (for reasons you can't always anticipate) from doing so. They can be useful, to be sure, but make sure you consider the decision carefully.

Why no example of private instance properties?

Coming Soon:

A shim for "private symbols" that works in ECMAScript 5, and which degrades gracefully for older browsers.

...

Private static properties are easy; simply create a variable outside the "class" definition.

But there is no really elegant way to create private instance properties in the currently available versions of Javascript, although there is a proposal to add a new feature to ECMAScript.Next (probably ECMAScript 6) called "Private Symbols" (http://tc39wiki.calculist.org/es6/symbols/).

ECMAScript 5 has already made some progress in this respect, with property descriptors that allow you to control whether a property is writable, enumerable, and/or configurable, but this falls short of being able to create properties that are completely inaccessible from outside the object.

The closest thing to an elegant method for creating private instance properties is the "module pattern," but that pattern comes with a lot of disadvantages; for one, it misses out on the main strengths of Javascript's prototypal inheritance model, and wouldn't work well in conjunction with this library. (Because of this, it also doesn't use memory as efficiently.)

Given that it will probably be possible to create private properties using the "private symbols" feature in ES6 (ECMAScript 6), the most straightforward solution for now is to either forget about private properties or use the leading underscore naming convention.

Although there aren't any real implementations of "private symbols" at this point, it's possible that this feature or perhaps a new feature that has yet to be introduced will provide a more natural way of creating private methods in addition to private properties.

If you really need truly private properties that will work in all versions of Javascript, there is a way to accomplish it, it's just a little less intuitive, and won't be future compatible with private symbols, assuming that proposal is accepted.

http://jeditoolkit.com/2011/04/11/shareable-private-properties.html

When deciding which approach to use -- for both private methods and private instance properties -- you might consider whether or not you plan to use the leading underscore convention if/when all modern browsers support "private symbols" (which is admittedly a long way off). It's important to question whether you truly need private properties, and to remember that in many cases, you can write very robust Javascript without even emulating private properties at all. If private properties wouldn't make a big difference to the particular code you're writing, then you might consider forgetting the underscores, knowing that in the future ES.Next will probably provide a more natural way to create private members.

Deep-copying (cloning) objects

Sometimes it's useful to be able to make a complete and exact clone of an object, including all its sub-properties, and their sub-properties, etc. Here's an example:

Let's say you have a function that you want to take an options object as an argument. You also want to specify some defaults, to be used for any properties that the user doesn't specify.

Let's further suppose that your defaults object includes some object properties in addition to simple literals, e.g.:

var defaults = {
	foo: null,
	num: 2,
	
	//object properties
	myArray: [],
	myObject: {
		color: 'red',
		foo: 'bar'
	},
	myDate: new Date()  //default to today
};

A naiive approach might be to use extend():

function mySuperCoolInitializationFunction(options) {
	var localOptions = extend(defaults, options); 
}

But this is problematic because for the object properties, any modifications to localOptions will also affect defaults. We want to make sure defaults never gets modified at all!

The inadvertent modification is a problem even if we start with an empty object to be used as the prototype:

function mySuperCoolInitializationFunction(options) {
	var localOptions = extend({}, defaults, options);
	if (localOptions.num == 2) {
		localOptions.myArray.push('something');
	}
}

mySuperCoolInitializationFunction({});
console.log(defaults.myArray);  //outputs ["something"] -- not good, we've inadvertently modified the defaults object!

We also want to avoid modifying any properties of the options object that the user gives us. We need a better solution.

Using deepCopy(), we can safely make any modifications to localOptions and be sure that none of the properties of defaults or options will ever be modified:

var localOptions = deepCopy( extend(defaults, options) );