未分类

JavaScript模仿块级作用域与私有变量

一、块级作用域

JS没有块级作用域的概念,定义在块语句中的变量,实际上是在包含函数中而非语句中创建的,如下面例子,变量ij创建之后即便离开了块语句,只要还在包含函数func中,就可以访问到这两个变量。必须等到函数func执行完毕销毁作用域之后,变量ij才会被销毁。

1
2
3
4
5
6
7
8
9
10
11
12
function func(count) {
for (var i = 0; i < count; ++i) {
console.log(i);
}
console.log(i); // 可以访问到i,输出5
{
var j = '2333';
}
console.log(j); // 可以访问到j,输出2333
}

func(5);

使用一个匿名立即调用函数就可以解决这个问题,模拟出块级作用域的效果。具体的语法如下,将块级作用域的内容放在这个匿名函数的函数体中,在这个匿名函数中定义的变量在函数执行完毕之后都会销毁,因此,外部访问不到块级作用域中的变量。这种技术经常在全局作用域中被用在函数外部,避免向全局作用域中添加过多的变量和函数。在多人协作开发的项目中,过多的全局变量和函数名常常会导致命名冲突,通过这种技术可以创建自己的私有作用域,不用再担心变量的命名,也不会搞乱全局作用域。

1
2
3
(function() {
// 模拟块级作用域
})();

二、私有变量

JS没有私有成员的概念,所有对象的属性都是公有的,外部都可以访问到。不过倒是有私有变量的概念,例如函数中定义的变量都可以被认为是私有变量,因为只能在函数内部访问到这些变量。下面介绍几种JS中创建私有变量常用的模式。

1. 构造函数内构建闭包

通过在函数内部创建闭包,那么闭包通过自己的作用域链可以访问这些私有变量,利用闭包,创建用于访问私有变量的公有方法(特权方法)。如下面代码示例,privateVarprivateFunc都位于闭包函数publicFunc的作用域链上,外部访问不到,只能通过publicFunc函数来访问。这种在构造函数中定义特权方法的方式有一个缺点,这个缺点就是构造函数模式下,每次创建对象的时候构造函数中的每个方法(无论公有或私有)都会被重新创建一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function MyObject() {
// 私有变量和函数
var privateVar = 1;
function privateFunc() {
return true;
}

// 特权方法
this.publicFunc = function() {
privateVar += 1; // 访问私有变量
if (privateFunc()) { // 调用私有方法
console.log(privateVar);
}
};
}

var obj = new MyObject();
obj.publicFunc(); // 输出2

2. 静态私有变量

这种模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中定义了私有变量和私有函数,然后定义构造函数和公有方法,公有方法在原型上定义。定义构造函数时不使用函数声明,而是直接使用函数表达式,并且不使用var关键字定义,目的是为了让构造函数变成一个全局函数,在私有作用域之外可以访问到构造函数,这样我们才可以创建对象。(严格模式下不允许给未经声明的变量赋值,因此不能使用这种方法)。这种模式下,特权方法在原型上定义,因此所有实例都使用同一个函数,这个特权方法作为一个闭包也总是引用私有作用域中的函数和变量,因此,这种模式下私有变量与函数都是有实例共享的,解决了构造函数模式下的多次创建问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 私有作用域
(function() {
// 静态私有成员和方法
var staticPrivateVar = '2333';
function staticPrivateFunc() {
console.log('static');
}
// 构造函数
MyObject = function() {}
// 公有方法
MyObject.prototype.publicFunc = function() {
console.log(staticPrivateVar); // 访问私有变量
staticPrivateFunc(); // 调用私有方法
}
})();

// 全局作用域
var obj = new MyObject();
obj.publicFunc(); // 输出"2333"和"static"

3. 模块模式

模块模式的存在是为了给单例创建私有变量和方法使单例得到增强,这种模式的语法如下,使用一个返回对象的匿名立即调用函数,在这个函数内部,首先定义了私有变量和函数,然后将一个对象字面量作为函数的返回值返回。返回的对象字面量中仅包含可以公开的属性方法。如果需要创建一个对象并对某些数据进行初始化,同时还要公开一些能够访问私有数据的方法,那么就可以使用模块模式。通过这种模式创建的对象都是Object的实例,没有特定属于某一种类型,因此不适用与instanceof之类的操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var singleton = (function() {
// 私有变量和函数
var privateVar = '2333';
function privateFunc() {
return true;
}
// 公有变量和函数
return {
publicVar: 'hehe~',
publicFunc: function() {
if (privateFunc()) { // 调用私有函数
console.log(privateVar); // 访问私有变量
}
}
};
})();

singleton.publicFunc(); // 输出'2333'
console.log(singleton.publicVar); // 输出'hehe~'

4. 增强的模块模式

这种模式解决了上面说到的模块模式无法检查类型的问题,具体做法就是使用某种类型的实例,然后在匿名立即调用函数返回之前给对象加入属性方法,具体语法如下。这种模式创建的单例对象属于某种特定的类型,可以使用instanceof操作进行检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 某种自定义类型
function SomeType() {}
var singleton = (function() {
// 私有变量和函数
var privateVar = '2333';
function privateFunc() {
return true;
}
// 创建对象
var object = new SomeType();
// 公有变量和函数
object.publicVar = 'hehe~';
object.publicFunc = function() {
if (privateFunc()) { // 调用私有函数
console.log(privateVar); // 访问私有变量
}
}
// 返回对象
return object;
})();

singleton.publicFunc(); // 输出'2333'
console.log(singleton.publicVar); // 输出'hehe~'
console.log(singleton instanceof SomeType); // 检测类型,输出'true'

分享到