Javascript的作用域、作用域链以及闭包详解
什么是作用域?
作用域是指代码中定义变量的区域,也是访问这些变量的规则。在Javascript中常见的作用域有全局作用域和函数作用域。
全局作用域
全局作用域是指定义在最外层的变量,在整个程序执行过程中都可以访问到。例如下面的代码:
var name = "Lucy";
function getName() {
console.log(name);
}
getName(); // 输出 "Lucy"
name
变量是在全局作用域中定义的。在 getName()
函数中,我们可以直接访问到 name
变量,并将其输出。
函数作用域
函数作用域是指在函数内部定义的变量,只能在函数内部被访问。例如下面的代码:
function sayHello() {
var message = "Hello, world!";
console.log(message);
}
sayHello(); // 输出 "Hello, world!"
console.log(message); // 报错,message未定义
message
变量是在 sayHello()
函数内部定义的。在函数外部访问 message
变量会报错,因为它只存在于函数作用域中。
作用域链
当Javascript代码运行时,变量的查找会按照作用域链的顺序进行。作用域链是多个作用域的集合,用于查找变量。
作用域链的顺序
在Javascript中,当代码运行到某个作用域时,会按照以下顺序查找变量:
- 查找当前作用域下是否有该变量。如果当前作用域中没有该变量则继续往外查找。
- 查找上一层作用域下是否有该变量。如果上一层作用域中没有该变量则继续往外查找。
- 直到找到全局作用域,如果全局作用域中还没有该变量,则返回 undefined。
例如下面的代码:
var name = "Lucy";
function getName() {
var message = "My name is " + name;
console.log(message);
}
getName(); // 输出 "My name is Lucy"
在 getName()
函数中,我们访问了全局作用域中的 name
变量。当查找 name
变量时,会首先在函数作用域中查找,发现没有该变量,则往上一层作用域(即全局作用域)查找,最终找到了 name
变量。
作用域链的生命周期
当函数执行完成时,其作用域链会被销毁,其中的变量也会随之被销毁。例如下面的代码:
function sayHello() {
var name = "Lucy";
console.log("Hello, " + name + "!");
}
sayHello(); // 输出 "Hello, Lucy!"
console.log(name); // 报错,name未定义
在 sayHello()
函数中,我们定义了 name
变量,当函数执行完成后,该变量会被销毁。在函数外部访问 name
变量会报错。
什么是闭包?
闭包是指在函数内部创建一个新的作用域,使得函数内部的变量可以被函数外部访问。通过闭包,我们可以实现变量的隐藏和封装,同时增强函数的灵活性。
创建闭包
在Javascript中,创建闭包有两种方式:
1. 返回函数
通过在函数内部定义一个新的函数,并返回该函数,从而使用闭包。例如下面的代码:
function createCounter() {
var count = 0;
function counter() {
count++;
console.log(count);
}
return counter;
}
var counterA = createCounter(); // counterA 是一个闭包
counterA(); // 输出 1
counterA(); // 输出 2
var counterB = createCounter(); // counterB 是另一个闭包
counterB(); // 输出 1
在 createCounter()
函数中,我们定义了 count
变量和 counter()
函数,并返回了 counter()
函数。当我们调用 createCounter()
函数时,实际上是在创建一个闭包。闭包中包含了 count
变量及其对应的值,以及 counter()
函数的引用。
2. 函数内声明函数
通过在函数内部声明一个新的函数,从而使用闭包。例如下面的代码:
function sayHello(name) {
function hello() {
console.log("Hello, " + name + "!");
}
hello();
}
sayHello("Lucy"); // 输出 "Hello, Lucy!"
在 sayHello()
函数中,我们定义了 hello()
函数,并在函数内部执行了 hello()
函数。由于 hello()
函数是在 sayHello()
函数内部定义的,因此可以访问 sayHello()
函数中的 name
参数。
示例说明
示例1:作用域链查询变量
var name = "Lucy";
function hehe() {
var name = "Jack";
function haha() {
console.log(name);
}
haha();
}
hehe(); // 输出 "Jack"
在 hehe()
函数中,定义了 name
变量并赋值 "Jack"。在 haha()
函数中,访问了 name
变量。由于 name
变量在函数作用域中已经定义,因此查找时会先在函数作用域中查找,找到了name
变量的值"Jack"。
示例2:闭包应用
function createFullName(firstName) {
return function(lastName) {
console.log(firstName + " " + lastName);
};
}
var fullNameA = createFullName("Lucy"); // fullNameA 是一个闭包,firstName = "Lucy"
fullNameA("Smith"); // 输出 "Lucy Smith"
fullNameA("Brown"); // 输出 "Lucy Brown"
var fullNameB = createFullName("Jack"); // fullNameB 是另一个闭包,firstName = "Jack"
fullNameB("Johnson"); // 输出 "Jack Johnson"
在 createFullName()
函数中,我们返回了一个匿名函数,并使用了参数 firstName
。当我们调用 createFullName()
函数时,实际上是在创建一个闭包。闭包中包含了 firstName
参数及其对应的值,以及匿名函数的引用。
当我们调用 fullNameA("Smith")
时,实际上是在执行闭包中的匿名函数,并传入了参数 lastName = "Smith"
。在匿名函数中,会将 firstName
和 lastName
拼接起来,并输出结果。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Javascript的作用域、作用域链以及闭包详解 - Python技术站