一 命名空间模式
1 命名空间模式的代码格式
var MYAPP={ name:"", version:"1.0", init:function(){ } };
2 命名空间的优点:
减少全局变量的数量,
避免命名冲突
3 缺点:
需要输入更多的字符,每个函数和变量前面都需要附加前缀
长嵌套名字需要更多的解析查询时间
var a=""; function addPerson(){ } function someVar=""; //上面有三个全局变量a ,addPerson,someVar //如果使用命名空间模式,可以减缩微一个变量 var MYAPP={ a:"", addPerson:function(){}, someVar:"" }; /* 按照公约,全局变量全部大写, 优点: 避免命名冲突, 避免与第三方的代码冲突 缺点: 需要输入更多的字符,每个函数和变量前面都需要附加前缀 长嵌套名字需要更多的解析查询时间 */
1.1 通用命名空间函数
当使用命名空间的时候,我们通常检查一下该命名空间是否存在,以免给覆盖了(当代码被分割为多个文件或者部分的时候),一般格式如下:
var MYAPP=MYAPP||{};
但是如果嵌套的层数比较多,检查的代码就会很多,下面给出一种通用的命名空间检查的函数
/*在创建一个命名空间的时候,首先检查它是否已经存在*/ var MYAPP=MYAPP||{}; //需要检查5次,这太烦了,下面给出一个通用的函数进行检查 MYAPP.module1.module2.module3.module4.module5={}; /*在创建一个命名空间的时候,首先检查它是否已经存在*/ var MYAPP=MYAPP||{}; MYAPP.namespace=function(ns){ var parts=ns.split("."), parent=MYAPP, i, len; if(parts[0]==="MYAPP"){ parts=parts.slice(1); } for(i=0,len=parts.length;i<len;i++){ if(typeof parent[parts[i]]==="undefined"){ parent[parts[i]]={}; } parent=parent[parts[i]]; } return parent; }; MYAPP.namespace("MYAPP.module1.module2.module3.module4.module5");
二 声明依赖关系
由于我们上面提到了使用命名空间的方法,各个命名空间之间可能会有依赖,
例如我们应用到页面中,可能要依赖DOM模块和Event模块(YAHOO.util.Dom和YAHOO.util.Event),
如果在我们的模块中,始终使用YAHOO.util.Dom和YAHOO.util.Event这两个变量,有以下不足:
1 压缩代码的时候,这两个不能压缩,
2 长嵌套要花更多的时间解析
3 写的时候也麻烦
所以我们来声明一下需要依赖的模块,将全局变量改为局部变量
var myFun=function(){ var event=YAHOO.util.Event, dom=YAHOO.util.Dom; //event..... //dom..... };
声明依赖关系的优点:
1 声明依赖关系,位于函数的头部,因此更清晰的看到我们所依赖的模块
2 解析局部变量比解析全局变量速度要快
3 可以将局部变量进行压缩,减少代码量
三 私有属性和方法
js没有私有,保护等属性和方法,js中所有的属性和方法都是公共的,但是我们可以利用闭包来实现私有属性和私有方法
3.1 私有成员
“闭包”(closure):能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取该函数的局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
/* *能够读取到其他函数(addPerson)内部的变量(name)的函数(getName) */ function addPerson(){ var name1="Jhon"; this.getName=function(){ return name1; }; } var onePerson=new addPerson(); console.log(onePerson.name1);//undefined console.log(onePerson.getName())//"Jhon" console.log(name1);//ReferenceError: name1 is not defined
3.2 特权方法
特权方法:可以访问到私有成员的公共方法,上面demo中的getName就是特权方法
3.3 私有性失效
当特权方法返回的是一个对象或者数组的时候,其实返回的是对象或者数组的引用(地址),这样的话就破坏了该对象或者数组的私有性
解决方案:1 最低授权原则,即不给予超过需要的特权,例如下面的例子中,我们只返回colors的第几个元素,colors[2],这样的话就不会改变colors,但是这种方法局限性太大
2 使用一个深度拷贝的函数,将数组或者对象里面的值进行复制,给一个新的变量,返回这个新的变量即可,会在下一章详细介绍
function addPerson(){ var colors=["red","black","blue","green"]; this.getColors=function(){ return colors;//这里传递是colors数组的引用,即地址 }; } var onePerson=new addPerson(); var newColors=onePerson.getColors();//这里newColors的值是私有属性colors的地址 newColors[2]="white"; console.log(onePerson.getColors());// ["red", "black", "white", "green"]
3.4 对象字面量以及私有性
上面我们提到了函数的私有属性和特权方法,这里我们看一下对象字面量的私有属性和特权方法------模块模式
var myobj=(function(){ var name="Jhon"; return { getName:function(){ return name; } }; }()); console.log(myobj.getName());//Jhon
3.5 原型和私有性
当将私有成员与构造函数一起使用的时候,其中一个缺点就是每次创建实例的时候,这些私有成员都会被创建,为了避免复制工作节省内存,我们将公用的属性和方法添加到构造函数的prototype属性中
优点:
1 多个实例共享公见的数据,
2 隐藏的私有成员
/* *其中name是每个实例的私有变量 *version虽然也是每个实例的私有变量,但是又是它们公用的 *所以可以放到原型里 *但是name不可以,因为每个实例的name值都不一样 */ function addPerson(name){ this.getName=function(){ return name; }, this.setName=function(newName){ name=newName; } } addPerson.prototype=(function(){ var version="1.0"; return { getVersion:function(){ return version; }, setVersion:function(newVersion){ version=newVersion; } }; }()); var p1=new addPerson("Jhon"); var p2=new addPerson("Amy"); console.log(p1.name);//undefined name是实例的私有成员,外面访问不到 console.log(p1.getName());//Jhon 只能通过特权方法来访问 console.log(p2.name);//undefined name是实例的私有成员,外面访问不到 console.log(p2.getName());//Amy 只能通过特权方法来访问 console.log(p1.version);//undefined version也是实例的私有成员,但是是所有实例公用的 console.log(p1.getVersion());//1.0 也只能通过特权方法来访问 /* 这里只要一个实例改变了所有实例公用的私有成员version 所有的成员的这个值都会改变 */ console.log(p1.setVersion("1.1")); console.log(p1.getVersion());//1.1 console.log(p2.getVersion());//1.1 /* 但是每个实例的私有成员发生改变的话,不会影响到其他成员 */ console.log(p1.setName("Green")); console.log(p1.getName());//Green console.log(p2.getName());//Amy
3.6 将私有方法揭示为公共方法-----揭示模式
将私有方法暴露为公共方法,当把多个功能放置在一个函数时,最好使用该方法,这样如果其中一个功能发生意外,也不会影响其他功能继续使用该私有方法
例如下面的例子,将查看元素在一个数组中的位置以及查看一个元素是否位于一个数组两个功能都放置在indexOf函数,使用下面的揭示模式,如果其中一个功能被修改,也不会影响另外一个的继续使用
/* 最重要的是,如果其中一个暴露出来的方法(indexOf)出了问题,也不会影响使用同一个私有方法(indexOf)的公共方法(inArray)的使用 */ var myarray; (function(){ var str="[object Array]", toString=Object.prototype.toString; //验证是否为数组 function isArray(a){ return toString.call(a)===str; } //找到该元素在数组中的位置 function indexOf(hayStack,needle){ var i=0, max=hayStack.length; for(;i<max;i++){ if(hayStack[i]==needle){ return i; } } return -1; } //揭示模式,将函数内部的私有方法(isArray和indexOf)暴露成为公共方法 myarray={ isArray:isArray, indexOf:indexOf, inArray:indexOf }; })(); console.log(myarray.isArray([1,2]));//true console.log(myarray.indexOf([1,2],3));//-1 console.log(myarray.inArray([1,2],2));//1 myarray.indexOf=null; console.log(myarray.inArray([1,2],2));//1 console.log(myarray.indexOf([1,2],2));//报错
四 模块模式
模块模式提供了一种创建自包含非耦合代码片段的有利工具,可以将它视为黑盒功能,并且可以根据您所编写软件的需求进行添加/替换或者删除这些模块
模块模式是以下模式的组合:
命名空间
即时函数
私有和特权成员
声明依赖
4.1 揭示模块模式
/*命名空间*/ var MYAPP=MYAPP||{}; MYAPP.namespace=function(ns){ var parts=ns.split("."), parent=MYAPP, i, len; if(parts[0]==="MYAPP"){ parts=parts.slice(1); } for(i=0,len=parts.length;i<len;i++){ if(typeof parent[parts[i]]==="undefined"){ parent[parts[i]]={}; } parent=parent[parts[i]]; } return parent; }; MYAPP.namespace("MYAPP.utilities.array"); //(function(){})()即时函数 MYAPP.utilities.array=(function(){ //声明依赖,这个demo没有使用到,但是模块模式可以包含该模式 var uobj=MYAPP.utilities.object, ulang=MYAPP.utilities.lang, str="[object Array]",//私有属性 toString=Object.prototype.toString;//私有属性 //私有方法 function isArray(a){ return toString.call(a)===str; } //私有方法 function indexOf(hayStack,needle){ var i=0, max=hayStack.length; for(;i<max;i++){ if(hayStack[i]==needle){ return i; } } return -1; } //特权成员,公有的API,将私有方法揭示为公共方法---揭示模式 return { isArray:isArray, indexOf:indexOf, inArray:indexOf }; })(); console.log(MYAPP.utilities.array.isArray([1,2]));//true console.log(MYAPP.utilities.array.indexOf([1,2],3));//-1 console.log(MYAPP.utilities.array.inArray([1,2],2));//1
4.2 创建构造函数的模式:与4.1的揭示模式的区别,就是返回值不同,揭示模式返回的是一个对象,创建构造函数模式返回的是一个函数
/*命名空间*/ var MYAPP=MYAPP||{}; MYAPP.namespace=function(ns){ var parts=ns.split("."), parent=MYAPP, i, len; if(parts[0]==="MYAPP"){ parts=parts.slice(1); } for(i=0,len=parts.length;i<len;i++){ if(typeof parent[parts[i]]==="undefined"){ parent[parts[i]]={}; } parent=parent[parts[i]]; } return parent; }; MYAPP.namespace("MYAPP.utilities.array"); //(function(){})();即时函数 MYAPP.utilities.array=(function(){ //声明依赖,这个demo没有使用到,但是模块模式可以包含该模式 var uobj=MYAPP.utilities.object, ulang=MYAPP.utilities.lang, Constr; Constr=function(o){ this.elements=this.toArray(o); }; Constr.prototype={ constructor:MYAPP.utilities.array, version:"1.0", toArray:function(obj){ for(var i=0,a=[],len=obj.length;i<len;i+=1){ a[i]=obj[i]; } return a; } }; return Constr; })();//将全局变量导入到模块中 var obj=[1,2,34]; var a=new MYAPP.utilities.array(obj); a.version;//"1.0"
五 沙箱模式
沙箱模式可以解决命名空间模式的几个缺点:
1 在命名空间模式中是没有办法使用同一个库的两个版本在一个页面使用的,因为我们都要使用全局变量MYAPP
2 对于以.分割的名称来说,需要更长的解析时间,例如MYAPP.utilities.array
沙箱是提供了一个可用于模块运行的环境,且不会对其他模块和其他的沙箱造成任何影响
六 静态成员
七 对象常量
八 链模式
九 method方法