作用域
指代码当前上下文,控制着变量和函数的可见性和生命周期。一个变量的作用域是在程序源代码中定义时决定的。不同作用域下同名变量不会冲突。
作用域可以分为:全局作用域、模块作用域、函数作用域(局部作用域)、块级作用域
全局作用域
-
直接写在 Script 标签中的 js 代码都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量)
-
全局作用域在全局的任何位置下都可以使用;全局作用中无法访问到局部作用域中的变量
-
所有window对象的属性拥有全局作用域
补充:
var和function命令声明的全局变量和函数是window对象的属性和方法
let命令、const命令、class命令声明的全局变量,不属于window对象的属性
为什么let,const,class命令声明的全局变量不属于window对象属性呢?
let 命令、const 命令和 class 命令声明的变量之所以不属于window对象属性,是因为它们是使用块级作用域在不同的作用域中声明的。在 ES6 以前,JavaScript 中只有全局作用域和函数作用域,变量的作用域只能局限于函数内或作为全局变量。全局变量是指声明在全局作用域下的变量,它们会被添加到 window 对象中。但是,ES6 引入了块级作用域,可以使用 let、const 和 class 命令在不同块级作用域中声明变量,这些变量不能被添加到 window 对象中,因为它们不是全局变量,而是块级作用域中的变量。所以,这些变量不属于 window 对象的属性,也不会影响全局作用域。
以下为代码示例:
<script>
const A = 1
var c = 3
function fun () {
const b = 2
console.log(A)
console.log(b)
}
fun() // 1 2
console.log(A) // 1
console.log(b) // b is not defined
console.log(window.A) // undefined
console.log(window.c) // 3
</script>
模块作用域
早期 js 语法中没有模块的定义,随着脚本越来越复杂,模块化方案(AMD、CommonJS、UMD、ES6模块等)应运而生。通常一个模块就是一个文件或者一段脚本,而每个模块拥有其自己的作用域。
函数作用域
- 函数作用域会随着函数的执行而被创建,当其执行完毕后,其作用域会被销毁。每次执行函数都会创建一个新的作用域,其是相互独立的。
- 函数作用域内可访问全局变量,函数外无法访问函其内部变量。
- 函数作用域内操作变量时,会先在自身作用域中寻找,有就直接使用,没有就向上一作用域中寻找,直到找到全局作用域,如果全局作用域中仍然没有找到,就会报错(这个过程就是作用域链)。
可参考上面的示例进行理解
块级作用域
- 在花括号{}中的语句都属于一个块,在块中使用let和const声明的变量,外部无法访问,这种作用域规则叫块级作用域。
- 通过var声明的变量或者非严格模式下创建的函数声明没有块级作用域
function fun () {
const A = 1
{
var b = 2
const C = 3
}
console.log('A=', A)
console.log('b=', b)
console.log('C=', C)
}
fun() // A = 1; b = 2; C is not defined
// 严格模式
'use strict'; // 开启严格模式
{
function fun () {
console.log('开启严格模式')
}
}
fun() // fun is not defined
补充:严格模式
JavaScript中的“严格模式”(strict mode)是一种可选的执行模式,它为代码定义了更强的语法和错误捕获机制,并禁用了一些不安全或不推荐使用的特性。
在JavaScript中,默认情况下,代码执行时处于“非严格模式”(non-strict mode),这意味着代码在运行时会尽可能地灵活适应开发人员的写法,但也会带来一些问题,例如:
- 一些变量可能无意中被赋值给全局对象(比如window对象),这会导致变量污染或不必要的冲突。
- 一些不安全或不推荐使用的特性可能会导致代码行为出乎意料,例如with语句和eval函数。
- 一些常见错误可能不会被捕获,例如内部函数引用的变量名与外部变量重名时,代码可能出现错误但却不报错。
相比之下,严格模式则强制执行更严格的语法和错误捕获机制,以避免这些问题,并提供更好的代码质量保证。在JavaScript中,你可以通过以下代码开启严格模式:
'use strict'; // 开启严格模式
当上述语句出现在脚本文件的顶部或函数体的顶部时,该代码片段将在严格模式执行,否则将在非严格模式中执行。
作用域链指如果在当前作用域中没有查到值,就会像上级作用域查询,直到全局作用域,这样一直查找过程所形成的链条就被称之为作用域链
闭包
闭包是什么呢?它是指函数 A 内创建了函数 B,B 可以使用 A 中的变量,则 B 被称为闭包。
闭包的特征
- 闭包可以访问外部函数中的变量:内部函数可以访问在外部函数中定义的变量和参数。
- 外部函数的变量和参数保持在内存中: 由于闭包可以访问外部函数的变量和参数,当外部函数返回后,这些变量和参数不会被销毁。它们将被保存在内存中,因为闭包仍然引用它们。
- 闭包可以更新外部变量的值: 闭包可以使用在外部函数中定义的变量和参数,并更改它们的值。这种行为可以被认为是非常强大和危险的,因为它可以产生意想不到的副作用,并且使代码难以理解和调试。
- 闭包可以封装私有变量: 闭包可以用于创建“私有变量”,这些变量只能在闭包内部访问。这种行为可以使代码更加安全,因为它可以防止外部代码意外地修改闭包内部的变量。
- 闭包可以用于在函数间共享状态: 由于闭包可以访问外部函数的变量和参数,它们可以被用于在函数之间共享状态,这对于避免全局变量和更好地管理应用程序状态非常有用
闭包代码示例
function makeCounter() {
let count = 0; // 定义一个局部变量
function counter() {
return ++count; // 访问了父函数中的自由变量 count
}
return counter;
}
let counter1 = makeCounter();
console.log(counter1()); // 输出 1
console.log(counter1()); // 输出 2
let counter2 = makeCounter();
console.log(counter2()); // 输出 1
console.log(counter2()); // 输出 2