【JS-task04】

如何理解JS作用域与作用域链?

小课堂

分享人:钟楚炯

目录

1.背景介绍

2.知识剖析

3.常见问题

4.解决方案

5.编码实战

6.扩展思考

7.参考文献

8.更多讨论

1.背景介绍

JS中声明的变量跟函数有它自己的可访问范围,超出这个范围就访问不到这个变量\函数。这个可访问范围就是今天要说的作用域。

作用域控制着变量跟函数的可见性和生命周期。

2.知识剖析

1.全局作用域(Global Scope)

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

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

                     
                    function person(){
                        var Name="老威";
                        nickName="掏粪男孩";
                    }
                    person();
                    console.log(nickName);
                    console.log(Name);
                     
            

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

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

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

                     
                    var site = 'baidu.com';
                    function getSite() {
                      alert(this.site);
                    }
                    alert(window.site);  // 'baidu.com'
                    getSite();	// 'baidu.com'
                    window.getSite();	// 'baidu.com'
                     
            

在上面示例中,site变量和getSite()方法没有指定上级对象,所在二者会被添加到window全局对象,所以直接访问二者与通过window访问本质相同(如,直接访问getSite()与使用window.getSite()访问一样)。

2. 局部作用域(Local Scope)

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

例如下列代码中的nickName和函数inner都只拥有局部作用域:

                    
                        function person(){
                            var nickName="掏粪男孩";
                            function inner(){
                                alert(nickName);
                            }
                            inner();
                        }
                        alert(nickName);
                        inner();
                    
                
作用域链(Scope Chain)

说完了作用域我们就可以接着来聊聊作用域链(Scope Chain)这个概念了.作用域链就是由多个作用域组成的 在JS中,函数的可以允许嵌套的。即,在一个函数的内部声明另一个函数.类似这样:

        
            function A(){
            var  a=1;
               function B(){  //在A函数内部,声明了函数B,这就是所谓的函数嵌套。
                    var b=2;
               }
            }
        
                

对于A来说,A函数在执行的时候,会创建其A函数的作用域, 那么函数B在创建的时候,会引用A的作用域,类似下面这样

函数B在执行的时候,其作用域类似于下面这样:

从上面的两幅图中可以看出,函数B在执行的时候,是会引用函数A的作用域的。所以,像这种函数作用域的嵌套就组成了所谓的函数作用域链。当在自身作用域内找不到该变量的时候,会沿着作用域链逐步向上查找,若在全局作用域内部仍找不到该变量,则会抛出异常。

3.常见问题

如何运用作用域链的知识进行性能优化?

4.解决方案

其实作用域链就是JS引擎查询数据的一个链表,后定义的覆盖先定义的,查询不到定义的数据就往深一层查询,一直到全局作用域为止 但是越往内层延伸,读写速度就会越慢,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。 如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:

                     
            function changeColor(){
                        document.getElementById("a").onclick=function(){
                        document.getElementById("b").style.backgroundColor="red";
                        document.getElementById("a").style.backgroundColor="red"; };
                        }
                     
                    

这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:

                     
                    function changeColor(){
                            var a=document.getElementById("a");
                            var b=document.getElementById("b")
                            a.onclick=function(){
                            a.style.backgroundColor="red";
                            b.style.backgroundColor="red";
                            }; }
                     
                    

这段代码比较简单,但是如果程序中有大量的全局变量被从新反复访问,那么重写后的代码性能会有显著改善。

5.编码实战

6.拓展思考

执行上下文创建过程是怎样的?

当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

虽然在定义上跟作用域还是有些差别的,执行上下文其实可以理解为作用域。

在JavaScript解释器内部,每次调用执行上下文,分为两个阶段:

  1. 创建阶段
  2. 代码执行阶段
创建阶段

当进入执行上下文时,会创建一个变量对象,所有在执行上下文里创建的变量、函数和形参等都会挂在这个变量对象上面(变量\函数提升)。

举个例子

                    
                        function foo(a) {
                          var b = 2;
                          function c() {}
                          var d = function() {};
                          b = 3;
                        }
                        foo(1);
                    
                

在进入执行上下文后,这时候的 AO 是:

                    
                        AO = {
                            arguments: {
                                0: 1,
                                length: 1
                            },
                            a: 1,
                            b: undefined,
                            c: reference to function c(){},
                            d: undefined
                        }
                    
                

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值

还是上面的例子,当代码执行完后,这时候的 AO 是:

                    
                        AO = {
                            arguments: {
                                0: 1,
                                length: 1
                            },
                            a: 1,
                            b: 3,
                            c: reference to function c(){},
                            d: reference to FunctionExpression "d"
                        }
                    
                

7.参考文献

参考:理解 JavaScript 作用域和作用域链
JavaScript深入之作用域链
了解JavaScript的执行上下文

8.更多讨论

感谢大家观看

BY : 钟楚炯