以前我基于谷歌地图封装过一个很大型的船舶监控的JS插件。当时由于入行时间不够,加之经验不足,导致js写得不好,全局变量到处都是。到后面居然前面的会覆盖前面的全局变量。今天就来研究下使用太多全局变量导致的问题。
一、全局变量是如何产生的
我们知道全局变量都是挂载在window对象上面的。它的产生方式有很多种,下面就列出来。
1、人为定义的
人为定义很简单,你这样写的代码就定义了一个全局变量person:
|
1
2
3
|
<script type="text/javascript">
var person = \'刘备\';
</script>
|
2、漏写var
很多人以为在函数内部写的变量只是函数内有效。实际上javascript并不是这样。如果在函数内定义变量的时候没写var,那么它依然是全局变量。
|
1
2
3
4
5
6
7
|
<script type="text/javascript">
function sayName() {
name = \'刘备\';
}
sayName();
alert(name); //弹出刘备
</script>
|
注意到,调用了一次name之后,就创建了name的全局变量,到处都可以访问。
只有在严格模式下,浏览器才会提示错误:name未定义;
有一个消防法,使用JSLint或JSHint扫描代码能够理清楚这些无意创建的全局变量。
二、全局变量太多会带来哪些问题
当然,少量的全局变量不会造成太大影响。但是如果js代码庞大了之后,到处都是全局变量会造成什么问题呢?
1、命名冲突
全局变量太多时,可能我们无意之中声明的一个全局变量,其实之前已经存在。这是可能就会造成后面的值覆盖掉前面的值。
2、代码脆弱
比如,在函数内部依赖一个全局变量,一旦这个全局变量被删除或被修改,都会影响到这个函数的执行是否正确。
|
1
2
3
4
5
6
7
8
|
var name = \'刘备\';
function sayName() {
alert(name)
}//改为参数传入好于依赖全局对象function sayName2(name) {
alert(name);
} |
如,对于上面两个函数的写法,依赖参数存入要好于依赖全局变量。
3、难以测试
依赖全局变量之后,整个框架要依赖于全局变量才能运行。所以要想进行局部测试或单元测试就必须要创建好完整的全局环境。
三、减少全局变量的方法
减少全局变量的方法有好多,下面就推荐几种。
1、单全局变量
单全局变量的意思是,只创建一个全局变量。然后其他的全局变量作为属性挂载到这个全局变量上面。
比如:
-
jQuery定义了两个全局对象:$和jQuery。
-
YUI定义了一个唯一的全局对象YUI全局对象。
-
Dojo定义了一个dojo的全局对象。
比如,我想需要实现如下逻辑:
|
1
2
3
4
5
6
7
8
9
10
|
function Person(id, name)
{ this.id = id;
this.name = name;
}Person.prototype.sayAge = function (age) {
alert(age);
}var p1 = new Person(1, \'刘备\');
var p2 = new Person(2, \'关羽\');
|
以上代码逻辑创建了1个全局函数,2个全局对象:Person、p1、p2。
实际上,完全可以只创建一个对象就能完成同样的功能。
|
1
2
3
4
5
6
7
8
9
10
11
|
var myjs = {};
myjs.Person = function(id, name)
{ this.id = id;
this.name = name;
}myjs.Person.prototype.sayAge = function (age) {
alert(age);
}myjs.p1 = new myjs.Person(1, \'刘备\');
myjs.p2 = new myjs.Person(2, \'关羽\');
|
以上代码,将所有的全局函数与全局对象都挂载到myjs这个全局对象上了。
实际上,你还可以挂载更多的变量、对象、函数等等。
2、命名空间
基于单全局变量,当变量太多时,只有一级也可能会造成同名覆盖的问题。这时候,我们就命名空间的方式来创建多级别的变量挂载。
命名空间这个概念在javascript中是不存在的,实际上是巧妙利用javascript的代码特性实现的功能划分。
雅虎的YUI就是依照命名空间的思路来管理它的代码。
比如Y.DOM下的所有方法都是与DOM操作相关的,Y.Event下的所有方法都是和事件相关的。
在javascript中,我们可以轻松地使用对象来创建自己的命名空间。
如,假设基于地图封装了一个船舶监控系统:
|
1
2
3
|
var myjs = {};
myjs.map = {}; //地图操作相关的
myjs.ship = {}; //船舶操作相关的
|
对于这种命名空间的方式,要特别注意的是,使用之前,要判断命名空间是否存在。
例如:
|
1
2
3
|
MyGlobal.Person.Say.SayHello = function () {
alert(\'hello\');
} |
使用MyGlobal.Person.Say这个命名空间,当MyGlobal下不存在Person这个对象,就会报错。这个可以为MyGlobal写一个函数,当没有这个命名空间时逐级创建,如果这个命名空间已存在则直接返回。这样就可以省略掉使用命名空间要判断的问题了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var MyGlobal = {
namespace: function (ns) {
var parts = ns.split(\'.\');
var object = this;
var i;
var len;
for(i = 0, len = parts.length; i < len; i++)
{
if(!object[parts[i]])
{
object[parts[i]] = {};
}
object = object[[parts[i]]]
}
return object;
}
}//不用判断命名空间是否存在的情况下直接使用;MyGlobal.namespace("Person.Say"); //namespace函数当明明空间存在时会返回,不存在时会创建再返回,
MyGlobal.Person.Say.SayHello = function () {
alert(\'hello\');
} |
在想使用任意命名空间前,只需要调用一下:MyGlobal.namespace("Person.Say");
3、模块化
这个东西得长篇大论,要独立一篇文章来写。
4、零全局变量(闭包)
使用闭包的场景不多,当我需要一段JS,并且这段JS不需要被其他JS访问,只是为了实现单一的功能而创建的。向轻松学会闭包的可以查看这篇文章《javascript闭包》。
最简单的闭包:
|
1
2
3
4
|
(function(win)
{ //...自己的代码逻辑,有权访问外部。
}(window)); |
你可以在"自己的代码逻辑",写上你自己的逻辑。这种方式有如下两个要求:
-
代码不需要被其他代码所依赖;
-
代码不需要被经常扩展;
即,脚本非常短,且不需要与其他的JS进行交互,才可以使用这种闭包的方式。实际上它真正用上的场景不多的。