JavaScript 尽量少用全局变量

2019-01-15  本文已影响3人  游学者灬墨槿

尽量少用全局变量

JavaScript 使用函数管理作用域。变量在函数内声明,只在函数内有效,不能在外部使用。全局变量与之相反,在函数外部声明,在函数内无需声明即可简单地使用。

var name = 'spirit';
console.log(this);      // Window 对象,并且该对象上多了一个 name 属性
console.log(this.name); // spirit
console.log(window);            // window 对象
console.log(this);              // window 对象
console.log(window === this);   // true

// 创建全局变量的方式:
// 方式一:在函数外声明变量
var global1 = "global1";
// 方式二:函数内不使用 var 关键字声明变量
function func() {
    global2 = "global2";
}
func();
console.log(window);             // window 对象,并且该对象上多了 global1、global2 两个属性
console.log(window.global1);     // global1
console.log(window['global2']);  // global2

全局变量的问题:

问题在于全局变量在整个 JavaScript 应用或 Web 页面内共享。它们生存于同一个全局命名空间内,总有可能发生命名冲突。譬如当一个应用程序中两个独立的部分定义了同名的全局变量,但却有不同目的时。

[图片上传失败...(image-373c91-1547520893760)]

function func() {
    var a = b = 0;
    /*
     * a 为局部变量,b 为全局变量
     * 产生的原因很简单:从右到左的操作符优先级。先执行 b = 0,而此刻 b 未声明,
     * 因此 b 为全局变量。整个过程等价于:
     * var a;
     * b = 0;
     * a = b;
     */
}
// 正确的做法:
function func() {
    var a, b;
    a = b = 0;
}

变量释放时的副作用

隐含全局变量(不使用 var 关键字创建)与明确定义的全局变量有细微的不同。不同之处在于能否使用 delete 操作符撤销变量。

这表明隐含全局变量严格来讲不是真正的变量,而是全局对象的属性。属性可以通过 delete 操作符删除,而变量不行。

// 明确定义的全局变量
var global1 = "global1";
// 隐含全局变量
function func() {
    global2 = "global2";
}
func();
// 删除这两个全局变量
delete global1;
delete global2;

// 测试删除情况
console.log(typeof global1);    // string
console.log(typeof global2);    // undefined

访问全局对象

在浏览器下,可通过 window 属性在代码的任意位置访问到全局对象(除非做了特别处理而发生了意外,如声明了一个名为 window 的局部对象)。

var global = (function() {
    return this;
}());
console.log(global);    // window 对象

单一 var 模式(Single var Pattern)

使用一个 var 关键字在函数顶部用逗号进行多个变量声明。

好处:

示例:

function func() {
    var a = 1,
        b = 2,
        sum = a + b,
        myObject = {},
        i,
        j;
        
    // 函数体...
}

初始化变量

声明变量的同时初始化变量(为变量赋初值)。

好处:

提升:凌散变量的问题

提升:允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等同于在函数顶部进行声明。

存在的问题:在变量声明前使用变量,会导致逻辑错误。因为变量提升,在同一个范围(同一函数)里声明的变量会默认提到函数顶部,因此无论在函数哪个位置使用该变量,该变量都可以视为已经声明。

[图片上传失败...(image-2a08f8-1547520893760)]

myName = "global";  // 全局变量
function func() {
    alert(myName);  // "undefined"
    var myName = "local";
    alert(myName);  // 局部变量
}
func();

// 前面代码片段运行结果等同于下述代码

myName = "global";      // 全局变量
function func() {
    var myName;         // 等同于 -> var myName = undefined
    alert(myName);      // undefined
    myName = "local";
    alert(myName);      // 局部变量
}

命名空间

简单的通过全局对象的单一属性表示的功能性分组。

创建命名空间的方法:

// 创建一个命名空间
var namespace = {};

// 在该命名空间上挂载一个名为 DOM 的对象
namespace.DOM = {};
// 在该命名空间上挂载一个名为 Event 的对象
namespace.Event = {};

一.普通写法:

// 创建一个命名空间
var NameSpace = NameSpace || {};
// 在该命名空间上挂载一个名为 Hello 的构造函数
NameSpace.Hello = function() {
    this.name = 'world';
};
// 为 Hello 对象写入公共方法
NameSpace.Hello.prototype.sayHello = function(_name) {
    return 'Hello ' + (_name || this.name);
};
var hello = new NameSpace.Hello();

二.简洁写法:

var NameSpace = window.NameSpace || {};
NameSpace.Hello = new function() {
    var self = this,
        name = 'world';
    self.sayHello = function(_name) {
        return 'Hello' + (_name || name);
    };
};

不清楚这种方法为什么能够实现命名空间的作用,新建的 hello 变量仍然是个全局变量,如果在其他外部文件中也有 hello 变量,那么这不就造成命名冲突了?

NameSpace.Hello = (function() {
    // 待返回的公有对象
    var self = {};
    // 私有变量或方法
    var name = 'world';
    // 公有方法或变量
    self.sayHello = function(_name) {
        return 'Hello' + (_name || name);
    }
    // 返回的公有对象
    return self;
}());

注意事项:

命名空间本身也是一个全局对象,在添加该“命名空间”时,有可能覆盖全局空间中的同名对象。因此我们需要在声明命名空间前进行检查,保证全局空间的安全。

var NameSpace = NameSpace || {};
var YourGlobal = {
    namespace : function(ns) {
        var parts = ns.split("."),
            object = this,
            len = 0,
            i = 0;
        
        for(i = 0, len = parts.length; i < len; i++) {
            if(!object[parts[i]]) {
                object[parts[i]] = {};
            }
            object = object[parts[i]];
        }
        
        return object;
    }
};

/*
 * 同时创建 YourGlobal.Books 和 YourGlobal.Books.Event
 * 因为之前没有创建过它们,因此每个都是全新创建的
 */
YourGlobal.namespace("Books.Event");

// 现在你可以使用这个命名空间
YourGlobal.Books.Event.click = function() {
    //...
}

/*
 * 不会操作 YourGlobal.Books 本身,只给它添加 DOM 对象
 * 它会保持 YourGlobal.Books.Event 保持不变
 */
YourGlobal.namespace("Books.DOM");

// 仍然是合法的引用
console.log(YourGlobal.Books.Event.click);

// 同样可以在方法调用之后立即给它添加新属性
YourGlobal.namespace("Books").ANewBook = {};
上一篇下一篇

猜你喜欢

热点阅读