一、变量提升

JavaScript 代码执行过程中,JavaScript 引擎把var变量的声明部分和函数的声明部分提升到代码开头的行为

具体表现形式:

对于变量的声明:
  1. // 使用var声明变量和赋值
  2. var name = 'gelx'

在js中,这一行给name赋值为’gelx’可以看成:

  1. var name = undefined
  2. name = 'gelx'

对于函数的声明:
  1. // 函数声明会有两种方式
  2. // 1.完整的函数声明
  3. function sayName() {
  4. console.log('name')
  5. }
  6. // 2.带变量的声明
  7. var sayName = function() {
  8. console.log('name')
  9. }

在js中对于函数的声明,会被视为:

  1. // 1.第一种情况不变,依旧为函数声明
  2. function sayName(){
  3. console.log('name')
  4. }
  5. // 2.会被视为声明、赋值两部分
  6. var sayName = undefined
  7. sayName = function() {
  8. console.log('name')
  9. }

二、执行流程

JavaScript 代码在运行时会经历两部分:编译阶段,执行阶段

2.1 编译阶段

编译阶段分为:

  • 变量提升
  • 执行部分代码

2.1.1 执行部分代码

js代码在经过编译后,会生成两部分内容:执行上下文和可执行代码

2.1.2执行上下文

执行上下文是 JavaScript 执行一段代码时的运行环境,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等

2.1.3可执行代码

当js检测到代码中存在声明赋值、函数调用时,会记录下这些代码,最终变成可执行的代码

2.2执行阶段

JavaScript开始根据可执行代码,按照代码顺序一行一行进行执行

二、调用栈

在了解调用栈前,先了解了一下js创建执行上下文的情况:

  1. 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份
  2. 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁
  3. 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文

调用栈是 JavaScript 引擎追踪函数执行的一个机制

  • 在js代码执行的时候,首先创建、执行全局上下文(压入栈中)
  • 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码
  • 如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶
  • 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈

当分配的调用栈空间被占满时,会引发“堆栈溢出”问题

三、块级作用域

3.1 作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。作用域就是变量与函数的可访问范围,作用域控制着变量和函数的可见性和生命周期

全局作用域:对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期

函数作用域:在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁

块级作用域:是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域

  1. //if 块
  2. if(1){}
  3. //while 块
  4. while(1){}
  5. // 函数块
  6. function foo(){
  7. //for 循环块
  8. for(let i = 0; i<100; i++){}
  9. // 单独一个块
  10. {}

变量提升带来的问题:

  • 变量容易在不被察觉的情况下被覆盖掉
  • 本应销毁的变量没有被销毁

3.2 ES6解决的问题

ES6通过引入letconst声明引入了块级作用域

let:声明一个属性,在块级作用域中生效,且可以随意修改参数,初始化时不需要赋值
const:声明一个属性,在块级作用域中生效,不可以修改参数,初始化时必须要赋值

在编译并创建执行上下文时,遇到letconst变量声明时,这部分的变量将会被存放在上下文中的词法变量中,与var声明的变量环境区分开

3.3 暂时性死区

  1. let name = 'gx'
  2. {
  3. console.log(name); //报错
  4. let name = 'gelx'
  5. }

使用letconst时,由于不会产生变量提升,如果在声明前使用该参数时,该属性查找不到,就会报错

四、作用域&闭包

4.1 作用域链

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文

当一段代码使用了一个变量时,JavaScript首先会在“当前的执行上下文”中查找变量,如果在当前的变量环境中没有查找到,那么 JavaScript会继续在 当前执行上下文中的外部引用 所指向的执行上下文中查找

JavaScript引擎在全局执行上下文中查找变量的链条就称为作用域链

4.2 词法作用域

词法作用域是指作用域是由代码中函数声明的位置决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系

4.3 闭包

JavaScript 中,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

4.3.1 回收闭包


  • 如果该闭包会一直使用,那么它可以作为全局变量而存在;
  • 如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

五、this

this 与执行上下文绑定,因此this也会有全局执行上下文中的 this、函数中的 this 和 eval 中的 this

5.1 全局执行上下文中的this

全局执行上下文中的 this 指向 window 对象

5.2 函数执行上下文中的this

在默认情况下调用一个函数,其执行上下文中的 this 指向 window 对象
在使用对象来调用其内部的一个方法,该方法的 this 指向对象本身

通过 new 关键字构建一个新对象,构造函数中的 this 指向就是新对象

5.2.1 new CreateObj对象的过程
  • 首先创建了一个空对象 tempObj;
  • 接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象;
  • 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象;
  • 最后返回 tempObj 对象。 ```javascript var tempObj = {}

CreateObj.call(tempObj)

return tempObj `` 可以通过callapplybind`修改this的指向

5.3 this的缺陷

  • 嵌套函数中的 this 不会从外层函数中继承
  • 普通函数中的 this 默认指向全局对象 window

弥补方案:

  • 声明一个self变量存储this,再利用变量的作用域机制传递给嵌套函数
  • 使用ES6的箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this

  • 使用严格模式,严格模式下的函数执行上下文中的 this 值是 undefined