nic jansma | nicj.net | @nicj
What is a JavaScript object?
{}
{}
:number
, string
, boolean
, object
or function
Only null
and undefined
are not objects
Using an object initializer {}
:
// create an empty object
var emptyObject = {};
// create an object with properties
var obj = {
stringProperty: "hello",
integerProperty: 123,
functionProperty: function() { return 0; },
"a property with spaces": false,
subObject: {
booleanProperty: true
}
};
Using a constructor function
(new
keyword):
// create an empty object
var emptyObject = new Object();
// define an object constructor
function Keg(contains, amount) {
this.contains = contains;
this.amount = amount;
}
// create an object
var keg = new Keg("Soda", 100.0);
Using Object.create()
:
// create an empty object
var emptyObject = Object.create(Object.prototype);
// define an object with default properties
var Keg = {
contains: "Unknown",
amount: 0.0
}
// create an object
var keg = Object.create(Keg);
// modify its properties
keg.contains = "Soda";
keg.abv = 100.0;
Let's build a module for a Keg that can be filled with soda. It has two basic properties:
function Keg(contains, amount) {
this.contains = contains;
this.amount = amount;
}
We can add a fill()
function so others can fill it with something tasty:
function Keg(contains, amount) {
this.contains = contains;
this.amount = amount;
this.fill = function(beverage, amountAdded) {
this.contains = beverage;
this.amount = amountAdded;
};
}
Right now, all of the Keg's properties are public. The world has full access to change our data:
var keg = new Keg();
keg.fill("Soda", 100.0);
keg.amount = 9999; // oh no! they accessed our internal data
Let's switch to the Module Pattern, which gives us the ability to have public and private members:
function Keg(_contains, _amount) {
// private members
var contains = _contains;
var amount = _amount;
// public methods
return {
fill: function(beverage, amountAdded) {
contains = beverage;
amount = amountAdded;
}
}
}
// create an instance of a Keg
var keg = new Keg("Soda", 100.0);
keg.fill("Pop", 50.0); // this is the only public member
var amt = keg.amount; // undefined! hidden from us
We can add additional methods to give access to our private variables without changing them:
function Keg(_contains, _amount) {
/* ... private members ... */
return {
fill: function() { ... },
getAmount: function() {
return amount;
},
getContents: function() {
return contains;
}
}
}
var keg = new Keg("Soda", 100.0);
var amt = keg.getAmount(); // 100.0
keg.fill("Pop", 50.0);
amt = keg.getAmount(); // 50.0
You can have private functions as well:
function Keg(_contains, _amount) {
// private members
var contains = _contains;
var amount = _amount;
// private function
function updateAmount(newAmount) {
if (newAmount < 0) { newAmount = 0; }
amount = newAmount;
}
// public methods
return {
fill: function(beverage, amountAdded) {
contains = beverage;
updateAmount(amountAdded);
}
/* ... */
}
}
Completed:
function Keg(_contains, _amount) {
// private members
var contains = _contains;
var amount = _amount;
// private function
function updateAmount(newAmount) {
if (newAmount < 0) { newAmount = 0; }
amount = newAmount;
}
// public methods
return {
fill: function(beverage, amountAdded) {
contains = beverage;
updateAmount(amountAdded);
},
getAmount: function() { return amount; },
getContents: function() { return contains; }
}
}
number
, string
, function
,
etc) that you can assign to all instances of a class using ClassName.prototype.
Keg
using prototype
Instead of each instance having it's own version of the same fill()
function,
there's one global Keg.prototype.fill
:
function Keg(contains, amount) {
// these now need to be public members
this.contains = contains;
this.amount = amount;
}
Keg.prototype.fill = function(beverage, amountAdded) {
// because this doesn't have access to 'vars' in the Keg function
this.contains = beverage;
this.amount = amountAdded;
};
Keg.prototype.getAmount = function() {
return this.amount;
};
Keg.prototype.getContents = function() {
return this.contains;
};
Keg
using prototype
contains
and amount
) need to
change from being defined within the Keg function's closure (var contains = ...
)
to be public properties (this.contains = ...
)Keg.prototype.fill
function wasn't defined within the Keg's
function closure, so it would have no visibility to var
s defined within itjQuery
), you can simplify the syntax a bit
var KegManager = (function() {
var kegs = [];
// exports
return {
addKeg: function(keg) { kegs.push(keg); }
getKegs: function() { return kegs; }
}
})();
var sodaKeg = new Keg("Soda", 100.0);
KegManager.addKeg(sodaKeg);
var kegs = KegManager.getKegs(); // a list of Keg objects
If you want to "import" other global variables or other modules, they can be passed in as IIFE arguments:
var KegManager = (function($) {
var kegs = [];
// do something with $
// exports
return {
addKeg: function(keg) { kegs.push(keg); }
getKegs: function() { return kegs; }
}
})(jQuery);
var sodaKeg = new Keg("Soda", 100.0);
KegManager.addKeg(sodaKeg);
var kegs = KegManager.getKegs(); // a list of Keg objects
function Keg(_contains, _amount) {
var contains = _contains
var amount = _amount;
function updateAmount(newAmount) {
if (newAmount < 0) { newAmount = 0; }
amount = newAmount;
}
function fill(beverage, amountAdded) {
contains = beverage;
updateAmount(amountAdded);
}
function getAmount() { return amount; }
function getContents() { return contains; }
// exports
return {
fill: fill,
getAmount: getAmount,
getContents: getContents
}
}
Pros:
exports
variable is available that you can assign your exports toA file contains a single module:
keg.js
// imports
var KegManager = require("kegmanager");
// constructor we'll export
function Keg(_contains, _amount) {
// ... same as before
// tell the KegManager about this new keg
KegManager.add(this);
}
// some other private vars
var foo = false;
// exports
exports.Keg = Keg;
Same as module definition:
var Keg = require("./keg").Keg;
var keg = new Keg("Soda", 100);
define
Defines a module, its dependencies, and the initialization function that runs once all dependencies are loaded:
define(
"Keg", // module name, optional but suggested
["KegManager"], // list of dependencies
function(KegManager) { // initialization function
// constructor we'll export
function Keg(_contains, _amount) {
// ... same as before
// tell the KegManager about this new keg
KegManager.add(this);
}
// some other private vars
var foo = false;
// exports
return {
Keg: Keg
}
});
require
Load the modules you need
require(
["Keg"],
function(Keg) {
// will only run once Keg (and its dependency, KegManager) is loaded
var keg = new Keg.Keg("Soda", 100);
});
<!DOCTYPE html>
<html>
<head>
<title>My Sample Project</title>
<!-- data-main attribute tells require.js to load
scripts/main.js after require.js loads. -->
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
<body>
<h1>My Sample Project</h1>
</body>
</html>
scripts/main.js
require(['app/module1', 'app/module2']);
Builds all of your modules into a single file (great for deployment)
Install requirejs:
> npm install -g requirejs
Optimize your JavaScript:
> node r.js -o baseUrl=. name=main out=main-built.js
https://github.com/jrburke/almond
Defines a module that works in Node, AMD and browser globals
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('b'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.b);
}
}(this, function (b) {
//use b in some fashion.
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
Goals:
keg.js
module Keg {
// imports
import { KegManager} from 'kegmanager';
// constructor we'll export
export function Keg(_contains, _amount) {
// ... same as before
// tell the KegManager about this new keg
KegManager.add(this);
}
}