本文主要介绍对闭包|作用域和作用域链|执行上下文三个的理解。
1.闭包
(1)定义
闭包指有权访问另一个函数作用域中变量的函数。
(2)闭包的基本特性
函数嵌套:内部函数可以访问外部函数的作用域,包括参数和局部变量,即使外部函数已经执行完毕。
持久性:闭包的生命周期比其外部函数的生命周期更长。只要闭包存在,就可以继续访问和操作其外部函数的变量。
(3)闭包的用途
数据封装和隐私:通过闭包,可以隐藏函数的具体实现和状态,只对外提供公共的接口函数。这有助于实现数据封装和隐藏内部实现细节。
回调函数和异步编程:在JavaScript等语言中,闭包常用于处理回调函数,帮助异步编程中维持上下文(即变量作用域)的连续性。
模拟私有变量:在JavaScript等没有原生私有变量概念的语言中,闭包可以用来模拟私有变量和私有方法。
示例:
function createCounter() { let count = 0; // 外部函数的局部变量 return function() { // 内部函数,闭包 count += 1; return count; }
} const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
......
// 注意:createCounter函数已经执行完毕,内部变量count仍可以被counter函数访问和修改
在这个例子中,createCounter
函数创建了一个局部变量 count
,并返回了一个内部函数。这个内部函数就是一个闭包,因为它可以访问和修改其外部函数 createCounter
的局部变量 count
。当 createCounter
函数已经执行完毕,count
变量仍然被保存在内存中,具有持久性。
2.作用域和作用域链
(1)作用域(Scope)
作用域定义了变量和函数在代码中的可访问性。简单来说,它决定了在哪里可以访问到特定的变量或函数。在JavaScript中,主要有两种作用域:全局作用域和局部作用域(也称为函数作用域,ES6后引入了块级作用域,如let
和const
声明的变量)。let/const/var的区别及理解
- 全局作用域:在代码的任何地方都可以访问到的变量和函数拥有全局作用域。在浏览器环境中,全局作用域是
window
对象,而在Node.js中,全局作用域是global
对象。
注意:全局作用域缺点就是过多使用全局作用域变量会污染全局命名空间,容易造成命名冲突。
- 局部作用域:在函数或代码块(ES6+)内部声明的变量或函数只能在其声明的函数或代码块内部访问。
除了全局作用域和局部作用域,还有块级作用域。块级作用域指的是变量或常量在代码块(如大括号{}
包围的区域)内部定义后,其作用范围仅限于该代码块内部。
(2)作用域链(Scope Chain)
作用域链是JavaScript在查找变量时的一个过程或机制。当JavaScript需要访问一个变量时,它会从当前作用域开始查找该变量。如果当前作用域中没有找到该变量,它就会依次向上级作用域查找,直到全局作用域window对象。这个逐级查找的过程就形成了作用域链。
作用域链的作用:保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链可以访问到外层的变量和函数。
示例
var x = 'global';
function outer() { var y = 'outer'; function inner() { var z = 'inner'; console.log(x); // 查找顺序:inner -> outer -> global,找到 'global' console.log(y); // 查找顺序:inner -> outer,找到 'outer' console.log(z); // 查找顺序:inner,找到'inner'}inner(); console.log(z); // z is not defined//访问z变量在outer作用域中,会报错,因为z只在inner作用域中
}
outer();
在这个例子中,inner
函数的作用域链包括inner
自身的作用域、outer
函数的作用域以及全局作用域。当inner
函数内部尝试访问变量x
时,由于inner
和outer
作用域中都没有x
变量,所以会继续在全局作用域中查找,成功找到x
的值为'global'
。
3.执行上下文
(1) 定义
执行上下文是指函数调用时在执行栈中产生的当前函数(或全局对象,如浏览器中的window
)的执行环境。这个环境像是一个隔绝外部的容器,保管着可访问的变量、this
对象等。
JavaScript中任何代码都是在执行上下文中运行的。执行上下文为代码的执行提供了必要的环境和信息。
(2)生命周期
执行上下文的生命周期可以分为三个阶段
创建阶段:在这个阶段,JavaScript引擎会进行词法分析、语法分析,确定作用域规则,并创建变量对象。对于全局执行上下文,变量对象就是全局对象(如window
);对于函数执行上下文,变量对象包括函数参数、内部变量和函数声明等。同时,建立作用域链,确定this
的指向。
执行阶段:在这个阶段,JavaScript引擎会逐行执行代码,进行变量赋值、函数调用等操作。执行上下文会根据词法环境和变量环境来执行代码,维护执行过程中的状态,如变量的值和执行的位置。如果遇到函数调用,会创建新的函数执行上下文,并将其压入执行栈的顶部。当函数执行完毕后,相应的执行上下文会从执行栈中弹出,控制权交给当前的栈顶执行上下文。
销毁阶段:当执行上下文执行完毕后,它会被销毁,并释放相关的资源。
(3)执行上下文栈
定义:执行上下文栈是一种用于管理执行上下文的数据结构,它遵循后进先出的原则。每当JavaScript引擎开始执行一段代码时,就会创建一个新的执行上下文,并将其压入执行栈中。当前正在执行的代码始终位于栈顶,执行完毕后会弹出栈顶的执行上下文。
作用:执行上下文栈确保了代码的执行顺序和上下文环境的正确管理。它允许JavaScript引擎在多个执行上下文之间切换,并在适当的时候销毁不再需要的执行上下文。
(4) 执行上下文的类型
JavaScript中有三种执行上下文:
全局执行上下文:任何不在函数内部的都是全局执行上下文,当JavaScript程序开始运行时创建的第一个执行上下文创建一个全局的window对象,设置this
的值等于这个全局对象。
函数执行上下文:在调用函数时创建的执行上下文,它代表函数的作用域,而this
的值取决于函数的调用方式。
Eval函数执行上下文:不常用。
简单来说,执行上下文是在执行JavaScript代码前,先解析代码。解析时先创建全局执行上下文环境,把代码中即将执行的变量和函数声明都拿出来,变量先赋值为undefined,函数先声明好。完成后,才开始正式的执行程序。在一个函数执行之前也会创建一个函数执行上下文环境,不过函数执行上下文会多this、arguments、函数的参数。