【转】对象创建模式

原帖:http://www.douban.com/note/169097922/

1. 命名空间模式

// unsafe
var MYAPP = {};
// better
if (typeof MYAPP === "undefined") {
    var MYAPP = {};
}
// or shorter
var MYAPP = MYAPP || {};

//一次性创建长命名层级时
//确保每层都存在
MYAPP.namespace = function (ns_string) {
    var parts = ns_string.split('.'),
        parent = MYAPP,
        i;
    // strip redundant leading global
    if (parts[0] === "MYAPP") {
        parts = parts.slice(1);
    }
    for (i = 0; i < parts.length; i += 1) {
        // create a property if it doesn't exist
        if (typeof parent[parts[i] === "undefined") {
            parent[parts[i] = {};
        }
        parent = parent[parts[i];
    }
    return parent;
};

// assign returned value to a local var
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true
// skip initial `MYAPP`MYAPP.namespace('modules.module51');
// long namespace
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');


2. 声明依赖 -----------------------------------------------------------------

var myFunction = function () {
    // dependencies
    var event = YAHOO.util.Event,
        dom = YAHOO.util.Dom;
    // use event and dom variables
    // for the rest of the function...
};

function test2() {
    var modules = MYAPP.modules;
    alert(modules.m1);
    alert(modules.m2);
    alert(modules.m51);
}

优点:
> 声明写在程序上部, 便于查找检查其依赖, 并确保相关模块文件的引入
> 缓存成本地变量会比全局变量运行更快
> 压缩工具会把本地变量替换成更短的名字


3. 私有变量和方法 ----------------------------------------------------------

3.1 在构造方法里用闭包创建私有成员:

function Gadget() {
    // private member
    var name = 'iPod';
    // public function
    this.getName = function () {
        return name;
    };
}

*如果返回的是object或array, 因为传递的是引用, 数据可能被外部修改:
function Gadget() {
    // private member
    var specs = {
        screen_width: 320,
        screen_height: 480,
        color: "white"
    };
    // public function
    this.getSpecs = function () {
        return specs;
    };
}
解决的办法
> 返回object或array等的clone对象
> 用新方法, 比如getDimensions()等直接返回数值对象

3.2 字面量对象实现隐私性:

var myobj; // this will be the object
(function () {
    // private members
    var name = "my, oh my";
    // implement the public part
    // note -- no `var`
    myobj = {
        // privileged method
        getName: function () {
            return name;
        }
    };
}());



var myobj = (function () {
    // private members
    var name = "my, oh my";
    // implement the public part
    return {
        getName: function () {
            return name;
        }
    };
}());

3.3 原型和隐私性:

*使用构造函数创建隐私性会在每次都创建新对象, 从而耗费内存, 可以使用prototype解决

function Gadget() {
    // private member
    var name = 'iPod';
    // public function
    this.getName = function () {
        return name;
    };
}
Gadget.prototype = (function () {
    // private member
    var browser = "Mobile Webkit";
    // public prototype members
    return {
        getBrowser: function () {
            return browser;
        }
    };
}());

3.4 揭秘模块模式

*无法像ECMAScript5中的对象一样可以设置freeze属性使外界无法修改
 但是可以使用 revealing module pattern 暴露某些有必要保护的私有方法

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
    };
}());
 
即使外界修改了对象的indexOf, inArray引用的私有方法indexOf的功能依然正常

myarray.indexOf = null;
myarray.inArray(["a", "b", "z"], "z"); // 2


4. 模块模式 ----------------------------------------------------------

*广泛使用的模式, 提供结构化并帮助应付增长的代码, 在黑盒中实现增删改

//首先定义命名空间
MYAPP.namespace('MYAPP.utilities.array');
//使用一个局部方法返回的对象创建模块, 从而实现私密性和公开的API
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
    };
}());

4.1 揭秘模块模式

*结合使用模块和解密模式, 可以将所有方法设为私有, 并在局部方法的最后决定公开哪些API

MYAPP.utilities.array = (function () {
    // private properties
    var array_string = "[object Array]",
        ops = Object.prototype.toString,
        // private methods
        inArray = function (haystack, needle) {
            for (var i = 0, max = haystack.length; i < max; i += 1) {
                if (haystack[i] === needle) {
                    return i;
                }
            }
            return −1;
        },
        isArray = function (a) {
            return ops.call(a) === array_string;
        };
        // end var
    // revealing public API
    return {
        isArray: isArray,
        indexOf: inArray
    };
}());

4.2 创建构造器的模块

*某些情况下需要对象的构造器函数, 与上面例子的区别是返回一个function而非object

MYAPP.namespace('MYAPP.utilities.Array');
MYAPP.utilities.Array = (function () {
    // dependencies
    var uobj = MYAPP.utilities.object,
        ulang = MYAPP.utilities.lang,
        // private properties and methods...
        Constr;
        // end var
    // optionally one-time init procedures
    // ...
    // public API -- constructor
    Constr = function (o) {
        this.elements = this.toArray(o);
    };
    // public API -- prototype
    Constr.prototype = {
        constructor: MYAPP.utilities.Array,
        version: "2.0",
        toArray: function (obj) {
            for (var i = 0, a = [], len = obj.length; i < len; i += 1) {
                a[i] = obj[i];
            }
            return a;
        }
    };
    // return the constructor to be assigned to the new namespace
    return Constr;
}());

var arr = new MYAPP.utilities.Array(obj);

