在javascript中,作用域是用来规定变量与函数可访问范围的一套规则。
作用域
最常见的作用域有两种,分别是全局作用域与函数作用域。
全局作用域中声明的变量与函数可以在代码的任何地方被访问。
一般来说,以下三种情况拥有全局作用域。
1 全局对象下拥有的属性和方法
window.name
window.location
window.top
window.window
2 在最外层声明的变量与方法
我们知道,全局上下文中的变量对象实际上就是window对象本身,因此在全局上下文中声明的变量与方法其实就变成了window的属性和方法,所以他们拥有全局作用域。
var foo = function() {};
var str = "out variable";
var arr = [1,2,3];
function bar() {};
3 在非严格模式下,函数作用域中未定义但是直接复制的变量与方法。
在非严格模式下,这样的变量与方法会自动变成全局作用域对象 window 的属性,因此他们拥有全局作用域。
function foo() {
bar = 20;
}
function fn() {
foo();
return bar + 30;
}
console.log(fn());
在实践中,无论是从避免多人协作带来的冲突的角度问题,还是从性能上优化的角度考虑,我们都要尽可能少得自定义全局变量和方法。
函数作用域
函数作用域中声明的变量与方法,只能被下层子作用域访问,而不能被其他不相干的作用域访问。
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时,往往会通过自执行函数的方式来实现模块化,而模块化是实际开发中需要重点掌握的开发思维,在后续的文章会讲解。
作用域链
作用域链是由当前执行环境与上层执行环境的一系列变量对象的组成,他保证了当前执行环境对符合访问权限的变量和函数的有序访问。
如果是第一次接触作用域链的概念,则可能对上面这句话有点理解上的困难,下面就结合一个例子,以及对应的图标来说明。
var a = 20;
function test() {
var b = a + 10;
function innerTest() {
var c = 10;
return b + c;
}
return innerTest();
}
test();
在上面这个例子中,先后创建了全局函数test 和函数 innerTest 的执行上下文。假设他们的变量对象分别为VO(global),VO(test),VO(innerTest),那么innerTest的作用域链则同时包含这三个变量对象。
所以innerTest的执行上下文可表示:
innerTestEC = {
VO: {...},
scopeChain: [VO(innerTest),VO(test),VO(global)],
this: {}
}
可以使用一个数组来表示作用域的有序性,数组的第一项scopeChain[0]表示作用域链的顶端,而数组的最后一项scopeChain[2]作为作用域链的末端,所有作用域链的最末端都是全局作用域变量对象。