请考虑以下代码:
function nepaliBuddha() {
var a = 20;
return function buddhaNepal() {
console.log(a);
}
}
var closure = nepaliBuddha();
closure(); // logs 20
现在,当我们调用closure
时,输出为20
。这证明了内部范围属性([[scope]]
)已分配给定义它的内部函数,或者在声明时说明。如果未在声明中分配,则无法记录20,因为它被调用不同的背景
调用closure()
函数上下文的作用域链是在函数调用时创建的,由当前上下文的激活对象或VO 和内部{{1}组成。这个函数的属性。
调用还会创建[[scope]]
属性,这意味着内部范围属性是在声明时创建的,也不是在执行时创建的?
通常定义说[[scope]]
属性是在运行时或函数调用时创建的,但这不是真的,因为[[scope]]
属性已在声明中分配。
我认为[[scope]]
属性在执行函数后可能会更新,是吗?请明确[[scope]]内部属性的定义。如何以及何时在申报时或执行时或两次创建。
答案 0 :(得分:8)
首先,我们将从范围开始。有两种类型的范围:
块程序在程序中发生时立即开始。另一方面,函数作用域在调用函数之前不会开始。因此,对同一函数的多次调用会导致创建多个范围。
JavaScript没有块范围。它只有功能范围。因此,要模拟块作用域,我们需要创建一个函数表达式并立即执行它。这个patttern被称为immediately invoked function expression (IIFE),它看起来像这样:
(function () {
// this is the JS equivalent of a block scope
}());
除了块范围和功能范围之外,还有另一种对范围进行分类的方法。因此我们也有:
这种区别仅适用于函数范围,因为块范围始终是词法范围的。 JavaScript只有词法范围。
要理解词法范围和动态范围之间的区别,我们需要了解自由变量和绑定变量之间的区别。
考虑以下计划:
function add(x, y) {
return x + y; // x and y are bound to add
}
在上面的程序中,变量x
和y
绑定到函数add,因为它们是在add
中声明的。
另一方面,以下程序中的变量x
和y
在函数add
中是免费的,因为它们未在add
内声明,但它们在add
:
function add() {
return x + y; // x and y are free within add
}
现在自由变量是个问题。它们需要映射到某个值,但是哪个值?这是词汇和动态范围的结果。我不会详细介绍,但你可以read about it on Wikipedia。
范围很像原型继承。当一个新的范围开始时,它继承自父范围,形成一系列范围,就像JavaScript中的原型链一样。
词法范围和动态范围因新范围继承形式的父范围而不同。
由于JavaScript只有词法范围,我们不会为动态范围而烦恼。请考虑以下程序:
var count = 0;
function incrementCount() {
return ++count;
}
(function () {
var count = 100;
alert(incrementCount()); // 1
}());
此处函数incrementCounter
有一个自由变量 - count
。由于JavaScript具有词汇范围count
,因此将映射到全局变量count
,而不是IIFE中声明的本地count
。因此,incrementCount
会返回1
而非101
。
现在,闭包仅适用于具有词法作用域的语言。请考虑以下程序:
function getCounter() {
var count = 0;
return function () {
return ++count;
};
}
var counter = getCounter();
alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3
在上面的程序中,getCounter
返回的函数是关于变量count
的闭包,因为:
count
在返回的函数中是免费的(即counter
)。count
的范围之外。这两个条件都是函数被称为闭包所必需的。有关更多信息,请阅读以下答案:https://stackoverflow.com/a/12931785/783743
现在要理解的重要一点是函数counter
仍然会被称为闭包,即使它永远不会被调用。闭包只是一个关闭变量的函数(称为闭包的upvalue)。
当我们调用getCounter
时,我们创建一个新的范围(让我们调用这个范围A
),每次我们调用getCounter
返回的函数(即counter
)我们创建了一个继承范围A
的新范围。就这样。没有创建新的闭包。
答案 1 :(得分:1)
closure
是一种特殊的对象,它结合了两个东西:一个函数,以及创建该函数的环境。该环境由关闭创建时范围内的任何局部变量组成。
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
现在致电makeFunc()
var myFunc = makeFunc();
myFunc();
在这种情况下,myFunc
是一个闭包,它包含{<1}}函数和闭包创建时存在的“Mozilla”字符串,{{3} }。
因此,我在调用displayName
和scope
(var myFunc = makeFunc();
调用的结果)时创建的myFunc()
现在是一个闭包。那么,转到第一行
makeFunc()
是一种特殊的对象,它结合了两件事: 功能,以及创建该功能的环境。
现在考虑这些
closure
这意味着,function nepaliBuddha() {
var a = 1;
return function buddhaNepal() {
a = a+1;
console.log(a);
}
}
var closure1 = nepaliBuddha(); // An individual scope
var closure2 = nepaliBuddha(); // An individual scope
closure1(); // 1
closure1(); // 2
closure2(); // 1
closure2(); // 2
closure2(); // 3
和closure1()
是封闭的,并且都有自己的个人范围/环境,一旦获得它们就可以访问自己的范围(在这种情况下,每次调用{{ 1}}您正在创建一个闭包并将其保存/保存到变量中。
范围定义了可用的函数,变量等区域。所以,当你在closure2()
(外部函数)中定义/声明函数nepaliBuddha
(内部函数)时,buddhaNepal
(内部函数)刚刚与全局作用域分离,没有别的。它不能访问全局范围内的任何东西,但它有自己的范围,就是这样。 nepaliBuddha
(外部函数)是buddhaNepal
(内部函数)的边界,在这种情况下,nepaliBuddha
外部函数的局部范围/环境是{{1}的全局范围。 (内部函数)。
在JavaScript中,这称为buddhaNepal
,它定义了如何在嵌套函数中解析变量名称。词法范围的其他名称是静态范围或关闭。这意味着内部函数的范围包含父函数的范围。