我们最常见的作用域有两种,分别为全局作用域与函数作用域。
全局作用域
全局作用域中声明的变量与函数,可以在代码的任何地方被访问。
一般来说,以下三种情形拥有全局作用域。
- 全局对象下的拥有的属性与方法。
window.name
window.location
window.top
...
- 在最外层声明的变量与方法。
我们知道全局上下文中的变量对象实际上就是window对象本身,因此我们在全局上下文中声明的变量与方法其实也变成了window的属性与方法,因此他们也拥有全局作用域。
var foo = function() {}
var str = 'out variable';
var arr = [1, 2, 3];
function bar() {}
...
- 在非严格模式下,函数作用域中未定义但直接复制的变量与方法。
在非严格模式下,这样的变量与方法会自动变成全局对象window的属性。因此他们也会有用全局作用域。
function foo() {
bar = 20;
}
function fn() {
foo();
return bar + 30;
}
fn(); // 50
在实践中,无论是从避免多人协作带来的冲突的角度考虑,还是从性能优化的角度考虑,我们都要尽量少的自定义全局变量与方法。
函数作用域
函数作用域中声明的变量与方法,只能被下层子作用域访问。而不能被其他不相干的作用域访问。
function foo() {
var a = 20;
var b = 30;
}
foo();
function bar() {
return a + b;
}
bar(); // 因为作用域的限制,bar中无法访问到变量a,b,因此执行报错
function foo() {
var a = 20;
var b = 30;
function bar() {
return a + b;
}
return bar();
}
foo(); // 50 bar中的作用域为foo的自作用域,因此能访问到变量a, b
在ES6以前,ECMAScript没有块级作用域,因此使用时需要特别注意,一定是在函数环境中才能生成新的作用域。如下情况则不会有作用域的限制。
var arr = [1, 2, 3, 4, 5];
for(var i = 0; i < arr.length; i++) {
console.log('do something by ', i);
}
console.log(i); // i == 5
因为没有块级作用域,因此单独的`{ }`并不会产生新的作用域。这个时候i的值会保留下来,在for循环结束之后仍然能够访问。
模拟块级作用域
没有块级作用域会给我们的开发带来一些困扰。例如上面for循环的例子中,i值仍然在作用域中可以被访问,那么这个值就会对作用域中的其他同名的变量造成干扰。因此我们需要想办法模拟块级作用域。而我们刚好知道,一个函数可以生成一个作用域,因此这个时候,我们可以利用函数来达到我们的目的。
var arr = [1, 2, 3, 4, 5];
(function() {
for(var i = 0; i < arr.length; i++) {
console.log('do something by ', i);
}
})();
console.log(i); // i is not defined
这种方式叫做函数自执行。
通过这种方式,我们就可以限定变量i值仅仅只在for循环中生效而不会对其他代码造成干扰。
函数自执行时声明一个匿名函数并且立即执行,这种方式大概有如下几种写法。
(function() {
...
})(); // 最常用
+function() {
...
}();
-function() {
...
}();
!function() {
...
}();
当我们使用ECMAScript5时,往往通过函数自执行的方式来实现模块化。而模块化是我们实际开发中需要重点掌握的开发思维,我们会在后续的章节中学习到相关的思路。