-
-
Save 0x-2a/3bb45c80b7c8f9636112 to your computer and use it in GitHub Desktop.
JavaScript Class Structure using requireJS. The following code shows you how to create a Class definition in one JavaScript file and then import it for use in another; coming from an ActionScript 3 background this (and some of JavaScript specific traits)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<script data-main="usage" src="http://requirejs.org/docs/release/1.0.8/comments/require.js"></script> | |
</head> | |
<body> | |
<p>Check your JavaScript console for output!</p> | |
</body> | |
</head> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This example make use of requireJS to provide a clean and simple way to split JavaScript class definitions | |
* into separate files and avoid global namespace pollution. http://requirejs.org/ | |
* | |
* We start by defining the definition within the require block inside a function; this means that any | |
* new variables / methods will not be added to the global namespace; requireJS simply requires us to return | |
* a single value (function / Object) which represents this definition. In our case, we will be returning | |
* the Class' function. | |
*/ | |
define(function () { | |
// Forces the JavaScript engine into strict mode: http://tinyurl.com/2dondlh | |
"use strict"; | |
/** | |
* This is our classes constructor; unlike AS3 this is where we define our member properties (fields). | |
* To differentiate constructor functions from regular functions, by convention we start the function | |
* name with a capital letter. This informs users that they must invoke the Person function using | |
* the `new` keyword and treat it as a constructor (ie: it returns a new instance of the Class). | |
*/ | |
function Person(name) { | |
// This first guard ensures that the callee has invoked our Class' constructor function | |
// with the `new` keyword - failure to do this will result in the `this` keyword referring | |
// to the callee's scope (typically the window global) which will result in the following fields | |
// (name and _age) leaking into the global namespace and not being set on this object. | |
if (!(this instanceof Person)) { | |
throw new TypeError("Person constructor cannot be called as a function."); | |
} | |
// Here we create a member property (field) for the Person's name; setting its value | |
// what the one supplied to the Constructor. Although we don't have to define | |
// properties ahead of time (they can easily be added at runtime as all Object / functions | |
// in JavaScript are dynamic) I believe it makes your code easier to follow if you list your | |
// classes intentions up front (eg: in the Constructor function). | |
this.name = name; | |
// Here we are defining a private member. As there is no `private` keyword in JavaScript | |
// there is no way for us to hide this data (without resorting to inelegant hacks); instead | |
// we choose to use a naming convention where a leading underscore indicates a property | |
// is private and should not be relied upon as part of the Classes public API. | |
this._age = -1; | |
} | |
/** | |
* Adding static properties is as simple as adding them directly to the constructor | |
* function directly. | |
*/ | |
Person.RETIREMENT_AGE = 60; | |
/** | |
* Public Static methods are defined in the same way; here's a static constructor for our Person class | |
* which also sets the person's age. | |
*/ | |
Person.create = function (name, age) { | |
var result = new Person(name); | |
result.setAge(age); | |
return result; | |
}; | |
/** | |
* Any functions not added to the Person reference won't be visible, or accessible outside of | |
* this file (closure); however, these methods and functions don't belong to the Person class either | |
* and are static as a result. | |
*/ | |
function formatNameAndAge(person) { | |
// Note that `this` does not refer to the Person object from inside this method. | |
if (person._age === -1) { | |
return "We don't know how old " + person.name + " is!"; | |
} | |
return (person.name + ", is " + person._age + " years old and " | |
+ ((person.canRetire()) ? "can" : "can't") + " retire"); | |
}; | |
/** | |
* The prototype is a special type of Object which is used as a the blueprint for all instances | |
* of a given Class; by defining functions and properties on the prototype we reduce memory | |
* overhead. We can also achieve inheritance by pointing one classes' prototype at another, for | |
* example, if we introduced a BankManager class which extended our Person class, we could write: | |
* | |
* `BankManager.prototype = Person.prototype` | |
* `BankManager.prototype.constructor = BankManager` | |
* | |
* However, due to the dynamic nature of JavaScript I am of the opinion that favouring composition | |
* over inheritance will make your code easier to read and re-use. | |
*/ | |
Person.prototype = { | |
/** | |
* Whenever you replace an Object's Prototype, you need to repoint | |
* the base Constructor back at the original constructor Function, | |
* otherwise `instanceof` calls will fail. | |
*/ | |
constructor: Person, | |
/** | |
* All methods added to a Class' prototype are public (visible); they are able to | |
* access the properties and methods of the Person class via the `this` keyword. Note that | |
* unlike ActionScript, usage of the `this` keyword is required, failure to use it will | |
* result in the JavaScript engine trying to resolve the definition on the global object. | |
*/ | |
greet: function () { | |
// Note we have to use the `this` keyword. | |
return "Hello, " + this.name; | |
}, | |
/** | |
* Even tho the `_age` property is accessible; it still makes a lot of sense to provide | |
* mutator methods (getters / setters) which make up the public API of a Class - here we | |
* validate the supplied value; something you can't do when a field is modified directly | |
*/ | |
setAge: function (value) { | |
// Ensure the supplied value is numeric. | |
if (typeof (value) !== 'number') { | |
throw new TypeError(typeof (value) + " is not a number."); | |
} | |
// Ensure the supplied value is valid. | |
if (isNaN(value) || value < 0) { | |
throw new RangeError("Supplied value is out of range."); | |
} | |
this._age = value; | |
}, | |
/** | |
* This method access both a member property and a static property. | |
*/ | |
canRetire: function() { | |
return this._age >= Person.RETIREMENT_AGE; | |
}, | |
/** | |
* Finally we can also access 'static' functions and properties. | |
*/ | |
toString: function() { | |
// Note that as `formatNameAndAge` is static we must supply a reference | |
// to `this` so it can operate on this instance. | |
return formatNameAndAge(this); | |
} | |
}; | |
// As mentioned up top, requireJS needs us to return a value - in this files case, we will return | |
// a reference to the constructor function. | |
return Person; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Here's a simple usecase for our Person class, again we will start by using requireJS to 'define' a | |
* new class; however note how we pass the `require` object through to the closure as an argument, this | |
* allows us to retrieve other exported modules / class definitions that have been 'define'd. | |
*/ | |
define(function (require) { | |
"use strict"; | |
// requireJS will ensure that the Person definition is available to use, we can now import | |
// it for use (think of this as your import statement in AS3). | |
var Person = require('Person'); | |
// We can now invoke the constructor function to create a new instance and invoke that instance's | |
// methods. | |
var jonny = new Person("Jonny"); | |
jonny.setAge(29); | |
console.log(jonny.toString()); | |
// We can also access any public static methods and properties attached to the Person function: | |
var sean = Person.create("Sean", 30); | |
console.log(sean.greet()); | |
console.log("Generally speaking, you can retire at " + Person.RETIREMENT_AGE); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment