# 2.浏览器中的JavaScript执行机制

## 07｜变量提升：JavaScript代码是按顺序执行的吗？

> 本节主要讲解`执行上下文`相关的内容。
>
> 通过一些代码的执行顺序与经验我们知道：
>
> * 在执行过程中，若使用了未声明的变量，那么 JavaScript 执行会报错。   &#x20;
> * 在一个变量定义之前使用它，不会出错，但是该变量的值会为 undefined，而不是定义时的值。   &#x20;
> * 在一个函数定义之前使用它，不会出错，且函数能正确执行。
>
>   **变量提升**
>
>   所谓的变量提升，是指在 JavaScript 代码执行过程中，JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后，会给变量设置默认值，这个默认值就是我们熟悉的 undefined.   &#x20;
>
>   之所以会发生变量提升，是因为一段JavaScript代码在执行之前，需要被JavaScript引擎编译，编译完成之后，才会进入执行阶段。也就是说在编译阶段，变量和函数的声明提升到了开头。

## 08 ｜调用栈：为什么JavaScript代码会出现栈溢出？

> 一般有三种情况，当一段代码执行的时候JS引擎对其进行编译并创建执行上下文： 1. 当 JavaScript 执行全局代码的时候，会编译全局代码并创建全局执行上下文，而且在整个页面的生存周期内，全局执行上下文只有一份.\
> 2\. 当调用一个函数的时候，函数体内的代码会被编译，并创建函数执行上下文，一般情况下，函数执行结束之后，创建的函数执行上下文会被销毁。\
> 3\. 当使用 eval 函数的时候，eval 的代码也会被编译，并创建执行上下文。
>
> ### 小结
>
> * 每调用一个函数，JavaScript 引擎会为其创建执行上下文，并把该执行上下文压入调用栈，然后 JavaScript 引擎开始执行函数代码。&#x20;
> * 如果在一个函数 A 中调用了另外一个函数 B，那么 JavaScript 引擎会为 B 函数创建执行上下文，并将 B 函数的执行上下文压入栈顶。  &#x20;
> * 当前函数执行完毕后，JavaScript 引擎会将该函数的执行上下文弹出栈。  &#x20;
> * 当分配的调用栈空间被占满时，会引发“堆栈溢出”问题。

## 09 | 块级作用域：var缺陷以及为什么要引入let和const

### 作用域

> 作用域是指在程序中定义变量的区域，该位置决定了变量的生命周期。通俗地理解，作用域就是变量与函数的可访问范围，即作用域控制着变量和函数的可见性和生命周期。\
> ES6出现之前，JS的作用域只有两种：`全局作用域`和`函数作用域`。 ES6出现，引入了`块级作用域`。

### 在同一段代码中，ES6 是如何做到既要支持变量提升的特性，又要支持块级作用域的呢？

> 当一段代码里面既有var声明的变量也有let声明的变量的时候：
>
> * 函数内部通过var声明的变量，在编译阶段全都被存放到`变量环境`里面. &#x20;
> * 通过let声明的变量，在编译阶段会被存放到`词法环境`中。&#x20;
> * 在函数作用域内部，通过let声明的变量并没有被存放到词法环境中。 &#x20;
>
>   也就是说：通过理解词法环境的结构和工作机制，块级作用域是通过词法环境的栈结构来实现的，而变量提升是通过变量环境来实现的，通过两者的结合，JavaScript引擎也就同时支持了变量 提升和块级作用域了。

## 10 ｜ 作用域和闭包：代码中出现相同的变量，JavaScript引擎是如何选择的

### 作用域链

> 理解作用域链是理解闭包的基础，而闭包在JavaScript中无处不在，同时作用域和作用域链还是作用语言的基础，所以我们先来学习一下`作用域链`。 理解了调用栈、执行上下文、词法环境、变量环境等概念，那么你理解起来作用域链也会很容易,看下面一段代码：
>
> ```
> function bar() {
>    console.log(myName)
> }
> function foo() {
>    var myName = "局部变量"
>    bar()
> }
> var myName = "全局变量"
> foo()
> ```
>
> 通过上面的代码，我们知道最终打印出来的结果是：”全局变量“。\
> 这是因为，当一段代码使用了一个变量后，JavaScript引擎会首先在“当前的执行上下文”中去查找该变量。若没有找到，由于每个执行上下文都包含一个外部引用指向外部执行上下文，所以bar函数中的变量会去全局上下文中区域查找。我们把这个查找的链条就称为作用域链。
>
> ### 词法作用域
>
> foo 函数调用的 bar 函数，那为什么 bar 函数的外部引用是全局执行上下文，而不是 foo 函数的执行上下文？了解这个问题我们继续来学习词法作用域：\
> 词法作用域就是指作用域是由代码中函数声明的位置来决定的，所以词法作用域是静态的作用域，通过它就能够预测代码在执行过程中如何查找标识符。\
> 然后，根据词法作用域，foo 和 bar 的上级作用域都是全局作用域，所以如果 foo 或者 bar 函数使用了一个它们没有定义的变量，那么它们会到全局作用域去查找。也就是说，词法作用域是代码阶段就决定好的，和函数是怎么调用的没有关系。
>
> ### 闭包
>
> 在 JavaScript 中，根据词法作用域的规则，内部函数总是可以访问其外部函数中声明的变量，当通过调用一个外部函数返回一个内部函数后，即使该外部函数已经执行结束了，但是内部函数引用外部函数的变量依然保存在内存中，我们就把这些变量的集合称为闭包。比如外部函数是 foo，那么这些变量的集合就称为 foo 函数的闭包。 在使用闭包的时候，要尽量注意一个原则：如果该闭包会一直使用，那么它可以作为全局变量而存在；但如果使用频率不高，而且占用内存又比较大的话，那就尽量让它成为一个局部变量。

## 11 ｜ this：从JavaScript执行上下文的视角讲清楚this

> 首先我们要知道，在对象内部的方法中使用对象内部的属性是一个非常普遍的需求，但是JavaScript作用域机制并不支持这一点，基于这个需求，JavaScript搞出了一套this机制。
>
> 在前几节中，我们提到执行上下文中包含了：`变量环境`、`词法环境`、`外部环境`、还有一个没有提及的`this`,this是和执行上下文绑定的，每个执行上下文都有一个this。\
> 在08节我们总结了执行上下文主要分三种：全局执行上下文、函数执行上下文和eval执行上下文。\
> 对应的this也只有这三种：全局执行上下文中的this、函数执行上下中的this和eval中的this(不做讨论)。
>
> * 全局执行上下文中的this：全局执行上下文中的this指向window对象。 &#x20;
> * 函数执行上下文中的this：
>   1. 默认情况下调用一个函数，其执行上下文中的 this 也是指向 window 对象的. &#x20;
>   2. 通过函数的call方法设置其this指向其他对象（还可以使用bind和apply方法来设置函数执行上下文中的this）。  &#x20;
>   3. 通过对象调用方法设置。（使用对象来调用其内部的一个方法，该方法的 this 是指向对象本身的。在全局环境中调用一个函数，函数内部的this指向的是全局变量window）。  &#x20;
>   4. 通过构造函数中设置。  &#x20;
>
>      **this的设计缺陷以及应对方案**
> * 嵌套函数的this不会从外层函数中继承。==>  1⃣️、将this保存一个self变量，利用变量作用域机制传递给嵌套函数。2⃣️、将乔套函数改为箭头函数。  &#x20;
> * 普通函数中的this默认指向全局对象window。==>可以通过设置JavaScript的“严格模式”来解决。
