JavaScript作用域与作用域链

JavaScript作用域

在JavaScript中,变量的作用域有全局作用域和局部作用域两种

1.  全局作用域(Global Scope)

  在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

  (1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:

 1   var jotaro = "承太郎";
 2   function doSomething() {
 3     var joseph = "乔瑟夫";
 4     function innerSay() {
 5       console.log(joseph)
 6     }
 7     innerSay();
 8   }
 9   console.log(jotaro) //承太郎
10   console.log(joseph) //error
11   doSomething(); //乔瑟夫
12   innerSay() //error

      (2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:

1   function doSomething() {
2     var jotaro = "承太郎";
3     joseph = "乔瑟夫";
4     console.log(jotaro)
5   }
6   doSomething(); //承太郎
7   console.log(joseph) //乔瑟夫
8   console.log(jotaro) //脚本错误

变量joseph拥有全局作用域,而jotaro在函数外部无法访问到。

      (3)所有window对象的属性拥有全局作用域

  一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。

2.  局部作用域(Local Scope)  

  和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域,例如下列代码中的blogName和函数innerSay都只拥有局部作用域。

1   function doSomething() {
2     var joseph = "乔瑟夫";
3     function innerSay() {
4       console.log(joseph)
5     }
6     innerSay();
7   }
8   console.log(joseph) //脚本错误
9   innerSay(); //脚本错误

 

作用域链(Scope Chain)

 1   var name1 = "haha";
 2   function changeName() {
 3     var name2 = "xixi";
 4     function swapName() {
 5       console.log(name1);//haha
 6       console.log(name2);//xixi
 7       var tempName = name2;
 8       name2 = name1;
 9       name1 = tempName;
10       console.log(name1);//xixi
11       console.log(name2);//haha
12       console.log(tempName);//xixi
13     }
14     swapName();
15     console.log(name1);//haha
16     console.log(name2);//xixi
17     //console.log(tempName);
18   }
19   changeName();
20   console.log(name1);//xixi
21  //console.log(name2); 抛出错误:Uncaught ReferenceError: name2 is not defined
22  //console.log(tempName);抛出错误:Uncaught ReferenceError: tempName is not defined

上述代码中,一共有三个执行环境:全局环境、changeName()的局部环境和 swapName() 的局部环境。所以,

 1.函数 swapName()的作用域链包含三个对象:自己的变量对象----->changeName()局部环境的变量对象 ----->全局环境的变量对象

 2.函数changeName()的作用域包含两个对象:自己的变量对象----->全局环境的变量对象。

就上述程序中出现的变量和函数来讲(不考虑隐形变量):

 1.swapName() 局部环境的变量对象中存放变量 tempName;

 2.changeName() 局部环境的变量对象中存放变量 name2 和 函数swapName();

 3.全局环境的变量对象中存放变量 name1 、函数changeName();

在swapName()的执行环境中,在执行第5句代码时,解析器沿着函数 swapName()的作用域链一级级向后回溯查找变量 name1,直到在全局环境中找到变量 name1.并输出在控制台上。同样,在执行第6句代码时,解析器沿着函数 swapName()的作用域链一级级向后回溯,在函数changeName()的变量对象中发现变量 name2.通过代码对 name1 和 name2进行交换,并输出在控制台上,根据结果我们发现,这两个变量的值确实交换了。因此我们可以得出结论,函数的局部环境可以访问函数作用域中的变量,也可以访问和操作父环境(包含环境)乃至全局环境中的变量。

  在changeName() 的执行环境中,执行第15行和第16行代码时,可以正确地输出  name1 和 name2 和两个变量的值(调用了函数swapName(),所以俩变量的值已相互交换),那是因为 name1 在changName()的父环境(全局环境)中, name2 在他自己的局部环境中,即 name1 和 name2 都在其作用域链上。但当执行第17行代码是发生错误 tempName is not defined。因为解析器沿着 函数changeName()的作用域链一级级的查找 变量 tempName时,并不能找到该变量的存在(变量 tempName不在其作用域链上),所以抛出错误。因此,我们可以得出结论:父环境只能访问其包含环境和自己环境中的变量和函数,不能访问其子环境中的变量和函数。

  同理,在全局环境中,其变量对象中只存放变量 name1 、函数changeName();  解析器只能访问变量 name1 和函数 changeName(), 而不能访问和操作 函数 changeName() 和函数 swapName() 中定义的变量或者函数。因此,在执行第21行和第22行代码时抛出变量没有定义的错误。所以说,全局环境只能访问全局环境中的变量和函数,不能直接访问局部环境中的任何数据。

  其实,我们可以把作用域链想象成这样(里面的能访问外面的,外面的不能访问里面的,图为参考): 

  

作用域链相关知识的总结:

1.执行环境决定了变量的生命周期,以及哪部分代码可以访问其中变量

2,执行环境有全局执行环境(全局环境)和局部执行环境之分。

3.每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链

4.函数的局部环境可以访问函数作用域中的变量和函数,也可以访问其父环境,乃至全局环境中的变量和环境。

5.全局环境只能访问全局环境中定义的变量和函数,不能直接访问局部环境中的任何数据。

6.变量的执行环境有助于确定应该合适释放内存。

引用文章:https://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

    :https://www.cnblogs.com/buchongming/p/5858026.html

原文地址:https://www.cnblogs.com/memeflyfly/p/14382462.html