Skip to content

Instantly share code, notes, and snippets.

@jonnyreeves
Created April 23, 2012 21:38

Revisions

  1. jonnyreeves revised this gist May 14, 2012. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions person.js
    Original file line number Diff line number Diff line change
    @@ -79,6 +79,7 @@ define(function () {
    * 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.
  2. jonnyreeves revised this gist May 14, 2012. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions person.js
    Original file line number Diff line number Diff line change
    @@ -84,6 +84,14 @@ define(function () {
    * 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
  3. jonnyreeves revised this gist Apr 24, 2012. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions usage.js
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,9 @@
    /**
    * Here's a simple usecase for our Person class, again we will use requireJS to 'define' a new class, but
    * this time we will list 'Person' in the Array of required dependencies.
    * 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(['Person'], function () {
    define(function (require) {
    "use strict";

    // requireJS will ensure that the Person definition is available to use, we can now import
  4. jonnyreeves revised this gist Apr 23, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion person.js
    Original file line number Diff line number Diff line change
    @@ -15,7 +15,7 @@ define(function () {
    * 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.
    * 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
  5. jonnyreeves revised this gist Apr 23, 2012. 1 changed file with 3 additions and 4 deletions.
    7 changes: 3 additions & 4 deletions person.js
    Original file line number Diff line number Diff line change
    @@ -63,8 +63,7 @@ define(function () {
    * and are static as a result.
    */
    function formatNameAndAge(person) {
    // Note that we are unable to access any member properties, or methods added to the prototype
    // of the Person object from inside this method.
    // 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!";
    }
    @@ -74,7 +73,7 @@ define(function () {
    };

    /**
    * The prototype is a special type of Object which is used as a the Blueprint for all instances
    * 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:
    @@ -118,7 +117,7 @@ define(function () {
    /**
    * This method access both a member property and a static property.
    */
    canRetire: function() {
    canRetire: function() {
    return this._age >= Person.RETIREMENT_AGE;
    },

  6. jonnyreeves created this gist Apr 23, 2012.
    9 changes: 9 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    <!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>
    138 changes: 138 additions & 0 deletions person.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,138 @@
    /**
    * 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.
    */
    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 we are unable to access any member properties, or methods added to the prototype
    // of 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`
    *
    * 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 = {
    /**
    * 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;
    });
    22 changes: 22 additions & 0 deletions usage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    /**
    * Here's a simple usecase for our Person class, again we will use requireJS to 'define' a new class, but
    * this time we will list 'Person' in the Array of required dependencies.
    */
    define(['Person'], function () {
    "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);
    });