深入理解javascript系列(六):作用域与作用域链

在javascript中,作用域是用来规范变量函数可访问范围的一套规则

6.1  作用域

最常见的作用域有两种:全局作用域与函数作用域。

6.1.1 全局作用域

全局作用域中声明的变量与函数可以在项目代码的任何地方使用。

一般来说,以下3种情况可以拥有全局作用域

1.  全局对象下拥有的属性与方法。(回忆一下,我们在变量对象系列说过的全局上下文的特殊性)

window.name
window.location
window.top
...复制代码

2.  在最外层声明的变量与方法

我们知道,全局上下文中的变量对象就是全局对象window,所以在全局上下文中声明的变量与方法其实也就是window的属性与方法(记着:javascript中声明的所有变量都保存在变量对象中),所以他们也拥有全局作用域。

3.  在非严格模式下,函数作用域中那些未定义却赋值的变量与方法都会自动变成window的属性与方法。

在实践中,无论是从避免多人协作带来的冲突的角度考虑,还是从性能优化的角度考虑,我们都要尽可能少地自定义全局变量和方法。

6.1.2 函数作用域

函数作用域中声明的变量与方法,只能被下一层子作用域访问,而不能被其它不相干的作用域访问。

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以前(现在ES2018已出),ECMAScript没有块级作用域,因此使用时需要特别注意,一定是在函数环境中才能生成新的作用域,下面的情况则不会有作用域的限制。

var arr = [1,2,3,4,5];

for(var i = 0; i<arr.length; i++) {
    console.log('i',i);
}
console.log(i);   // i == 5复制代码

因为没有块级作用域,因此单独的'{}'并不会产生新的作用域。这个时候i的值会被保留下来,在for循环结束后仍然能够被访问。因此,在ES6之前我们需要模拟块级作用域。

6.1.3  模拟块级作用域

如果没有块级作用域则会给我们的开发带来一些困扰(初学javascript的如写个选项卡)。例如,上面的for循环的例子中,i值在作用域中仍然可以被访问,那么这个值就会对作用域中其它同名的变量造成干扰,因此我们需要模拟一个块级作用域。我们知道一个函数能够生成一个作用域,因此这个时候,可以利用函数来达到我们的目的。

var arr = [1,2,3,4,5];

(function(){
    for(var i = 0; i<arr.length; i++) {    console.log('i',i);
}})()

console.log(i);   // i is not defined复制代码

这种方式叫做函数自执行。

通过这种方式,我们就可以限定变量i值仅仅只在for循环中生效,而不会对其它代码造成干扰。

自执行函数的写法有很多,这里就不做累述。

当我们使用ECMAScript5时,往往通过函数自执行的方式来实现模块化。而模块化是实际开发中需要重点掌握的开发思维(模块化相关话题我会在之后分享自己的笔记)。

6.2  作用域链

作用域链(Scope Chain)是当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限变量和函数的有序访问。

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }
    
    return innerTest();
}
test();复制代码

请先按照call stack的调用方式,在你的大脑中执行本次代码。

在上面的例子中,先后创建了全局函数test和函数innerTest的执行上下文(当test(),表示其执行上下文入栈)。假设它们的变量对象分别为VO(global)、VO(test)、VO(innerTest),那么innerTest的作用域链则同时包含了这三个变量对象。

所以innerTest的执行上下文可表示如下。

innerTestEC = {
    VO: {...},                                        //变量对象
    scopeChain: [VO(innerTest),VO(test),VO(global)],  //作用域链
    this: {}
}复制代码

可以用一个数组来表示作用域链的有序性。数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项则为作用域链的最末端。所有作用域链的最末端都是全局变量对象。

很多人会用父子关系或者包含关系来理解当前作用域与上层作用域之间的关系。但我更喜欢@阳波大神的描述:以当前上下文的变量对象为起点,以全局变量对象为终点的单方向通道。


理解作用域链至关重要,但是更多的知识还需要结合闭包来理解。

这些都是我以往的学习笔记。如果您看到此笔记,希望您能指出我的错误。有这么一个群,里面的小伙伴互相监督,坚持每天输出自己的学习心得,不输出就出局。希望您能加入,我们一起终身学习。欢迎添加我的个人微信号:Pan1005919589



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值