在javascript中,咱们知道可使用对象字面量或者构造函数建立对象,可是如何优雅地建立一个对象你却不必定了解。javascript
前人在踩过无数坑又填过无数坑以后,给咱们总结了不一样场景下的几种对象建立模式:java
有这样一种场景,假如你正在写一个插件,这个插件内部会用到不少的全局变量,这时候你要怎么保证你的变量不会与其余插件的变量产生命名冲突呢?咱们知道,在javascript中并无内置命名空间,后面的变量会覆盖掉前面的变量,你要如何避免这种问题的发生?ajax
可能你已经想到,只要足够独特的命名就能够了吧,好比lynneShowVar,这样确实是能够的,可是若是变量不少,难道要为每一个都起一个很独特的命名?是否是很累?并且维护也是很麻烦的。设计模式
命名模式提出,为应用程序建立一个全局对象,好比MYAPP,而后将全部的变量和函数挂到这个全局对象(MYAPP)的属性上。数组
//建立全局对象 var MYAPP = {}; //构造函数 MYAPP.parent = function () {}; MYAPP.child = function () {}; //一个变量 MYAPP.some_var = 1; //一个对象容器 MYAPP.modules = {} //嵌套对象 MYAPP.modules.module1 = {}; MYAPP.modules.module2 = {};
命名约定:一般以所有大写的方式来命名这个全局对象。闭包
优势:app
缺点dom
那么,有什么办法能够避免这些缺陷么?声明代码依赖关系应该是一个不错的主意了。模块化
声明依赖关系函数
var myFunction = function () { //依赖 var event = MYAPP.util.event, dom = MYAPP.util.dom; //... }
优势:
通用命名空间
随着程序的复杂度的增长,如何保证全局对象上新添属性不会覆盖原有的属性呢。这就须要每次在添加新属性以前先检查它是否已经存在了。
var MYAPP = MYAPP || {}; MYAPP.name = function (ns_string) { var parts = ns_string.split('.'), parent = MYAPP, i; //剥离最前面的冗余全局变量 if (parts[0] === 'MYAPP') { parts = parts.slice(1); } for(i = 0; i < parts.length; i++) { //若是不存在就建立一个属性 if (typeof parent[parts[i]] === 'undefined') { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; }
命名模式虽然好用,但仍是有个问题,那就是任何部分的代码均可以修改全局实例,以及里面的属性,怎么避免?咱们知道es5并无类的概念,若是要实现私有成员,可使用闭包来 模拟这种特性。
其实模块模式是多种模式的组合
MYAPP.ntilities.array = (function () { //依赖 var uobj = MYAPP.ntilities.object, ulang = MYAPP.ntilities.lang; //私有属性 var array_string = '[object array]', ops = Object.prototype.toString; //私有方法 //... //其它 //共有API return { inArray: function (needle, haystack) { ... }, isArray: function (str) { ... } //更多... } })()
将全局变量导入到模块中
能够将参数传递到包装了模块的即时函数中,有助于加速即时函数中全局符号的解析。
MYAPP.ntilities.array = (function (app, global) { }(MYAPP, this);
假设须要在同一个页面运行同一个库的两个版本,很遗憾,前面两种建立模式都是不支持的。这时候就须要模块化。
在命名模式中,有一个全局对象 ,在沙箱模式中,有一个全局构造函数,咱们这里命名为Sandbox()。这个构造函数能够接收一个或多个参数,这些参数指定对象实例须要的模块名,以及一个回调函数。如:
//模块名可使用数组的形式传参 Sandbox(['ajax', 'dom'], function () { //这里是回调函数 }); //模块名也可使用单个参数的形式传参,参数之间使用,号隔开 Sandbox('ajax', 'dom', function () { //这里是回调函数 }); //不指定模块名称或指定'*',表示须要依赖全部模块 Sandbox(function () { //这里是回调函数 }); Sandbox('*', function () { //这里是回调函数 });
在实现实际的构造函数以前,先为Sandbox添加一个名为modules的静态属性,这须要理解的是Sandbox同时也是一个对象。
Sandbox.modules = {}
把须要的模块添加到Sandbox.modules对象中。假设咱们一共须要dom、ajax、event模块
Sandbox.modules.dom = function(box) { box.getElement = function () {}; }; Sandbox.modules.ajax = function(box) { box.getResponse = function () {}; }; Sandbox.modules.event = function(box) { box.attachEvent = function () {}; };
接下来实现构造函数
function Sandbox = function () { //将参数转换成一个数组 var args = Array.prototype.slice.call(arguments), //最后一个参数是回调函数 callback = args.pop(), //args[0]若是是String类型,说明模块做为单独参数传递,不然模块做为数组形式传递 modules = (args[0] && typeof args[0] === 'String') ? args : arg[0], i; //确保该函数做为构造函数调用 if(!(this instanceof Sandbox)) { return new Sandbox (modules, callback); } //须要向this添加的属性,也可能没有,这里看实际项目需求 this.a = 1; this.b = 2; //如今向该核心'this'对象添加模块 //不指定模块名称或指定'*',都表示制定全部模块 if (!modules || modules === '*') { modules = []; for (i in Sandbox.modules) { modules.push[i]; } } //初始化所需的模块 for(i = 0; i < modules.lenght; i++) { Sandbox.modules[modules[i]](this) } //依赖模块已所有初始化,能够执行回调函数 callback(); } //获取你还须要添加一些原型属性,看需求 Sanndbox.prototype = { name: 'MY Application', version: '1.0', getName: function () { return this.name; } };
优势
链式模式可使您可以一个接一个地调用对象的方法,而无需将前一个操做返回的值付给变量,且无需分割成多行。如:
myobj.method1('hello').method2().method3('str');
这个很好理解,咱们直接来看一看代码
var obj = { al: 1, add: function (v) { this.val += v; return this; }, set: function (v) { this.val = v; return this; }, shout: function (v) { console.log(this.val); } }; //链方法调用 obj.add(1).set(2).shout();
能够看到,obj的每一个方法都返回this对象,这就是实现链式的原理。
优势
缺点
每种设计模式都各有优缺点,具体使用哪一种模式还得看项目需求,你们一块儿学习吧。