阅读 52

javascript作用域链及闭包原理

一:什么是作用域链

要理解作用域链,需要先清楚下面两个概念:

  • 执行环境
  • 变量对象

其中执行环境是javascript中最为重要的一个概念。在《javascript高级程序设计》中对于它们的解释如下:

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
每个执行环境都有一个与之关联的变量对象(variable object)。
环境中定义的所有变量和函数都保存在这个对象中。
复制代码

顾名思义,执行环境是一系列变量、函数及其行为规则的集合,变量对象则是执行环境的介质。全局执行环境是最外围的执行环境,每个函数都有自己的执行环境。当某个执行环境的所有代码执行完毕后,该环境被销毁,在该执行环境中保存和定义的所有变量和函数也随之销毁,全局执行函数直到应用程序退出才被销毁。

这一部分内容刚开始接触可能还是比较抽象的,哈哈,反正我是重复了很多遍才记忆深刻。推荐一种记忆方法吧,可以在熟记几次之后试着讲给别人听,在给别人讲解的过程中自己也会成长很快。

下面再来看一下作用域链吧。

当执行流进入一个函数时,这个函数的执行环境就被推入环境栈中,函数执行完毕后再从环境栈中弹出。这里不难看出,环境栈最上层即为当前执行代码所在的执行环境,最下层则为全局执行环境。而这一系列环境栈所对应的变量对象集合即为作用域链。作用域链保证了对执行环境有权访问的所有变量和函数的有序访问。

由此可见,作用域链的成员其实是各个执行环境的变量对象。作用域链最前端即为当前执行环境的变量对象,如果当前执行环境是函数,则将其活动对象作为变量对象。作用域链的最末端即为全局执行环境的变量对象。

在作用域链中,内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。这些环境的联系是线性的有次序的。不难想到,作用域链就是一个单向链表。

值得注意的一点是,在javascript中函数的参数也被当做变量来处理,其访问规则与执行环境中的其它变量相同。并且函数的参数是按值传递的,即使在函数内部修改了参数的值,其原始的引用仍然保持不变。

二:闭包

闭包即可以访问其他作用域变量的函数。首先,闭包是一个函数;其次,它可以访问到其它作用域内的变量。在js中,闭包的实现一般为在一个函数内定义一个函数。举个栗子:

function outer() {
    var a = 1
    var inner = function() {
        a++;
        console.log(a)
    }
    return inner 
}
var get = outer(); 
get();  //2
get();  //3
复制代码

inner函数内定义了一个函数inner并作为返回值返回,此时inner函数即为一个闭包。不妨这样理解,外层函数的作用域即为外层函数内变量的集合,当在该作用域中又定义了一个函数时,内层函数作为变量也包含在外层作用域中,但同时它又有自己的作用域,javascript作用域链的特性使得它可以访问到上层函数作用域内的变量。当内层函数作为返回值时,相当于返回了整个内层作用域,包括其引用过的上层函数作用域内的变量(这一段好像有点绕2333)。

var get = outer();其实是创建了一份outer函数的执行环境,返回了内层函数。但是由于该内层函数作为返回值赋给了get变量,所以在get变量销毁之前,该内层函数执行环境始终存在,并且其作用域内的变量也由于被引用而不被销毁。这也解释了为什么闭包使得函数拥有私有变量成为了可能。

不妨看一下下面这段代码:

function outer() {
    var a = 1
    console.log("外层函数会执行吗?", a)
    var inner = function() {
        a++;
        console.log(a)
    }
    return inner 
}
var get = outer(); //外层函数会执行吗 1 
get(); //2
get(); //3
复制代码

结果很明显,只有执行outer函数的时候输出了外层函数会执行吗 1,再执行get时相当于只在执行inner,但是变量a却被保留了下来。

事实上,通过适当使用闭包,我们的代码可以变得更优雅,更简洁,在模拟面向对象的代码风格上用处十分广泛,也为模块化开发提供了强有力的支持。但是由于变量一直保存在内存中,所以对性能的消耗也很明显,在使用的时候要注意权衡利弊。

关注下面的标签,发现更多相似文章
评论