在浏览器中,全局对象指的 window 对象,因此任何在全局作用域中声明的变量和函数都是 window 对象的属性。

1. 全局变量带来的问题

  • 命名冲突

  • 代码的脆弱性

  • 难以测试

2. 意外的全局变量

function doSomething() {
var count = 10;
title = "Maintainable JavaScript"; // 不好的写法:创建了全局变量
}

(注:使用单 var 语句是不是很容易出现这种错误?)

这时候需要 JSLint 或 JSHint 发挥作用了。

3. 单全局变量方式

  • YUI 定义了唯一一个 YUI 全局对象

  • jQuery 定义了两个全局对象,$ 和 jQuery

  • Dojo 定义了一个 dojo 全局对象

  • Closure 类库定义了一个 goog 全局对象

“单全局变量”的意思是所创建的这个唯一全局对象名是独一无二的(不会和内置 API 产生冲突),并将你所有的功能代码都挂载到这个全局对象上。

var MaintainableJS = {};

MaintainableJS.Book = function(title) {
this.title = title;
this.page = 1;
};

MaintainableJS.Book.prototype.turnPage = function(direction) {
this.page += direction;
};

MaintainableJS.Chapter1 = new MaintainableJS.Book("Introduction to Style Guidelines");
MaintainableJS.Chapter2 = new MaintainableJS.Book("Basic Formatting");
MaintainableJS.Chapter3 = new MaintainableJS.Book("Comments");
3.1 命名空间
var ZakasBooks = {};

// 表示这本书的命名空间
ZakasBooks.MaintainableJavaScript = {};

// 表示另外一本书的命名空间
ZakasBooks.HighPerformanceJavaScript = {};

保证命名空间存在:

var YourGlobal = {
namespace: function(ns) {
var parts = ns.split("."),
object = this,
i, len;

for (i=0, len=parts.length; i<len; i++) {
if (!object[parts[i]]) {
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
}
};

基本用法如下:

YourGlobal.namespace("Books.MaintainableJavaScript");

YourGlobal.Books.MaintainableJavaScript.author = "Nicholas C. Zakas";

YourGlobal.namespace("Books.HighPerformanceJavaScript");

console.log(YourGlobal.Books.MaintainableJavaScript.author);

YourGlobal.namespace("Books").ANewBook = {};
3.2 模块

模块并没有创建新的全局变量或命名空间,相反,所有的这些代码都存放于一个表示执行一个任务或发布一个接口的单函数中。

两种最流行的类型是“YUI 模块”模式和“异步模块定义”(Asynchronous Module Definition,简称 AMD)模式。

YUI 模块

YUI 模块就是使用 YUI 类库来创建新模块的一种模式。YUI3 中包含了模块的概念,写法如下:

YUI.add("module-name", function(Y) {
// 模块正文
}, "version", { requires: ["dependency1", "dependency2"]});

YUI 中约定在每个模块内使用命名空间的方式来管理模块代码,比如:

YUI.add("my-books", function(Y) {
Y.namespace("Books.MaintainableJavaScript");
Y.Books.MaintainableJavaScript.author = "Nicholas C. Zakas";
}, "1.0.0", { requires: ["dependency1", "dependency2"] });

通过调用  YUI().use 函数并传入想加载的模块名称来使用模块:

YUI().use("my-books", "another-module", function(Y) {
console.log(Y.Books.MaintainableJavaScript.author);
});

“异步模块定义”(AMD)

AMD 模块和 YUI 模块最大的不同在于,(AMD 中)每一个依赖都会对应到独立的参数传入方法中。

define("module-name", ["dependency1", "dependency2"], 
function(dependency1, dependency2) {
// 模块正文
});

AMD模块期望从工厂方法中返回它们的公有接口:

define("my-books", ["dependency1", "dependency2"],
function(dependency1, dependency2) {

var Books = {};
Books.MaintainableJavaScript = {
author: "Nicholas C. Zakas"
};

return Books;
});

AMD 模块可以匿名,完全省略模块名称,因为模块加载器可以将 JavaScript 文件名当作模块名称。

AMD 规范:https://github.com/amdjs/amdjs-api/wiki/AMD。

要想使用 AMD 模块,需要使用一个兼容的模块加载器。Dojo 的标准模块加载器支持 AMD 模块的加载。

// 使用 Dojo 加载 AMD 模块
var books = dojo.require("my-books");

console.log(books.MaintainableJavaScript.author);

Dojo 同样将自己也封装为 AMD 模块,叫做“dojo”,因此它可以被其他 AMD 模块加载。

另一个模块加载器是 RequireJS(http://www.requirejs.org/):

// 使用 RequireJS 加载 AMD 模块
require(["my-book"], function(books) {
console.log(books.MaintainableJavaScript.author);
});

jQuery 和 Dojo 都可以使用 RequireJS 来加载 AMD 模块。

4. 零全局变量

某些特殊场合下才会有用。最常见的情形就是一段不会被其他脚本访问到的完全独立的脚本。最常见的用法是创建一个书签。

(function(win) {

var doc = win.document;
// 在这里定义其他的变量

// 其他相关代码

}(window));

这个函数包装器(function wrapper)可以用于任何不需要创建全局对象的场景。