4.Function Pattern
// BEFORE: 5 globals // Warning: antipattern // constructors function Parent() {} function Child() {} // a variable var some_var = 1; // some objects var module1 = {}; module1.data = {a: 1, b: 2}; var module2 = {};You can refactor this type of code by creating a single global object for your application, called, for example, MYAPP, and change all your functions and variables to become properties of your global object:
// AFTER: 1 global // global object var MYAPP = {}; // constructors MYAPP.Parent = function () {}; MYAPP.Child = function () {}; // a variable MYAPP.some_var = 1; // an object container MYAPP.modules = {}; // nested objects MYAPP.modules.module1 = {}; MYAPP.modules.module1.data = {a: 1, b: 2}; MYAPP.modules.module2 = {};For the name of the global namespace object, you can pick, for example, the name of your application or library, your domain name, or your company name. Often developers use the convention of making the global variable ALL CAPS, so it stands out to the readers of the code. (But keep in mind that all caps are also often used for constants.)
var myFunction = function () { // dependencies var event = YAHOO.util.Event, dom = YAHOO.util.Dom; // use event and dom variables // for the rest of the function... };This is an extremely simple pattern, but at the same time it has numerous benefits:
var myobj = { myprop: 1, getProp: function () { return this.myprop; } }; console.log(myobj.myprop); // `myprop` is publicly accessible console.log(myobj.getProp()); // getProp() is public tooThe same is true when you use constructor functions to create objects; all members are still public:
function Gadget() { this.name = 'iPod'; this.stretch = function () { return 'iPad'; }; } var toy = new Gadget(); console.log(toy.name); // `name` is public console.log(toy.stretch()); // stretch() is publicAlthough the language doesn’t have special syntax for private members, you can implement them using a closure. Your constructor functions create a closure and any variables that are part of the closure scope are not exposed outside the constructor. However, these private variables are available to the public methods: methods defined inside the constructor and exposed as part of the returned objects. Let’s see an example where name is a private member, not accessible outside the constructor:
function Gadget() { // private member var name = 'iPod'; // public function this.getName = function () { return name; }; } var toy = new Gadget(); // `name` is undefined, it's private console.log(toy.name); // undefined // public method has access to `name` console.log(toy.getName()); // "iPod"As you can see, it’s easy to achieve privacy in JavaScript. All you need to do is wrap the data you want to keep private in a function and make sure it’s local to the function, which means not making it available outside the function.
function Gadget() { // private member var specs = { screen_width: 320, screen_height: 480, color: "white" }; // public function this.getSpecs = function () { return specs; }; }
var myarray; (function () { var astr = "[object Array]", toString = Object.prototype.toString; function isArray(a) { return toString.call(a) === astr; } function indexOf(haystack, needle) { var i = 0, max = haystack.length; for (; i < max; i += 1) { if (haystack[i] === needle) { return i; } } return −1; } myarray = { isArray: isArray, indexOf: indexOf, inArray: indexOf }; }());
myarray.isArray([1,2]); // true myarray.isArray({0: 1}); // false myarray.indexOf(["a", "b", "z"], "z"); // 2 myarray.inArray(["a", "b", "z"], "z"); // 2
• Namespaces • Immediate functions • Private and privileged members • Declaring dependenciesThe first step is setting up a namespace. Let’s use the namespace() function from earlier in this chapter and start an example utility module that provides useful array methods:
MYAPP.namespace('MYAPP.utilities.array');The next step is defining the module. The pattern uses an immediate function that will provide private scope if privacy is needed. The immediate function returns an object—the actual module with its public interface, which will be available to the consumers of the module:
MYAPP.utilities.array = (function () { return { // todo... }; }());Next, let’s add some methods to the public interface:
MYAPP.utilities.array = (function () { return { inArray: function (needle, haystack) { // ... }, isArray: function (a) { // ... } }; }());Using the private scope provided by the immediate function, you can declare some private properties and methods as needed. Right at the top of the immediate function will also be the place to declare any dependencies your module might have. Following the variable declarations, you can optionally place any one-off initialization code that helps set up the module. The final result is an object returned by the immediate function that contains the public API of your module:
MYAPP.namespace('MYAPP.utilities.array'); MYAPP.utilities.array = (function () { // dependencies var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, // private properties array_string = "[object Array]", ops = Object.prototype.toString; // private methods // ... // end var // optionally one-time init procedures // ... // public API return { inArray: function (needle, haystack) { for (var i = 0, max = haystack.length; i < max; i += 1) { if (haystack[i] === needle) { return true; } } }, isArray: function (a) { return ops.call(a) === array_string; } // ... more methods and properties }; }());
new Sandbox(function (box) { // your code here... });
Sandbox(['ajax', 'event'], function (box) { // console.log(box); });This example is similar to the preceding one, but this time module names are passed as individual arguments:
Sandbox('ajax', 'dom', function (box) { // console.log(box); });And how about using a wildcard “*” argument to mean “use all available modules”? For convenience, let’s also say that when no modules are passed, the sandbox will assume '*'. So two ways to use all available modules will be like:
Sandbox('*', function (box) { // console.log(box); }); Sandbox(function (box) { // console.log(box); });
Sandbox('dom', 'event', function (box) { // work with dom and event Sandbox('ajax', function (box) { // another sandboxed "box" object // this "box" is not the same as // the "box" outside this function //... // done with Ajax }); // no trace of Ajax module here });
Sandbox.modules = {}; Sandbox.modules.dom = function (box) { box.getElement = function () {}; box.getStyle = function () {}; box.foo = "bar"; }; Sandbox.modules.event = function (box) { // access to the Sandbox prototype if needed: // box.constructor.prototype.m = "mmm"; box.attachEvent = function () {}; box.dettachEvent = function () {}; }; Sandbox.modules.ajax = function (box) { box.makeRequest = function () {}; box.getResponse = function () {}; };In this example we added modules dom, event, and ajax, which are common pieces of functionality in every library or complex web application.The functions that implement each module accept the current instance box as a parameter and may add additional properties and methods to that instance.
function Sandbox() { // turning arguments into an array var args = Array.prototype.slice.call(arguments), // the last argument is the callback callback = args.pop(), // modules can be passed as an array or as individual parameters modules = (args[0] && typeof args[0] === "string") ? args : args[0], i; // make sure the function is called // as a constructor if (!(this instanceof Sandbox)) { return new Sandbox(modules, callback); } // add properties to `this` as needed: this.a = 1; this.b = 2; // now add modules to the core `this` object // no modules or "*" both mean "use all modules" if (!modules || modules === '*') { modules = []; for (i in Sandbox.modules) { if (Sandbox.modules.hasOwnProperty(i)) { modules.push(i); } } } // initialize the required modules for (i = 0; i < modules.length; i += 1) { Sandbox.modules[modules[i]](this); } // call the callback callback(this); } // any prototype properties as needed Sandbox.prototype = { name: "My Application", version: "1.0", getName: function () { return this.name; } };
// constructor var Gadget = function () {}; // a static method Gadget.isShiny = function () { return "you bet!"; // 没错 }; // a normal method added to the prototype Gadget.prototype.setPrice = function (price) { this.price = price; }; /* Now let’s call these methods. The static isShiny() is invoked directly on the constructor, whereas the regular method needs an instance: */ // calling a static method Gadget.isShiny(); // "you bet" // creating an instance and calling a method var iphone = new Gadget(); iphone.setPrice(500); typeof Gadget.setPrice; // "undefined" typeof iphone.isShiny; // "undefined"Attempting to call an instance method statically won’t work; same for calling a static method using the instance iphone object
Gadget.prototype.isShiny = Gadget.isShiny; window.alert(iphone.isShiny()); // "you bet"In such cases you need to be careful if you use this inside the static method. When you do Gadget.isShiny() then this inside isShiny() will refer to the Gadget constructor function. If you do iphone.isShiny() then this will point to iphone(!).
// constructor var Gadget = function (price) { this.price = price; }; // a static method Gadget.isShiny = function () { // this always works var msg = "you bet"; if (this instanceof Gadget) { // this only works if called non-statically msg += ", it costs $" + this.price + '!'; } return msg; }; // a normal method added to the prototype Gadget.prototype.isShiny = function () { return Gadget.isShiny.call(this); }; // Testing a static method call: Gadget.isShiny(); // "you bet" //Testing an instance, nonstatic call: var a = new Gadget('499.99'); a.isShiny(); // "you bet, it costs $499.99!"
var Gadget = (function () { // static variable/property var counter = 0; // returning the new implementation // of the constructor return function () { console.log(counter += 1); }; }()); // execute immediately
// constructor var Gadget = (function () { // static variable/property var counter = 0, NewGadget; // this will become the // new constructor implementation NewGadget = function () { counter += 1; }; // a privileged method NewGadget.prototype.getLastId = function () { return counter; }; // overwrite the constructor return NewGadget; }()); // execute immediately // Testing the new implementation: var iphone = new Gadget(); iphone.getLastId(); // 1 var ipod = new Gadget(); ipod.getLastId(); // 2 var ipad = new Gadget(); ipad.getLastId(); // 3