栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > JavaScript

JavaScript作用域与闭包

JavaScript 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaScript作用域与闭包

作用域

任何 Javascript 代码在执行前都有大量的工作要做,如果只靠引擎自己,其实很难做到,前面提到过引擎的一个得力助手:作用域,在整个代码的编译阶段,发挥了非常大的作用。
作用域为引擎提供了环境内每一个标识符的位置信息,引擎依赖这些信息可以迅速查找到它们定义的位置。标识符的位置信息,是你在写代码时将标识符写在哪里来决定的,在词法分析阶段( 编译中的第一个阶段 ),这些标识符的位置信息就会以有序列表的形式保存到环境的 scope 属性中( 也就是作用域 ),以供引擎使用。也可以简单理解为,作用域里面保存的信息,在你写代码的时候已经决定了,而且会一直保持这个作用域不变。

例子:

var a = 1;

function afn() {
    var b = a + 1;
    var m = 999;

    function bar(c) {
 var n = a + b + c;
 console.log(n);
    }
    bar(3);
}
afn();
//输出:6

示意图:

(1) 标识符 afn 和 a 位于全局作用域。
(2) 标识符 b、m 和 bar 位于 afn 所创建的作用域。
(3) 标识符 c 和 n 位于 bar 所创建的作用域。

我们可以通过浏览器,直观的看一下作用域的形式:

全局作用域:

afn 所创建的作用域:

bar 所创建的作用域:

作用域的查找规则

1、查找标识符的过程会始终从当前作用域开始,然后逐级地向外层嵌套的作用域展开,直到找到标识符,或抵达最外层的作用域(也就是全局作用域)为止,如果找不到标识符,通常会导致错误发生。
2、每个执行环境都可以进入到外层作用域中查找标识符,但不能进入到内层作用域中查找标识符。

示意图:

*有关于作用域基础性的介绍,可以参考我的另一篇手记:Javascript作用域与作用域链

闭包

首先,闭包是一个函数;其次,也是最重要的一点,这个函数会一直保持对定义时所处作用域的引用。
下面,我们举例说明这个定义:

例子:

function fn() {
    var a = 1;

    function childFn() {
 console.log(a);
    }
    childFn();
}
fn();
//输出:1

上例中,内部函数 childFn 可以访问外部作用域中的变量 a,我相信大家通过作用域的查找规则,可以很容易理解。这个例子可能看起来更像函数的嵌套,那么,函数 childFn 算是闭包吗?函数 childFn 是完全符合闭包的两点定义的,所以属于闭包,只是不像"标准"的闭包。下面,我们简单修改,把它变成"标准"的闭包。

例子:

function fn() {
    var a = 1;

    function childFn() {
 console.log(a);
    }
    return childFn;
}

var bar = fn();
bar();
//输出:1

在修改后的例子中,我们将内部函数 childFn 当作返回值,在 fn 执行后,其返回值(也就是内部的 childFn 函数)赋值给变量 bar 并调用 bar,bar 被正常执行。这是一个我们最常见的标准闭包。

真正理解闭包

真正理解闭包的过程,也可以说是对作用域工作原理更加深入的探索。简单回顾一下作用域:在你书写一个函数的时候,这个函数和上下文的位置关系已经确定( 除非你重写 ),当调用这个函数的时候,函数会携带这些位置信息,复制给环境中的 scope 属性。因此,无论内部函数是在定义的作用域中被调用,还是在定义的作用域以外的地方被调用,属性 scope 中保存的信息都是一样的。
我们可以通过浏览器,直观的看一下:

视图1:内部函数在定义的作用域中被调用

视图2:内部函数在定义的作用域以外的地方被调用

*通过浏览器可以发现,不论内部函数 childFn 是在定义的作用域中被调用,还是在定义的作用域以外的地方被调用,属性 scope 中保存的信息都是一样的。

作用域不会被销毁

我们知道, scope 属性中只是保存了标识符的位置信息,而不是标识符的副本,因此每一个标识符指向的变量或函数都应该是真实存在的。但是,如果内部函数作为闭包被返回,却不知道何时何地才会去执行,此时,可能它的父函数早就已经执行完毕,父函数中定义的变量或函数应该已经不复存在。但实际上,被返回的闭包函数依然可以访问到它父函数中的变量或函数。

例子:

function fn() {
    var name="Tom";

    function childFn() {
 console.log(name);
    }
    return childFn;
}

var person = fn();
person();
//输出:Tom

在 fn 执行后,我们通常会认为 fn 的作用域会被销毁,变量 name 也会被清除,但闭包阻止了这件事情的发生。之所以会这样,和 Javascript 的垃圾回收机制有很大的关系:Javascript 的垃圾收集器对于正处于环境中的标识符以及环境中的标识符引用的变量或函数( 包括变量或函数定义时的作用域 ),不会进行清除,而是仍然留在内存中。

也许,你还会有这样一个疑问,代码都是一行一行地在执行,前面声明的变量或函数,怎么判断在后面的代码中是否还在使用呢?代码的确是逐行执行,但代码的编译却不是逐行进行的,而是以 "