编写可维护的javascript(六):避免使用全局变量

来源:互联网 发布:汉语言文学专业 知乎 编辑:程序博客网 时间:2024/05/18 17:58

全局变量带来的问题


命名冲突
当脚本中的全局变量和全局函数越来越多的时候,发生命名冲突的概率也随之增高,即很可能无意间就使用了一个已经声明了的变量。所有的变量都被定义为局部变量,这样的代码才是最容易维护的。
代码的脆弱性
一个依赖于全局变量的函数即是深耦合于上下文环境中,如果环境发生改变,函数很可能就失效了。
难以测试
当你想进行一些单元测试的时候,发现所有的测试都要依赖于一些全局变量才会正常工作。
所以,确保函数不会对全局变量有依赖,当然函数可能会依赖原生的Javascript全局对象,比如Date等。
(类似Date、Array的全局对象是可以直接依赖的,而全局变量更多来自程序开发者。)


单全局变量方式
“单全局变量”的意思是创建的这个唯一全局对象名是独一无二的,并将你所有的功能代码都挂载到这个全局对象上,因此每一个可能得全局变量都成为唯一全局变量的属性,从而不会创建多个全局变量。比如:想让每一个对象表示本书的一章,代码如下:

    function Book(title) {        this.title = title;        this.page = 1;    }    Book.prototype.turnPage = function(direction) {        this.page += direction;    };    var Charpter1 = new Book("Introduction to Style Guidelines");    var Charpter2 = new Book("Basic Formatting");    var Charpter3 = new BooK("Comments");

这段代码创建了4个全局对象:Book、Chaipter1、Charpter2、Charpter3。单全局变量的模式只会创建一个全局对象并将这些对象赋值为它的属性。

    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");

这段代码只有一个全局对象,MaintainableJS。其他任何信息都挂载到这个对象上,因此很容易做到为它添加属性以避免全局污染。
命名空间
将功能按照命名空间进行分组,可以让单全局对象变得井然有序,同事可以让团队成员能够知晓功能应该属于哪个部分。在Javascript中你可以使用对象来轻而易举地创建你自己的命名空间,比如:

    var ZakasBooks = {};    // 表示这本书的命名空间    ZakasBooks.MaintainableJS = {};    // 表示另外一本书的命名空间    ZakasBooks.HighPerformanceJS = {};

另外一个场景,每个文件都需要给一个命名空间挂载东西。在这种情况下,首先保证这个命名空间是已经存在的。这时全局对象非破坏性的处理命名空间的方式则非常有用,如下:

    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() 方法,给这个方法传入一个表示命名空间对象的字符串,它会非破坏性地创建这个命名空间,基本用法如下:

    /*     * 同时YourGlobal.Books和YourGlobal.Books.MaintainableJS     * 因为之前没有创建过它们,因此每个都是全新创建的     */    YourGlobal.namespace("Books.MaintainableJS");    // 现在可以使用这个命名空间了    YourGlobal.Books.MaintainableJS.author = "Mangoyi";    /*     * 不会操作YourGlobal.Books本身,同时会给它添加HighPerformanceJS     * 它会保持YourGlobal.Books.MaintainableJS原封不动     */    YourGlobal.namespace("Books.HighPerformanceJS");    // 任然是合法的引用    console.log(YourGlobal.Books.MaintainableJS.author);    // 你同样可以在方法调用之后立即给它添加新属性    YourGlobal.namespace("Books").ANewBook = {};

基于单全局对象使用namespace() 方法让开发者放心地认为命名空间总是存在的。
这样每个文件都可以先调用namespace() 来声明开发者将要使用的命名空间,这样不会对已有的命名空间造成破坏,可以让开发者解放出来,在命名空间之前不必再去判断它是否存在。

文章内容从《编写可维护的Javascript》[美] Nicholas C. Zakas著 一书中总结写出。

原创粉丝点击