-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1213a5b
commit ca6a4ae
Showing
5 changed files
with
304 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# 作用域&上下文 | ||
|
||
你一定需要掌握的面试输出题分析方法 | ||
|
||
不讲历史原因,纯讲怎么分析 | ||
|
||
在文章开始前必须要知道作用域和上下文的区别 | ||
|
||
> 作用域(Scope)指的是在程序中定义变量的区域,它决定了变量的可见性和生命周期。作用域规定了在代码中访问变量的规则。在 JavaScript 中,主要有全局作用域和函数作用域。全局作用域中的变量可以在整个程序中访问,而函数作用域中的变量则只能在函数内部访问。作用域链用于在嵌套的作用域中查找变量。 | ||
> | ||
> 上下文(Context)指的是代码执行时所处的环境,它包含了当前代码的执行状态和相关信息。在 JavaScript 中,主要有全局上下文和函数上下文。全局上下文是在程序启动时创建的,而函数上下文是在函数调用时创建的。每个上下文都有自己的变量对象(Variable Object)用于存储变量、函数和其他相关信息。 | ||
> | ||
> 作用域和上下文之间的关系是这样的:作用域决定了变量的可见性和访问规则,而上下文提供了执行代码所需的环境和状态。作用域规定了在代码中如何查找变量,而上下文则提供了变量的具体值和其他执行时的信息。 | ||
> | ||
> 简单来说,作用域关注的是变量的可见性和访问规则,而上下文关注的是代码执行时的环境和状态。 | ||
<br> | ||
|
||
在每个函数**开始之初**,执行上下文中会创建**变量环境**和**词法环境**,并将当前函数执行上下文推入执行上下文栈中,当函数执行结束后会让执行上下文栈出栈 | ||
|
||
- 变量环境: | ||
- 环境记录:包含`var`声明和`function`声明的变量 | ||
- 外部环境引用 | ||
- 词法环境: | ||
- 环境记录:包含`let`和`const`声明的变量 | ||
- 外部环境引用 | ||
|
||
<br> | ||
|
||
!!!核心要点: | ||
|
||
**var和function声明的变量会存入最近的变量环境**,**而const和let声明的变量会存入最近的词法环境** | ||
|
||
<br> | ||
|
||
一个上下文中的变量会有三个状态 | ||
|
||
1. 声明(Declaration):当在代码中使用 `var`、`let`、`const` 或函数声明时,会进行变量的声明。在这个阶段,变量的标识符被引入到当前作用域中,但尚未分配内存空间,也没有初始化值。 | ||
2. 创建(Creation):在进入变量的作用域时,会创建该变量的内存空间。这个阶段被称为变量的创建阶段。在创建阶段,变量会被分配内存空间,并绑定到相应的作用域中。 | ||
3. 赋值(Assignment):在变量创建后,可以对变量进行赋值操作。赋值阶段是给变量分配一个具体的值或引用的过程。变量的赋值可以在声明的同时进行,也可以在稍后的代码中进行。 | ||
|
||
对于 `var` 声明的变量,会在作用域的顶部进行提升(hoisting),即在变量的作用域开始之前就进行了创建。而对于 `let` 和 `const` 声明的变量,则是在块级作用域中实际声明的位置进行创建。 | ||
|
||
<br> | ||
|
||
作用域总共分为三种: | ||
|
||
- 全局作用域 | ||
|
||
- 函数作用域 | ||
|
||
- 块级作用域 | ||
|
||
全局作用域和函数作用域都是老生常谈的问题了,除此之外的由`{}`包裹的就是块级作用域。 | ||
|
||
- 当程序执行到函数时会创建函数执行上下文,然后会创建变量环境和词法环境 | ||
|
||
- 但当程序在函数中执行到块级作用域时,不会创建新的上下文,只会创建一个新的词法环境 | ||
|
||
该词法环境的环境记录会存储块级作用域中由`const`和`var`声明的变量,`outer`指向当前函数执行上下文的词法环境,当块级作用域部分执行结束后,词法环境会被销毁 | ||
|
||
举例如下: | ||
|
||
```ts | ||
const name = "luowei"; | ||
|
||
function test1(){ | ||
console.log(name); // luowei | ||
} | ||
|
||
function test2(){ | ||
const name = "passionfruit" | ||
test1(); | ||
} | ||
|
||
function main() { | ||
console.log(a); // undefined | ||
console.log(b); // undefined | ||
{ | ||
const test1 = 2; | ||
var a = 1 | ||
function b() { | ||
test2(); | ||
} | ||
console.log(test1); // 2 | ||
} | ||
b(); | ||
} | ||
``` | ||
|
||
我们逐步分析函数执行过程: | ||
|
||
调用`main`函数(左侧为`main`函数执行过程,右侧为执行上下文栈) | ||
|
||
![1.7](/images/1.7.png) | ||
|
||
变量查找时,从自身的环境记录开始沿着`outer`查找,这就是**作用域链** | ||
|
||
重点讲一下为什么`test2`函数调用`test1`时,`name`值为`"luowei"`,或者说为什么`test1`的执行上下文中词法环境的`outer`指向全局执行上下文的词法环境 | ||
|
||
这里我们要说一个新名词叫**词法作用域** | ||
|
||
词法作用域就是指作用域是由代码中的函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符 | ||
|
||
<img src="/images/1.8.png" alt="1.8" style="zoom:50%;" /> | ||
|
||
所以,词法作用域是编译阶段就决定好的,和函数怎么调用的没有一点关系 | ||
|
||
<br> | ||
|
||
那闭包又是什么? | ||
|
||
我相信前面看完这个问题也很清晰了,闭包产生后函数的执行上下文就没有出栈,如果闭包返回了了一个函数,这个函数执行上下文中的词法环境和变量环境的`outer`指向声明时的函数上下文中词法环境和变量环境 | ||
|
||
> 所有的函数在“诞生”时都会记住创建它们的词法环境。从技术上讲,这里没有什么魔法:所有函数都有名为 `[[Environment]]` 的隐藏属性,该属性保存了对创建该函数的词法环境的引用。 ——《现代JavaScript教程》 | ||
<br> | ||
|
||
参考文章: | ||
|
||
[现代JavaScript教程](https://zh.javascript.info/closure) | ||
|
||
浏览器工作原理与实践[李兵]——极客时间 |
Oops, something went wrong.