您好,我的朋友,您非常困惑。您的第一句话就是错的:
“函数在定义时创建它们的环境(作用域),而不是在执行时创建。”
实际上恰恰相反。定义函数并不会创建作用域。调用函数才会创建作用域。
什么是作用域?
简单地说,作用域是变量的生命周期。您知道,每个变量都有出生、生存和死亡。作用域的开始标志着变量诞生的时间,作用域的结束标志着变量死亡的时间。
起初,只有一个作用域(称为程序作用域或全局作用域)。在此作用域中创建的变量只有在程序结束时才会死亡。它们被称为全局变量。
例如,请考虑以下程序:
const x = 10;
{
const x = 20;
console.log(x);
}
console.log(x);
在这里我们创建了一个名为x
的全局变量。然后我们创建了一个块级作用域,在这个块级作用域内,我们创建了一个名为x
的局部变量。由于局部变量覆盖全局变量,因此当我们记录x
时,我们得到20
。回到全局作用域时,我们记录x
,我们得到10
(局部变量x
现在已经失效)。
块级作用域和函数作用域
现在编程中有两种主要的作用域类型-块级作用域和函数作用域。
前面示例中的作用域是块级作用域。它只是一段代码块。因此得名。块级作用域会立即执行。
另一方面,函数作用域是块级作用域的模板。顾名思义,函数作用域属于一个函数。但是更准确地说,它属于一个函数调用。函数作用域在函数被调用之前是不存在的。例如:
const x = 10;
function inc(x) {
console.log(x + 1);
}
inc(3);
console.log(x);
inc(7);
你可以看到每次调用函数都会创建一个新的作用域。这就是为什么你会得到输出结果为
4
,
10
和
8
的原因。
最初,JavaScript只有函数作用域,没有块级作用域。因此,如果你想创建块级作用域,那么你必须创建一个函数并立即执行它:
const x = 10;
(function () {
const x = 20;
console.log(x);
}());
console.log(x);
这种模式被称为
立即调用函数表达式(IIFE)。当然,现在我们可以使用
const
和
let
创建块级作用域变量。
词法作用域和动态作用域
函数作用域可以再次分为两种类型——词法作用域和动态作用域。你看,在一个函数中有两种类型的变量:
- 自由变量
- 绑定变量
在作用域内声明的变量绑定到该作用域。未在作用域内声明的变量是自由的。这些自由变量属于其他作用域,但是属于哪个作用域呢?
词法作用域
在词法作用域中,自由变量必须属于父级作用域。例如:
function add(x) {
return function (y) {
return x + y;
};
}
const add10 = add(10);
console.log(add10(20));
像大多数编程语言一样,JavaScript 采用词法作用域。
动态作用域
与词法作用域相反,动态作用域中的自由变量必须属于调用范围(即调用函数的范围)。例如(这也不是 JS - 它没有动态作用域):
function add(y) { // template of a new scope, y is bound, x is free
return x + y
}
function add10(y) { // template of a new scope, bind y
var x = 10
return add(y)
}
print(add10(20))
// the x in add resolves to 10 because the x in add10 is 10
就是这样。很简单,对吧?
问题
你的第一个程序存在问题,那就是JavaScript没有动态作用域,只有词法作用域。看到错误了吗?
function f1() {
var a = 1;
f2();
}
function f2() {
return a;
}
f1();
你的第二个程序非常混乱:
function f() {
var b = "barb";
return function() {
return b;
}
}
console.log(b);
这里有一些错误:
- 你从未调用
f
。因此变量 b
从未被创建。
- 即使你调用了
f
,变量 b
也只是局部的。
这是你需要做的:
function f() {
const b = "barb";
return function() {
return b;
}
}
const x = f();
console.log(x());
当你调用
x
时,它会返回
b
。但是这并不会使
b
成为全局变量。要使
b
成为全局变量,你需要这样做:
function f() {
const b = "barb";
return function() {
return b;
}
}
const x = f();
const b = x();
console.log(b);
希望这能帮助你理解作用域和函数。
return
语句的工作方式。 - Ian