深刻理解javascript系列(六):做用域与做用域链

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

6.1  做用域

最多见的做用域有两种:全局做用域与函数做用域。

6.1.1 全局做用域

全局做用域中声明的变量与函数能够在项目代码的任何地方使用。前端

通常来讲,如下3种状况能够拥有全局做用域java

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

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

2.  在最外层声明的变量与方法性能优化

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

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

相关文章
相关标签/搜索