4.3 将全局变量传入模块

*通常可以向局部方法中传递全局变量, 包括模块自身, 使其变为局部变量以加速

MYAPP.utilities.module = (function (app, global) {
    // references to the global object
    // and to the global app namespace object
    // are now localized
}(MYAPP, this));


5. 沙盒模式 -------------------------------------------------------------------

*此模式着眼于解决命名空间模式的以下缺陷:
 >一个页面中无法有两个以上的相同程序或库共存, 因为会共用全局的变量名
 >需要打印长的命名空间名称, 并在运行时解析之

*沙盒模式使得模块在自己的沙盒中运行, 不受其他模块的影响

//这个Sandbox可以命名成自己的程序名, 比如MYAPP
function Sandbox() {
    var args = Array.prototype.slice.call(arguments), //将参数表转换为数组
        callback = args.pop(), //最后一个参数必须是callback方法
        //模块名称可以用数组或分开传递进来
        modules = (args[0] && typeof args[0] === "string") ? args : args[0],
        i;
    
    //确保方法由new操作符调用
    if (!(this instanceof Sandbox)) {
        return new Sandbox(modules, callback);
    }

    //一些必要的属性
    this.a = 1;
    this.b = 2;

    //将指定的模块附加到'this'对象上
    //没指定模块, 或者使用了'*', 将附加所有模块
    if (!modules || modules === '*') {
        modules = [];
        for (i in Sandbox.modules) {
            if (Sandbox.modules.hasOwnProperty(i)) {
                modules.push(i);
            }
        }
    }
    //初始化所需模块
    for (i = 0; i < modules.length; i += 1) {
        Sandbox.modules[modules[i](this);
    }
    //调用callback, 其中使用的'this'就是附加了所需模块后的独立对象
    callback(this);
}

//一些必要的受保护信息
Sandbox.prototype = {
    name: "My Application",
    version: "1.0",
    getName: function () {
        return this.name;
    }
};

//模块定义
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 () {};
};

//使用实例
Sandbox(function (box) {
    //参数表中的box不写也行, 直接用this
});
Sandbox('*', function () {
    //等同于上一种
});
Sandbox(['ajax', 'event'], function (box) {
    // console.log(box);
});
Sandbox('ajax', 'dom', 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
});

http://www.nowamagic.net/javascript/js_SandBoxPatterm.php
http://www.nowamagic.net/javascript/js_SandBox.php
http://www.planabc.net/lab/yui/sandbox.html

6. 静态成员 ---------------------------------------------------------------

6.1 公开的静态成员

// 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;
};

6.2 私有静态成员

*所谓私有静态成员, 即只能在由构造函数创建的对象中共享的成员

var Gadget = (function () {
    //静态变量/属性
    var counter = 0;
    // 返回的方法成为了新的构造函数
    return function () {
        console.log(counter += 1);
    };
}()); //立即执行的局部方法

var g1 = new Gadget(); // 1
var g2 = new Gadget(); // 2
var g3 = new Gadget(); // 3


7. 对象常量 -------------------------------------------------------------

*一般采用通行的全大写变量的方法表示常量, 如果确实需要不变的值, 可以这样:

var constant = (function () {
    var constants = {},
        ownProp = Object.prototype.hasOwnProperty,
        allowed = {
            string: 1,
            number: 1,
            boolean: 1
        },
        prefix = (Math.random() + "_").slice(2);
    return {
        set: function (name, value) {
            if (this.isDefined(name)) {
                return false;
            }
            if (!ownProp.call(allowed, typeof value)) {
                return false;
            }
            constants[prefix + name] = value;
            return true;
        },
        isDefined: function (name) {
            return ownProp.call(constants, prefix + name);
        },
        get: function (name) {
            if (this.isDefined(name)) {
                return constants[prefix + name];
            }
            return null;
        }
    };
}());

// check if defined
constant.isDefined("maxwidth"); // false
// define
constant.set("maxwidth", 480); // true
// check again
constant.isDefined("maxwidth"); // true
// attempt to redefine
constant.set("maxwidth", 320); // false
// is the value still intact?
constant.get("maxwidth"); // 480


8. 链式模式 -------------------------------------------------------------

*就是jQuery式的调用方法:
 myobj.method1("hello").method2().method3("world").method4();

如果方法没有明确的有意义的返回值时, 就可以返回this, 这样就可以实现链式模式
var obj = {
    value: 1,
    increment: function () {
        this.value += 1;
        return this;
    },
    add: function (v) {
        this.value += v;
        return this;
    },
    shout: function () {
        alert(this.value);
    }
};
// chain method calls
obj.increment().add(3).shout(); // 5
// as opposed to calling them one by one
obj.increment();
obj.add(3);
obj.shout(); // 5


9. method()方法

*使用prototype定义方法可能在写法上带来不便, 结合链式模式, 可以采用method()方法

if (typeof Function.prototype.method !== "function") {
    Function.prototype.method = function (name, implementation) {
        this.prototype[name] = implementation;
        return this;
    };
}

var Person = function (name) {
    this.name = name;
}.method('getName', function () {
    return this.name;
}).method('setName', function (name) {
    this.name = name;
    return this;
});

var a = new Person('Adam');
a.getName(); // 'Adam'
a.setName('Eve').getName(); // 'Eve'

posted @ 2014-06-19 17:18  MissUU  阅读(251)  评论(0编辑  收藏  举报