第六章 避免使用全局变量
JS执行环节有很多独特之处相对于其他语言来说,如全局变量和函数的使用。
JS本身的初始执行环境就是有多种多样的全局变量所定义的,这些全局变量在环境创始之初就存在了。
全局对象是一个神秘的对象,表示脚本的最外层上下文。
浏览器中 window对象往往重载并等于全局对象,因此任何在全局对象中声明的变量或者函数都为window对象的属性。
而不需要显式的将这些挂在到 window 对象上。
var color = "red";
function getColor(){
alert(color);
}
color === window.color;
getColor === window.getColor;
6.1 全局变量带来的问题
随着代码的增长,全局变量毫无疑问的会导致一些非常重要的可维护性问题。
6.1.1 命名冲突
当全局变量和全局函数越来越多的时候,发生命名冲突概率就会越来越大。各种变量,函数将会被重置,那么很多各种各样的BUG就会随之而来。
比如:
function getColor(){
alert(color);
}
中的 color 由于依赖与全局的变量,那么这种依赖关系将很难被追踪到,我在不小心重置了 color 后不知道有多少像该函数一样的函数进行了全局依赖的行为。
接下来,全局变量与一些浏览器内置API冲突的风险。
6.1.2 代码的脆弱性
一个依赖于全局变量的函数即是深耦合于上下文环境之中。环境的变化就可能会导致函数的失效。
但是当 color 被作为参数传递进函数的话,那么情况就大大不一样了。
function getColor( color ){
alert(color);
}
不在依赖全局变量,并且从与上下文的深耦合之中脱离出来。所以当定义函数尽可能的将数据置于局部变量之中,任何外部数据都应当以参数的形式传递进入函数,保证函数与其外部环境隔离开,不至于形成深度耦合的关系。
6.1.3 难以测试
任何依赖于全局变量才能正常工作的函数,只有为其重新创建完整的全局变量才能正确的测试,然后,就木有然后了。
保证函数不多全局变量有依赖,将大大增强代码的可测试性,当然不包括JS中原生的对象,如Date,Array等。
6.2 意外的全局变量
JS 中有很多陷阱会使我们一不小心就创建了全局变量,如:
function doSomething(){
var count = 10;
title = "abcdefg";
var a = b = 0;
}
对于意外的全局变量一些工具,比如JSLint和JSHint就可以起到作用了,因为意外创建全局变量并不会引起JS引擎的报错,有时候很难察觉到,而这些工具就是我们很好的预防,消除一些意外创建的情况。还有严格模式下也会报错来提醒程序猿。
6.3 单全局变量方式
YUI 引入 唯一 YUI全局变量。
jQuery 引入 $ 和 jQuery 全局变量。
Dojo 引入 dojo 全局变量。
Closure 引入 goog 全局变量。
单全局变量意味着创建一个唯一的全局对象名,将所有你的功能代码挂在到该对象上,都成为该对象的属性,从而不创建其他的全局变量。
function Book( title ){
this.title = title;
this.page = 1;
}
Book.prototype.turnPage = function( desc_num ){
this.page += desc_num;
}
var chapter1 = new Book("one");
var chapter2 = new Book("two");
var chapter3 = new Book("three");
会有好多全局变量,那么:
var MainJS = {};
MainJS.Book = function( title ){
this.title = title;
this.page = 1;
}
MainJS.Book.prototype.turnPage = function( desc_num ){
this.page += desc_num;
}
MainJS. chapter1 = new MainJS.Book("one");
MainJS. chapter2 = new MainJS.Book("two");
MainJS. chapter3 = new MainJS.Book("three");
其实,很简单,但是带来的效果确是非常不错的。
6.3.1 命名空间
JS中的命名空间,其实质就是不断的往一个定义的全局对象中,合理有规则的塞东西。
var MyGlobal = {
namespace : function( ns ){
var parts = ns.split("."),
object = this,
i, len;
if( parts[0] === "MyGlobal" ){
parts = parts.slice(1);
}
for( i = 0,len = parts.length; i
if( !object[parts[i]] ){
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
}
}
然后可以自由的创建命名空间了,
MyGlobal.namespace("aa.bb.cc.dd");
MyGlobal.namespace("MyGlobal.aa.bb.cc.dd");
6.3.2 模块
另外一种基于单全局变量的扩充方法是使用模块。
模块是一种通用的功能片段,并不创建全局变量和命名空间,而是将这些代码存放在一个表示执行一个任务或者发布一个借口的单函数中。两种流行的模块是 YUI模块 和 异步模块定义(AMD)。
YUI模块
是使用YUI JS类库创建新模块的一种模式,写法:
YUI.add("module-name", function(Y){
// 模块正文
}, "version", { requires: ["depend1", "depend2"] });
异步模块定义 AMD
define( "module-name", ["depend1", "depend2"] , function(d1, d2){
// 模块正文
});
6.4 零全局变量
JS代码注入到页面的时候可以实现不创建全局变量。当然使用的场景不会非常多。在一段完全独立的代码,或者代码非常小且不提供任何接口的时候。
(function(win){
// 代码 不暴露任何接口
}( window ));
只要代码需要被其他的代码所依赖的时候,就不可以使用零全局变量方式。在对于代码块的代码合并时候有挺大作用。
第七章 事件处理
事件处理在JS中是至关重要的,影响着网站的各个方面。所有的JS代码均通过事件绑定到UI上,所以大多前端工程师需要花费很多的事件来编写和修改事件处理程序。
大多事件处理程序相关代码和事件环境紧紧偶合在一起,导致了可维护性很糟糕。
7.1 典型用法
开发人员基本都了解,事件触发的时候事件对象event会作为回调函数参数传入事件处理程序之中。event对象中含有的大量信息基本上我们只会用到很小一部分。
// 不好的例子
function handleClick( event ){
var popup = document.getElementById( "popup" );
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "reveal";
}
addListener(element, "click", handleClick);
这是比较普遍的做法,实际上并不是很好,具有局限性。
7.2 规则1:隔离应用逻辑
上一段代码中,第一个问题是事件处理程序中包含了应用逻辑(application logic),应用逻辑是与应用相关的功能性代码,而不是用户行为相关的。
将应用逻辑从所有的事件处理程序中抽离出来是一种最佳实践。因为很有可能在之后的某段代码中我们会使用到同一段逻辑,抽离就降低了代码的耦合度,增强了可读性和维护成本。
如:
var MyApplication = {
handleClick : function(event){
this.showPopup( event );
} ,
showPopup : function(event){
var popup = document.getElementById( "popup" );
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "reveal";
}
}
addListener(element, "click", function(event){
MyApplication.handleClick( event );
});
这样就将应用逻辑从事件处理程序中剥离,如果在某些地方又要使用该逻辑就可以很方便的使用方法。
7.3 不要分发事件对象
再拨离了应用逻辑之后上段代码又引发了一个问题,event对象被无节制的分发,所以,应用逻辑不应当依赖于event对象。
1.方法接口没有表明那些数据是必要的。
2.影响测试方面效率。
var MyApplication = {
handleClick : function(event){
this.showPopup( event.clientX, event.clientY );
} ,
showPopup : function(x, y){
var popup = document.getElementById( "popup" );
popup.style.left = x + "px";
popup.style.top = y + "px";
popup.className = "reveal";
}
}
这样就很不错了,再加上进入程序时,阻止默认事件和事件冒泡即可。
清楚地展示了事件处理程序和应用逻辑之间的分工。应用逻辑也不需要对event产生任何依赖,进而很多地方都可以使用相同的应用逻辑。