Javascript---作用域、作用域链、闭包

238 阅读6分钟

作用域与作用域链

作用域(执行环境)

概念

执行环境是Javascript中一个重要概念。在JS中,所有的变量都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。

在JS中,某一段代码中所用到的变量并不是在每个地方都可以使用的,而对于一个变量来说,它使用的有效范围就是他的作用域。

分类

作用域分为全局作用域和函数作用域。从ES6以后,JS还包括了块级作用域。

全局作用域

在代码的任何地方都可以访问到的变量的作用域就是全局作用域。

对于下列几种情况,所声明的变量都具有全局作用域:

  • 最外层函数以及最外层函数外定义的变量
  • 所有未经定义直接赋值的变量自动声明为用于全局作用域
  • 所有window对象的属性拥有全局作用域,eg:window.name,window.location....

缺点:对于全局作用域来说,当代码过多的时候,如过全都是全局作用域内声明的变量过多的话,会污染全局命名空间,且会引起起名冲突。

函数作用域

函数作用域也称为局部作用域,是指:在函数内部声明的变量所拥有的可以有效访问的区域。

对于拥有局部作用域的变量来说,他们只有在他们的局部作用域内才会被访问到。由于函数的嵌套存在,所以作用域也是分层的,内层的作用域可以访问外层作用域的变量,但是反之则不成立。

全局作用域与函数作用域

对于这部分代码而言,1所代表的为全局作用域,2、3均为局部作用域,由于他们的相互嵌套,所以3可以调用2,1中的变量,同样,2也可以调用1中的变量,但是1并不可以调用2,3中的变量。

块级作用域

在发布了ES6之后,定义了块级作用域。

块级作用域可以通过新增命令let和const声明,所生命的变量只可以在指定块的作用域内进行访问。

块级作用域被建立的条件:

  • 在一个函数内部
  • 在一个代码块内部(即一对花括号包裹的代码)

let和const的使用方法与var相同。

块级作用域特点:

  • 声明的变量不会被提升到代码块顶部 如果需要在整个代码块中都可以使用该变量,则需要手动将let/const提升到代码块的顶部。
  • 禁止重复声明 如果在同一个作用域下,使用let/const所声明的变量名已经被声明过,那么对于这个let声明就会报错。所以不可以在同一作用域下声明同一个变量

词法作用域与动态作用域

词法作用域:也称为静态作用域,是指无论函数在哪里被调用,也无论他被如何调用,他的词法作用域只由函数被声明时所处的位置决定。也就是说,在函数执行之前就已经确定了它的作用域。

动态作用域:函数的作用域是在函数调用的时候才决定的。

对于JS来说,所有的函数的作用域都是词法作用域。

作用域链

当代码在一个作用域中执行时,会创建变量对象的一个作用域链。

作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

由于函数的嵌套导致了局部作用域的存在,也使得局部作用域有了层的概念。每个局部作用域可以访问到他的外层作用域的变量以及本层作用域的变量。为了使访问变量有序,所以就产生了作用域链。

作用域链前端都是当前作用域,下一个变量为当前作用域的外层作用域,直到延伸到全局作用域。

作用域链本质上是一个指向变量对象的指针列表,他只引用但不实际包含变量对象。

对于这个代码而言,他的作用域链为:

作用域链为两个指针的集合。

小栗子:

function foo() {
    let a = 1;
    function bar() {
        let a = 2;
        function baz() {
            let a = 3;
            console.log(a);
        }
        baz();
    }
    bar();
}
foo();

对于这部分代码来说,他的作用域链分别为:

baz():baz->bar->foo->window

bar():bar->foo->window

foo():foo->window

let a = 1;
function foo(){
  let a = 2;
  function baz(){            
    console.log(a);     
  }
  bar(baz);
}
function bar(fn){
  let a = 3;    
  fn();
}
foo();

作用域链:

a:window

baz():baz->foo->window

foo():foo->window

bar():bar->window

闭包及其销毁

闭包

在JS当中,对于变量的调用只能函数内部调用函数外部的所有变量,反之不可以,但是由于代码的实现过程中,往往需要外部函数调用内部函数的变量值,所以就有了闭包的存在。

概念

能够读取其他函数内部变量的函数。

闭包就是将函数内部和函数外部连接起来的一座桥梁。

小栗子:

在f1的局部作用域中,存在了一个n以及一个指向f2的指针,在对其调用时,只能调用f1作用域内的内容,而不能调用f2作用域的内容,所以调用f1时,并不能弹出警告框。

为了在f1中可以调用f2的值,我们可以修改f1的内容,使其返回值为f2,这样就可以在f1中调用f2中的内容了。即:

由于f2的函数中调用了f1函数中的变量,并且一直在调用,所以内存并不会被销毁。 在这样一个函数中,f2即为一个闭包。

所以,可以将闭包简单理解为 定义在函数内部的函数,并且调用了外部函数变量的函数。

特点

  • 函数嵌套函数
  • 内部函数可以访问外部函数的变量
  • 参数和变量不会被回收

闭包的销毁

关于内存的销毁:

通常,函数的作用域及其所有的变量在函数执行结束后都会被销毁,以用来节省内存。

但是对于闭包来讲,当一个函数返回了一个闭包时,这个函数的作用域就会一直存在于内存中,直至闭包不存在为止。这是因为闭包一直调用了外部函数的变量,所以作用域不会及时销毁。

当不在对该函数进行调用的时候,就会对闭包进行销毁。

参考博客:

www.cnblogs.com/fundebug/p/…

blog.csdn.net/qappleh/art…

blog.csdn.net/Yzaxdm/arti…

segmentfault.com/a/119000001…

www.ruanyifeng.com/blog/2009/0…

blog.csdn.net/l344938248/…

blog.csdn.net/weixin_4358…

www.cnblogs.com/morongwenda… (好懂的闭包)

以及《Javascript高级程序设计》