JavaScript作用域和执行上下文

7
我正在尝试理解JavaScript的作用域规则。我在教材和文档中读到的让我感到困惑。
在我看来,JavaScript是一种静态(或词法)作用域语言 - 在尝试将变量名绑定到变量(定义)时,使用代码的词法结构。
执行上下文似乎类似于调用栈上的堆栈帧。每个执行上下文都有一个变量对象,其中定义了所有本地变量(相关函数的)。这些变量对象链接在一起,从堆栈顶部的变量对象到堆栈底部的变量对象(窗口对象)提供“作用域链”。在将变量名绑定到变量时,会从上到下搜索此作用域链。这与C / C++ / Java等静态作用域语言非常相似。
有一个重要差异涉及C / C++ / Java -- 可以访问在不再存在于调用栈上的函数中定义的变量,如下面的示例所示:
var color = "red";
var printColor;

function changeColor() {
    var color = "green";

    printColor = function(msg) {
        alert(msg + color);
    }
    printColor("in changeColor context, color = ");  // "green"
}

changeColor();

// stack frame for "changeColor" no longer on stack
// but we can access the value of the variable color defined in that function

printColor("in global context, color = ");  // "green"

我理解得对吗?我还需要注意哪些问题?
提前感谢。

4
今天在 HN 上发布了一篇非常详尽的文章,涉及到这个问题:JavaScript 中的执行上下文和堆栈是什么? - Wyatt Anderson
2
这被称为闭包。你分配给printColor的函数即使在函数终止后也可以访问changeColor中定义的所有变量。我不知道C语言中是怎样的。 - Felix Kling
@FelixKling 差不多。但是 changeColor 也在全局作用域中定义,因此它的作用域永远不会被垃圾回收。 - webduvet
2个回答

3
这的确是C/C++和JavaScript之间的一个重要区别:JavaScript是一种基于引用计数和垃圾回收的语言,这意味着当对象没有任何引用时,它们可以被引擎回收。你为printColor分配的函数并不像在C或C++中那样在堆栈上,而是动态分配并分配给当前作用域外的变量。因此,当控制流从changeColor返回时,匿名函数仍然具有1个引用计数,因为外部的printColor引用它,因此它可以从外部作用域使用。

因此,你的例子并不是一个作用域问题 - 明显你在changeColor函数作用域之外声明了printColor。当你定义changeColor时,它将printColor封闭到新的函数作用域中,使其可访问。就像Combat所说,如果你在第二个内部定义的printColor中添加一个var,它将会遮蔽你声明的第一个printColor,并且在该函数块之外将无法访问。

至于其他需要注意的问题,是的,有很多,但请参见我在您原始帖子上的评论,那是一个好的开始。


唉,我可能误读了问题(关于“color”变量),但我认为我所写的仍然站得住脚。 - Wyatt Anderson
当你说printColor不在堆栈上时,我想你是指代表printColor的对象。当调用printColor时,它的执行上下文/堆栈帧被推入堆栈中。或者我理解错了吗? - asterix

0

这总归是要归结于词法作用域,即函数在定义时就会执行其作用域链,而不是在调用时。

匿名函数定义在changeColor函数的局部作用域中,而不是全局作用域。因此,当它再次执行时,它会打印出在changeColor函数的局部作用域中列出的颜色绿色。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接