一、尽量少用全局对象
全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免。
web页面包含不是该页面开发者所写的代码也是比较常见的,例如:
- 第三方的JavaScript库
- 广告方的脚本代码
- 第三方用户跟踪和分析脚本代码
- 不同类型的小组件,标志和按钮
比方说,该第三方脚本定义了一个全局变量,叫做result;接着,在你的函数中也定义一个名为result的全局变量。其结果就是后面的变量覆盖前面的,第三方脚本就一下子嗝屁啦!
因为,你不小心,在代码的某一处修改了全局变量, 会导致依赖全局变量的其它模块出错。而且出错原因难调试,难找到。
再者就是,网页运行肯定用到window对象,浏览器引擎又要遍历一次window的属性,性能下降。
- 全局变量是维系不同模块之间的纽带,模块之间只能通过全局变量来访问对方提供的功能
- 能使用局部变量的时候,绝不要使用全局变量
12345678 | var i,n,sum //globals function averageScore(players){ sum =0; for (i = 1, i = player.length; i<n; i++){ sum += score(players[i]); } return sum/n; } |
保持这些变量为局部变量,仅将其作为需要使用它们的代码的一部分。
12345678 | function averageScore(players){ var i,n,sum; sum =0; for (i = 1, i = player.length; i<n; i++){ sum += score(players[i]); } return sum/n; } |
在browser中,this关键字会指向全局的window对象
JavaScript 的全局命名空间也被暴露为在程序全局作用域中可以访问的全局对象,该对象作为 this 关键字的初始值。在 Web 浏览器中,全局对象被绑定到全局的 window 变量。添加或修改全局变量会自动更新全局对象。
123 | this .foo; //undefined foo = "global foo" ; //"global foo" this .foo; //"global foo" |
类似地,更新全局对象也会自动地更新全局命名空间:
1234 | var foo = "global foo" ; this .foo; //"global foo" this .foo = "changed" ; foo; //"changed" |
两种用来改变全局对象的方式,通过var关键字声明以及给全局对象设置属性(通过this关键字)
通过全局对象进行针对当前运行环境的特性检测(Feature Detection),比如在ES5中提供了一个JSON对象用来操作JSON数据,那么可以通过if(this.JSON)来判断当前运行环境是否支持JSON
123456 | if (! this .JSON){ this .JSON ={ parse:..., stringify:... } } |
二、如何避免全局变量
方法一:只创建一个全局变量。
12345678910111213141516171819 | MYAPP.stooge = { "first-name" : "Joe" , "last-name" : "Howard" }; MYAPP.flight = { airline: "Oceanic" , number: 815, departure: { IATA: "SYD" , time: "2004-09-22 14:55" , city: "Sydney" }, arrival: { IATA: "LAX" , time: "2004-09-23 10:42" , city: "Los Angeles" } }; |
方法二:使用模块模式
123456789101112131415161718192021222324252627282930 | var serial_maker = function ( ) { // Produce an object that produces unique strings. A // unique string is made up of two parts: a prefix // and a sequence number. The object comes with // methods for setting the prefix and sequence // number, and a gensym method that produces unique // strings. var prefix = '' ; var seq = 0; return { set_prefix: function (p) { prefix = String(p); }, set_seq: function (s) { seq = s; }, gensym: function ( ) { var result = prefix + seq; seq += 1; return result; } }; }( ); var seqer = serial_maker( ); seqer.set_prefix = 'Q' ; seqer.set_seq = 1000; var unique = seqer.gensym( ); // unique is "Q1000" |
所谓模块模式,就是创建一个函数,该函数包括,私有变量和一个特权对象,特权对象的内容是,利用闭包能访问到私有变量的函数,最后返回特权对象。
首先,方法二,不仅可以当作全局变量用,也可以用在局部声明全局变量。因为就算你在不知道某个地方修改了seqer,就会立即报错,因为这是个对象,不是字符串。
方法三:零全局变量
零全局变量实际上是为了适应一小段封闭代码而采取的一种局部变量处理方式,只适合在一些特殊场景中使用。最常见的就是一些不会被其他脚本访问到的完全独立的脚本。
使用零全局变量的方式需要采用立即执行函数,用法如下。
123456 | ( function ( win ) { 'use strict' ; var doc = win.document ; //在此定义其他的变量并书写代码 } ) |
三、意外的全局变量
由于JavaScript的两个特征,不自觉地创建出全局变量是出乎意料的容易。首先,你可以甚至不需要声明就可以使用变量;第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。参考下面的代码:
12345 | function sum(x, y) { // 不推荐写法: 隐式全局变量 result = x + y; return result; } |
此段代码中的result没有声明。代码照样运作正常,但在调用函数后你最后的结果就多一个全局命名空间,这可以是一个问题的根源。
经验法则是始终使用var声明变量,正如改进版的sum()函数所演示的:
1234 | function sum(x, y) { var result = x + y; return result; } |
另一个创建隐式全局变量的反例就是使用任务链进行部分var声明。下面的片段中,a是本地变量但是b确实全局变量,这可能不是你希望发生的:
12345 | // 反例,勿使用 function foo() { var a = b = 0; // ... } |
此现象发生的原因在于这个从右到左的赋值,首先,是赋值表达式b = 0,此情况下b是未声明的。这个表达式的返回值是0,然后这个0就分配给了通过var定义的这个局部变量a。换句话说,就好比你输入了:
var a = (b = 0);
如果你已经准备好声明变量,使用链分配是比较好的做法,不会产生任何意料之外的全局变量,如:
1234 | function foo() { var a, b; // ... a = b = 0; // 两个均局部变量 } |
然而,另外一个避免全局变量的原因是可移植性。如果你想你的代码在不同的环境下(主机下)运行,使用全局变量如履薄冰,因为你会无意中覆盖你最初环境下不存在的主机对象
总是记得通过var关键字来声明局部变量
使用lint工具来确保没有隐式声明的全局变量