学习目标
完成本单元后,您将能够:
- 确定变量在JavaScript中的范围。
- 描述如何
this
根据调用函数的位置进行更改。 - 使用闭包捕获对函数中变量的引用。
理解任何编程语言的关键在于理解变量的可用性,如何维护状态以及如何访问该状态。
在JavaScript中,变量的可用性和可见性称为范围。范围由声明变量的位置确定。
上下文是当前代码执行的状态。通过this
指针访问它。
可变范围
在JavaScript变量使用申报var
,let
或const
关键字。在何处调用关键字指示要创建的变量的范围。
了解这三者之间的区别归结为两个因素:分配可变性和支持非功能块作用域。我们在本模块的第一个单元中介绍了分配可变性。现在该讨论范围了。
范围的乐趣
声明变量或参数的代码块确定其范围。但var
不能识别非功能代码块。这意味着调用var
在一个if
块或一个环块将变量分配给最近的封闭功能的范围。此功能称为提升。
使用let
或时const
,参数或变量的范围始终是在其中声明参数的实际块。有一个经典的思维 练习可以证明这一点。
function countToThree() {
// i is in the scope of the countToThree function
for (var i = 0; i < 3; i++){
console.log(i); // iteration 1: 0
// iteration 2: 1
// iteration 3: 2
}
console.log(i); // What is this?
}
所述console.log
内部输出for
回路不足为奇,输出的值i
对于每次迭代。可能更令人惊讶的是最终console.log
声明,它输出3
。您可能已经预料到了错误,因为i
在您认为是for
循环范围之内声明了该错误。但是随着吊装,i
实际上属于countToThree
的范围。
虽然不一定很糟糕,但是如果在代码块中重新声明了变量,则吊装通常会被误解,并可能导致变量泄漏或导致意外覆盖。为了解决这些误解let
,将const
其添加到语言中以创建具有块级作用域的变量。让我们重新思考一下。
for (let j = 0; j < 3; j++){
console.log(j); // 0
// 1
// 2
}
console.log(j); // error
通过替换let
为var
,我们现在有了一个仅在for
循环上下文中存在的变量。在循环关闭后尝试访问它会给我们一个错误。
上下文和这个
正如我们探索的那样,JavaScript围绕对象。对象是跟踪状态的地方。调用一个函数时,该函数周围始终有一个对象容器。该对象容器是其上下文,this
关键字指向该上下文。因此,在声明函数时不会设置上下文,而是在调用函数时设置上下文。
因为功能可以在对象之间传递,所以this
指向的内容可以更改。
例如说这个JavaScript 对象。
var obj = {
aValue: 0,
increment: function(incrementBy) {
this.aValue = this.aValue + incrementBy;
}
}
如果然后访问增量功能,它将按预期工作。
obj.increment(2);
console.log(obj.aValue); // 2
但是,让我们将该函数分配给另一个变量,看看它是如何工作的。
//assign function to variable
var newIncrement = obj.increment;
//now invoke through the new pointer
newIncrement(2);
console.log(obj.aValue); // still 2 not 4
通过将变量分配给newIncrement
,现在可以在其他上下文中执行该功能。具体而言,在这种情况下,在全局范围内。
注意
的Function.apply()
,Function.call()
和Function.bind()
函数提供的方式来调用函数,同时明确其绑定到不同的对象上下文。
全局对象
当执行JavaScript而没有以开发人员身份编写的任何包含对象时,它将在全局对象中运行。因此,据称在此调用的函数正在全局上下文中运行,这意味着访问this
将指向该全局上下文。
在浏览器中,全局上下文是window
对象。您可以通过在浏览器开发人员工具中运行以下命令来轻松测试该功能。
this === window; // true
在该increment
示例中,将increment
函数分配给newIncrement
变量会将调用它的上下文移动到全局对象。这很容易证明。
console.log(this.aValue); // NaN
console.log(window.aValue); // NaN
console.log(typeof window.aValue); // number
当我们尝试this.aValue
使用新的上下文进行分配时,JavaScript对象的可变性开始发挥作用。新的未初始化aValue
属性已添加到中this
。对未初始化的变量执行数学运算将失败,因此该NaN
值也会失败。但是我们可以看到aValue
存在于window
,的确是一个数字。
与对象的上下文
在此increment
示例中,只要increment
使用obj
点符号来调用函数,就this
指向obj
。或者,通常来说,当调用函数作为object.function()
点左侧的事物时,始终是调用该函数的上下文。
想想这个Bike
例子。的Bike
构造限定了与几个属性this
参考。它还具有分配给其引用该原型的功能this
。
const Bike = function(frontIndex, rearIndex){
this.frontGearIndex = frontIndex || 0;
this.rearGearIndex = rearIndex || 0;
...
}
...
Bike.prototype.calculateGearRatio = function(){
let front = this.transmission.frontGearTeeth[this.frontGearIndex],
rear = this.transmission.rearGearTeeth[this.rearGearIndex];
if (front && rear) {
return (front / rear) ;
} else {
return 0;
}
};
然后Bike
,我们使用new
关键字进行调用。
const bike = new Bike(1,2);
console.log(bike.frontGearIndex); // 1
console.log(bike.rearGearIndex); // 2
看起来我们正在Bike
全局上下文中调用构造函数。但是,new
关键字将上下文(和this
指针)移动到分配左侧的新对象。
当我们调用任何函数时,它们现在是bike
对象的成员,因此它们将其用作包含上下文。
let gearRatio = bike.calculateGearRatio();
console.log(gearRatio); // 3
以错误的方式调用构造函数很容易。在这里事情会崩溃。
const badBike = Bike(1,2);
console.log(badBike.frontGearIndex); // error
console.log(window.frontGearIndex); // 1
当您忘记使用时new
,Bike
将像其他任何函数一样调用,并且this
从window
到新创建的对象的关键转换失败。frontGearIndex
引入对象可变性,并添加属性window
。
注意
class
JavaScript中的语法会强制您使用new
关键字调用构造函数,因此您不会误导上下文。
关闭
声明函数时,它将保留对其中声明的任何变量或参数的引用,以及对其所包含范围内引用的任何变量的引用。它的变量和自变量以及其包含范围中的局部变量和自变量的这种组合称为闭包。
考虑此 函数及其返回的函数。
const greetingMaker = function(greeting){
return function(whoGreeting){
return greeting + ", " + whoGreeting + "!";
}
}
const greetingHello = greetingMaker("Hello");
const greetingBonjour = greetingMaker("Bonjour");
const greetingCiao = greetingMaker("Ciao");
console.log(greetingHello("Gemma")); // Hello, Gemma!
console.log(greetingBonjour("Fabien")); // Bonjour, Fabien!
console.log(greetingCiao("Emanuela")); // Ciao, Emanuela!
当greetingMaker
被调用时,我们通常可以想象它的greeting
参数仅在被调用的整个生命周期内持续存在。
但是返回的函数会greeting
在greetingMaker
的范围内保留对参数的引用。这样,最后通过greetingHello
/ Bonjour
/ 调用它时Ciao
,它仍然可以访问。
掌握闭包也是理解和使用该语言的重要组成部分。