date
Jan 4, 2024 07:37 AM
type
status
slug
summary
tags
category
updated
Oct 24, 2024 12:52 PM
icon
password
作用域是什么
作用域可以被视为一个存储变量和其他标识符的容器,当程序需要查找这些变量或标识符时,它会根据一些规则在作用域中进行查找。例如,在下面的代码中:
词法作用域
词法作用域也被称为静态作用域,是在编写代码或者说定义函数时确定的。例如,在下面的代码中:
函数
foo
在定义时(词法地)就已经确定其所在的作用域,所以当它在函数bar
内部被调用时,它依然可以访问到全局的变量x,结果是10而非20。函数作用域和块作用域
在JavaScript中,每个函数都有其自己的作用域,而块作用域则是由花括号({})包围的任何地方。函数作用域和块作用域是在JavaScript中定义变量的两种方式。他们主要的区别在于它们的作用范围,即变量在何处可以被访问:
函数作用域:在函数内声明的所有变量,都只能在函数内部被访问,而在函数外部则无法访问。也就是说,当你在一个函数中声明一个变量,那个变量只有在该函数里边才可以被访问,函数之外就不认识这个变量了。
例如:
在这里,
greeting
变量就被限制在sayHello
函数作用域内。块作用域:块作用域是指在一对花括号(
{}
)中声明的所有变量。这通常出现在诸如if
语句、for
和while
循环以及其他的代码块中。这是由于ES6中引入的let
和const
关键字的出现,使得JavaScript拥有了块级作用域。例如:
在这里,
blockScoped
变量具有块作用域,它的生命周期仅限于花括号里的这个块级代码块。所以主要的区别在于,函数作用域是在函数内部,而块作用域则是在一对花括号(
{}
)之间。另一个重要的区别是,函数作用域对于变量来说有var
,let
和const
,而块级作用域只有let
和const
。提升
JavaScript会将函数和变量的声明在编译阶段会被移动到他们各自的作用域的最顶部。但是,只有声明被提升,初始化(如果有的话)则不会被提升。例如:
变量myVar的声明被提升了,但初始化被留在原地,所以第一行代码输出
undefined
。另外,函数的提升表现得比变量的提升更加特殊一些。编译阶段,如果遇到了函数声明,JavaScript会把整个函数搬到作用域顶部。注意,这里指的是函数声明,而不是函数表达式。
以下是一个函数提升的例子:
你可以看到,尽管函数
sayHello()
的调用出现在函数的声明之前,但依然可以正常运行,这就是函数提升的体现。但对于函数表达式,如下方代码所示:
您可以看到在这个例子中,
sayHello
是未定义的,这是因为函数表达式并不会被提升,只有变量的声明会被提升,变量的赋值则不会提升。作用域闭包
闭包是一个函数与其周围状态(词法环境)的引用捆绑在一起的组合。简单来说,一个闭包就是有权访问另一个函数作用域中的变量的函数。例如:
下面这个例子,函数
innerFunction
访问了它的外部函数outerFunction
的变量outerVariable
,即使outerFunction
函数的执行环境已经消失,但是innerFunction
仍然可以访问到变量outerVariable
,这就是闭包。这里多说一句,正常函数或者说当前的代码执行完之后就会被垃圾回收,但是闭包将会保存它所在的词法作用域的引用。这就是为什么,即使它的外部函数已经执行完毕,我们仍然可以在闭包中访问到外部函数中的变量。这是因为这些变量和函数还保存在闭包的词法环境中。要注意的是,只有当闭包本身也不再被任何代码引用(换句话说,不能再被访问)的时候,才会被 JavaScript 的垃圾回收机制清理掉,包括它所引用的词法作用域也会一并被清理。所以,当我们说“外部函数的执行环境消失”,实际上是指外部函数执行完毕,控制权已经离开了该函数的作用域。但由于闭包的存在,这些作用域中的变量无法立即被回收,它们仍然存在于内存中,直到闭包本身无法被访问。
模块
由上面的作用域,闭包和词法作用域这些概念而由此产生了一种代码编写方式
模块
。实际上模块并没有创造什么新的东西,而是一个代码编写的方式,是闭包的一种写法。让我们来谈谈模块。模块是一种结构,它能使你将代码分解成互相独立但却能互相协作的单元。这样做的优点之一就是你可以更好地复用和共享代码。这样的模式其实在JavaScript中一直存在,但在ES6(也被称为ES2015)引入了基于语法的模块系统之前,大家都是通过一些库或是一些特殊的设计模式来实现的。
模块模式必须具备以下2个条件
1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
下面的代码就是一个模块模式:
现代的模块机制
在JavaScript的基础模块系统中,你可以使用
export
关键字来输出你想要让别人使用的东西(如函数,对象,值等),然后再在另一个文件里使用import
关键字来引入它们。我们来看一个例子,假设我们有一个
greet.js
文件:然后我们在
main.js
文件中引入和使用它:这就是如何创建和使用模块的一个基本示例。而事实上,模块是闭包的一个运用,它们实际上是使用了闭包来封装和保护代码,以此来控制哪些代码可以被外部访问,哪些则一个保持私有,通过
export
关键字明确规定了哪些是对外公开的接口。这能保护你的代码不受干扰,也使得代码更加容易理解和管理。