开篇
编写高质量javascript代码的基本要点
1、最小全局变量
2、函数作用域和声明提前
3、命名规范
命名函数表达式
函数声明:
function 函数名称 (参数:可选){ 函数体 }
函数表达式:
function 函数名称(可选)(参数:可选){ 函数体 }
函数声明只能出现在程序或函数体内
var f = function foo(){
return typeof foo; // foo是在内部作用域内有效
};
// foo在外部用于是不可见的
typeof foo; // "undefined"
f(); // "function"
JScript的Bug 关于ie的
详细内容参看
JScript的内存管理
var f = (function(){
if (true) {
return function g(){};
}
return function g(){};
})();
释放空间是必须的
var f = (function(){
var f, g;
if (true) {
f = function g(){};
}
else {
f = function g(){};
}
// 设置g为null以后它就不会再占内存了
g = null;
return f;
})();
var hasClassNameFun = (function(ement,cname){
// 定义私有变量
var cache = { };
// 使用函数声明
function hasClassName(element, className) {
var _className = '(?:^|\s+)' + className + '(?:\s+|$)';
var re = cache[_className] || (cache[_className] = new RegExp(_className));
return re.test(element.className);
}
// 返回函数
return hasClassName;
})();
其实返回一个函数,而不是函数表达式,在应用上会好一些,代码简单明白。
在应用严格模式的时候,会大量用到命名函数表达式,理解命名函数表达式的语义及其bug显得更加重要了。
// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号
自执行函数
function foo(){ /* code */ }( 1 );
自执行函数格式可以简单写成这样
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
编写插件的五大原则
The Single Responsibility Principle(单一职责SRP)
The Open/Closed Principle(开闭原则OCP)
The Liskov Substitution Principle(里氏替换原则LSP)
The Interface Segregation Principle(接口分离原则ISP)
The Dependency Inversion Principle(依赖反转原则DIP)
1、单一职责的描述如下:
类发生更改的原因应该只有一个
遵守单一职责的好处是可以让我们很容易地来维护这个对象,当一个对象封装了很多职责的话,一旦一个职责需要修改,势必会影响该对象想的其它职责代码。通过解耦可以让每个职责工更加有弹性地变化。
2、开闭原则的描述是:
软件实体(类,模块,方法等等)应当对扩展开放,对修改关闭,即软件实体应当在不修改的前提下扩展。
3、里氏替换原则的描述是:
派生类型必须可以替换它的基类型
在面向对象编程里,继承提供了一个机制让子类和共享基类的代码,这是通过在基类型里封装通用的数据和行为来实现的,然后已经及类型来声明更详细的子类型,为了应用里氏替换原则,继承子类型需要在语义上等价于基类型里的期望行为。
4、接口隔离原则的描述是:
不应该强迫客户依赖于它们不用的方法。
当用户依赖的接口方法即便只被别的用户使用而自己不用,那它也得实现这些接口,换而言之,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。
接口隔离原则ISP和单一职责有点类似,都是用于聚集功能职责的,实际上ISP可以被理解才具有单一职责的程序转化到一个具有公共接口的对象。
5.依赖倒置原则的描述是:
高层模块不应该依赖于低层模块,二者都应该依赖于抽象
抽象不应该依赖于细节,细节应该依赖于抽象
依赖倒置原则的最重要问题就是确保应用程序或框架的主要组件从非重要的底层组件实现细节解耦出来,这将确保程序的最重要的部分不会因为低层次组件的变化修改而受影响。
单例模式概念
在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
单例模式的几种形式
基本单例模式
它就是一个简单的有一些方法和属性的对象组成的,可以称为命名空间
var Singleton={
attr:1,
another_attr:‘value’,
method:function(){...},
another_method:function(){...},
Util:{
util_method1: function(){...},
util_method2:function(){}
},
Ajax:{
ajax_method:function(){...}
},
some_method:function(){...}
};
可以通过一个全局变量的入口访问所有的方法和属性。
Singleton.attr+=1;
Singleton.method();
Singleton.Util.util_method1();
Singleton.Ajax.ajax_method();
Singleton.some_method();
通过创建命名空间或者包来把代码组织到逻辑块中,当你通过使用命名空间把你的代码从全局作用域移动到一个新的单例中的时候,可以避免全局变量被意外覆盖以及全局变量引起其他的bug. 例如一些方法作为全局函数,有可能会被覆盖,尤其是像get一样简单的名字。遇到这种情况就把全部的亦是和函数添加到一个单例中,完全不给别的代码篡改你的代码的机会。
扩展该对象
如果以后要扩展该对象,你可以添加自己的私有成员和方法,然后使用闭包在其内部封装这些变量和函数声明,只暴露你想暴露的public成员和方法,样例代码如下:
var mySingleton=function(){
var privateVariable="something private";
function showPrivate(){
console.log(privateVariable);
}
reutrn{
publicMethod:function(){
showPrivate();
},
publicVar:"the public can see this!"
};
}
var single=mySingleton();
single.publicMethod();
console.log(single.publicVar);
如果我们想做到只有在使用的时候才初始化,为了节约资源的目的,我们可以另外一个构造函数里来初始化这些代码 :
var Singleton=(function(){
function Singleton(args){
var args=args||{};
this.name='SingletonTester';
this.pointX=args.pointX|| 6;
this.pointY=args.pointY||10;
}
var instance;
var _static={
name:'SingletonTester',
getInstance:function(args){
if(instance===undefined){
instance=new Singleton(args);
}
return instance;
}
};
return _static;
})();
var singletons=Singleton.getInstance({pointX:5});
console.log(singletons.pointX); //输出5
单例模式的模块化使用
javascript中’private’是作为保留字,而不是关键字,javascript没有私有化这样的,一是定义变量的时候在前面加上下划线”_”,第一种方法并不是真正的私有,只是一种规范,如果要做到真正的私有,还是要用第二种方法–闭包
(function(){
//此作用域的所有变量,函数依旧可在特定作用域中被访问
})();
先用括号把函数定义括起来,从而得到该函数对象,然后后面的括号是立即运行它,这种形式可以在很多js库中见到,例如jQuery:
js 代码
(
function(window,undefined){
...
window.jQuery=window.$=jQuery;
}
)(window);
上面的代码可以看到是jQuery把window这个全局变量传进匿名函数中,然后把内部定义的jQuery赋值给了window,从而在全局作用域中都可以通过”$”符号来访问匿名函数中的内容。
javascript单体的模块化编写的基本样子是下面的代码:
var Module=(function(){
var my={}, privateVar=8;
function privateFun(){
return ++privateVar;
};
my.publicVar=1;
my.publicFun=function(){
return privateFun();
};
return my;
}());
console.log(Module.publicVar);//1
console.log(Module.publicFun());//9
在匿名函数中返回了一个my变量给Module作为外部访问闭包内容的接口,除闭包内my之外的内容都得到了私有性保护,闭包的数据在Module变量的作用域中保持可以访问。
这种方法解决了javascript私有化的问题,我们可以利用它来定义命名空间,单例,拥有私有化封装的对象等等,然后这种模式有其缺陷,例如:定义私有,公共变量的方法是不同的,当开发过程中我们需要改变某个变量可见性的时候,就不得不在它所有出现过的地方进行修改,并且javascript作为动态编译的语言,我们可以随时给对象添加属性,方法,然而我们在闭包之外定义的方法是无法直接访问私有数据的。
高级的使用方法
上面的方法有一个限制,整个模板必须定义在一个文件中,曾面对一大堆代码工作的人肯定明白将它划分为多个文件的意义,好在,有个巧妙的方法来扩展我们的Modules,首先,在匿名函数的参数中导入Module,然后给它添加属性,然后再导出它,下面举出在另一个文件中对上面提到的Module进行扩展(必须是全局作用域的情况)
js代码
var Module=(function(my){
my.anotherFun=function(){
//do something...
};
return my;
}(Module||{})); //注意,这里还有个“{}”
Module=(function(my){
my.workFun=function(){
//do something...
}
return my;
})(Module||{})
我们重新用var关键字定义Module以保证一致性,并且如果之前Module没有被创建的话,在这里会自动的被创建为空对象”{}”,当这段代码运行结束之后,我们的Module又有了一个新的公共方法Module.anotherFun(), 每个些扩展的Module都有自己独有的私有属性,方法,由于导入的时候可以自动创建,这些包含Module定义的文件之间可以任意顺序加载(如果存在依赖关系 ,必须按次序加载的话,那你按次序就好了)。
跨文件私有属性共享
目前的多文件扩展Module有一严重的限制,那就是现在文件 的Module都只保持自己的私有状态,无法访问其他文件的私有属性,当然,这也可以解决,下面是解决多文件私有属性共享的一个方法。
var MODULE = (function () {
//将所有的私有属性、方法都定义在_private对象中
//每个扩展Module都可以通过my._private来访问
var my = {},
_private = my._private = {},
_seal = function (){
//密封,删除所有私有数据的可访问性
delete my._private;
},
_unseal = function (){
//解封,让私有数据重新可访问
my._private = _private;
};
my.extend = function(otherModules){
//必须通过此方法来添加扩展Module文件
_unseal();
//add other modules
_seal();//异步调用,此处只是示意,真正的代码并非如此
}
return my;
}());