《编写可维护的 JavaScript》编程实践 - 第 6 章 避免使用全局变量

编程实践 - 第 6 章 避免使用全局变量

《编写可维护的 JavaScript》—— Nicholas C. Zakas

JavaScript 执行环境在很多方面都有其独特之处。全局变量和函数的使用便是其中之一。

JavaScript 的初始执行环境是由多种多样的全局变量所定义的,这些全局变量在脚本环境创建之初就已经存在了。

我们说这些都是挂载在“全局对象”(global object)上的,“全局对象”是一个神秘的对象,它表示了脚本的最外层上下文。

在浏览器中,window 对象往往重载并代理了全局对象,因此任何在全局作用域中声明的变量和函数都是 window 对象的属性,比如:

var color = 'red';

function getColor() {
  return color;
}

console.log(window.color);
consoel.log(window.getColor());

colorgetColor 都是 window 对象的属性,即使没有显式挂载。

1. 全局变量带来的问题

创建全局变量是糟糕的实践。全局变量越多,引入错误的概率将会因此变得越来越高。

1.1. 命名冲突

当脚本中的全局变量和全局函数越来越多时,发生命名冲突的概率也随之增高。

所有变量都被定义为局部变量,这样代码才是最容易维护的。

全局函数、全局变量如果分隔到不同的文件,其依赖关系很难追踪到:

function getColor() {
  return color; // color 是哪里来的?
}

全局环境是用来定义 JavaScript 内置对象的地方,如果你在全局环境定义了变量,则很可能与浏览器附带的内置变量冲突。

1.2. 代码的脆弱性

一个依赖于全局变量的函数,与上下文环境深耦合。
如果环境发生改变,函数很可能就失效了,如果全局变量 color 不存在,getColor() 函数会报错。

这意味着,任何对全局环境的变量进修改都可能造成某处代码出错;
同样,任何函数也会不经意间修改全局变量,导致对全局变量值的依赖变得不稳定。

当定义函数的时候,最好尽可能多地将数据至于局部作用域。
任何来自函数外部的数据都应当以参数形式传进来,这样做可以将函数和外部环境隔离开来,并且在函数内的修改不会影响到程序的其他部分。

1.3. 难以测试

任何依赖全局变量才能正常工作的函数,只有为其重新创建完整的全局环境才能正确地测试它。

这意味着,你得同时管理测试、生产情况下的全局环境,一旦全局环境发生变化,则要进行同步。
这会增加维护的成本,且越来越难以理清头绪。

确保你的函数不会对全局变量有依赖,这会提高可测试性(testability)。

2. 意外的全局变量

当你给一个未被声明的变量赋值时,JavaScript 会自动创建一个全局变量,比如:

function doSomething() {
  var count = 10;
      title = '哇哈哈'; // 不好的写法:创建了全局变量。
}

上面的代码展示了一个常见的错误,不小心将 , 敲成了 ;,造成创建了一个全局变量 title

可使用 JSLint 或 JSHint 来给予警告。

也可启用严格模式,在支持严格模式的环境(IE 10+、Firefox 4+、Chrome)中,给未声明的变量赋值会抛出一个 ReferenceError 错误。

3. 单全局变量方式

“单全局变量”的意思是所创建的这个唯一全局对象是独一无二的,并将你所有的功能代码都挂载到这个全局对象上。

因此每个可能的全局变量都成为了你唯一全局对象属性,从而不会创建多个全局变量。

因为团队中的每个人都知道这个全局对象,因此很容易做到继续为它添加属性以避免全局污染。

  • YUI 定义了 YUI 全局对象
  • jQuery 定义了 $jQuery
  • Dojo 定义了 dojo
  • Closure 定义了 goog

3.1. 命名空间

即使你的代码只有一个全局对象,也存在着全局污染的可能。

大多数使用单全局变量模式的项目同样包含“命名空间”的概念。
命名空间是通过全局对象的属性将功能进行分组,比如,YUI

  • Y.DOM 下的所有方法都和 DOM 操作相关
  • Y.Event 与事件相关

3.2. 模块

另外一种基于单全局变量的扩充方法是使用模块(modules)。

模块是一种通用的功能片段,它并没有创建新的全局变量或者命名空间;
模块的代码存在于一个函数中,用于执行任务或暴露接口;
可以用名称表示这个模块,同样这个模块可以依赖其他模块。

有一些通用的模式用来创建模块,最流行的是 “YUI 模式” 和异步模块定义(Asynchronous Module Definition,简称 AMD)模式。

YUI 模块

// 定义模块
YUI.add('module-name', function(Y) {
  Y.namespace('System.dept')
}, 'version', { requires: [ 'dep1', 'dep2' ] });

// 使用模块
YUI().use('module-name', function(Y) {
  // Y.System.dept
});

将命名空间和模块的概念合并在一起了

异步模块定义(AMD)

指定模块名称、依赖、工厂方法,依赖加载完成后执行这个工厂方法。

这些内容都作为参数传递到 define() 全局函数:

define('module-name', [ 'dep1', 'dep2' ], function(dep1, dep2) {
  var person = {
    name: '张三',
    say: function() {
      console.log(this.name);
    },
  }

  return person;
});

AMD 中每一个依赖都会对应到独立的参数传入工厂方法里,以避免命名冲突,并返回公有接口。

要想使用 AMD 模块,还需要一个与之兼容的模块加载器。
比较著名的加载器是 RequireJS,它添加了另一个全局函数 require(),专门用来加载指定的依赖和执行回调函数,比如:

require([ 'module-name' ], function(person) {
  person.say();
});

调用 require() 时会首先加载依赖,依赖加载并执行完成后执行回调函数。

4. 零全局变量

(function(win) {
  var doc = win.document;

  // 其他代码

}(window));

不需要暴露接口,也不需要依赖其他的脚步;作为完全独立的脚步插入到页面中。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值