当系统大了、程序复杂了、写的人多了,名字的问题就是个大问题。目前来说,解决名字问题的最好办法就是命名空间。
使用命名空间,可以避免变量或对象名称产生的冲突,同时,也有助于组织代码,有更强的可维护性和可读性。
JavaScript不提供原生的命名空间支持,但我们可以利用JavaScript的一些语言特性,实现类似的效果。
1.闭包
简单的说,闭包就是,一个函数可以使用函数之外定义的变量,那么这个函数就是闭包。
var msg = "你好,朋友";
function sayHello() {
alert(msg);
}
sayHello();
这就是一个最简单的闭包实例。函数sayHello调用了函数外部定义的变量msg,所以函数sayHello就是闭包。
function extfunc() {
var n = 1;
function internal_func(){
alert(n++);
}
return internal_func();
}
extfunc();
这个例子稍微复杂一些,其内部函数(internal_func )是一个闭包,因为它使用外部函数的变量n的值。extfunc()的最后一步调用了internal_func(),增加了变量n的值,并打印它。
通过闭包可以让函数内部变量的值始终保持在内存中。比如下面的例子,函数fn()无论执行多少次,其内部变量n的值都是1,因为函数每次执行的时候都会重新分配内存,并初始化其内部变量,而且执行完毕,会释放函数内部变量的内存。
function fn() {
var n = 1;
alert(n++);
}
fn();
fn();
fn();
现在,我们用闭包做一些处理。
function fn() {
var n = 1;
function in_fn(){
alert(n++);
}
return in_fn;
}
var f = fn();
f();
f();
f();
我们发现,每次执行,函数fn()内部的变量n的值都会加一,说明变量n的值始终保存在内存中了。
这个例子中,函数fn()最后返回了闭包函数in_fn(),也就是说var f=fn()执行后,f其实是函数in_fn()的引用,此时,函数实例fn()在f引用销毁之前不会销毁,所以,其内部变量n可以保存在内存中。
2.自调用匿名函数
自调用匿名函数,或者说,立即执行函数表达式,英文缩写IIFE,是一个立即执行的匿名函数表达式,我们把这种代码结构称为“自调用匿名函数”。
(function() {
alert("我是一个自调用匿名函数");
})();
该代码定义了一个匿名函数,而且会立即执行。其实,这段代码等同于下面的代码,但这种表达方式更加简单直接。
function fn(){
alert("我是一个自调用匿名函数");
};
fn();
下面的方式也是合法的。
(function(){
alert("我是一个自调用匿名函数");
}());
当然,自调用匿名函数是可以带参数的。
(function(window){
window.msg = "我被自调用匿名函数初始化";
})(window);
alert(window.msg);
这里,自调用匿名函数执行的时候,将window对象传给了它,它给window对象设置了一个属性msg,并初始化。
很明显的是,对于自调用匿名函数,我们不能调用两次,因此,它非常适合做一次性或者初始化性质的工作。
要特别注意的是,自调用匿名函数用在赋值函数表达式中的情况。先看下面的例子。
var fn = function(){
return "这是函数执行结果";
}
alert(fn);
其fn是一个赋值函数,我们打印出来的是函数的代码:
var fn = function(){
return "这是函数执行结果";
}();
alert(fn);
这时候fn就不是一个函数的地址了,而是匿名函数执行后的结果:
3.命名空间
因此,我们可以通过函数作用域来实现JavaScrpt的命名空间 ,也就是利用自调用匿名函数,创建一个特殊的函数作用域,将该作用域中的代码和外界隔离开,从而实现命名空间的概念。
比如JQuery框架的命名空间:
(function(window,undefined){
var jQuery = (function(){
var jQuery = function(){
alert("创建JQuery对象");
};
return jQuery;
})();
window.jQuery = window.$ = jQuery;
})(window);
执行$(),或者jQuery(),显示:
该代码通过自调用匿名函数创建了一个和外界隔离的对象jQuery,并通过全局变量$(window.$)和jQuery(window.jQuery)对外公开,其jQuery对象起到了命名空间的作用。同时利用闭包的特性,让命名空间对象的值始终保持在内存中。
4.模块化
虽然我们通过JavaScript的闭包、自调用匿名函数等特性,创建了命名空间,或者说我们创建了命名空间模式,可以很好地解决名字问题。但随着系统的增大,需要团队协作完成一个项目,此时,多文件就是必须的了。多文件开发环境下,模块化编程就是我们必须要掌握的一项技术。
我们可以通过扩展模式,来实现多人、多文件开发。比如,A定义了一个命名空间Module_A,为了表述的统一,这里我们称为模块Module_A。
(function(){
var Module = {};
Module.moduleProperty = "模块Module_A的原始属性moduleProperty";
Module.moduleMethod = function () {
alert("模块Module_A的原始方法moduleMethod");
};
window.Module_A = Module;
})();
该代码写在文件file_a.js中。现在,B要负责Module_A模块其他方法的实现,他可以在文件file_b.js中,通过扩展模式,给Module_A添加方法、或者属性。
(function(Module){
Module.anotherMethod = function () {
alert("模块Module_A的扩展方法anotherMethod");
};
})(window.Module_A);
下面,我们用一个测试文件测试一下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="file_a.js"></script>
<script src="file_b.js"></script>
<title>Document</title>
</head>
<body>
<script type="text/javascript">
Module_A.moduleMethod();
Module_A.anotherMethod();
</script>
</body>
</html>
很好,运行顺利:
但是,如果我们对file_a.js和file_b.js两个文件的引用顺序错了的话,就会产生错误。
错误的原因是我们在扩展Module_A时,Module_A还没有定义,也就是还不存在。这个问题可以通过宽模式扩展解决。
file_a.js文件代码的修改:
(function(Module){
Module.moduleProperty = "模块Module_A的原始属性moduleProperty";
Module.moduleMethod = function () {
alert("模块Module_A的原始方法moduleMethod");
};
window.Module_A = Module;
})(window.Module_A || {});
file_b.js文件代码的修改:
(function(Module){
Module.anotherMethod = function () {
alert("模块Module_A的扩展方法anotherMethod");
};
window.Module_A = Module;
})(window.Module_A || {});
也就是说,我们在给自调用匿名函数传递参数的时候,就判断全局变量是否存在,如果不存在则直接创建一个Object对象给它。这样,我们就可以可以创建灵活多变的模块,可以将他们无顺序加载,以宽松的方式扩展。
本文出自 “老惠” 博客,转载请与作者联系!
原文地址:http://19680411.blog.51cto.com/11767410/1952294