几句话理解javascript中的执行上下文、this、作用域、闭包

这几个问题是javascript问题中的老大难的几个问题,很多文章都有讲过这几个问题,但总也讲不清楚,希望可以尝试用最简单明了的语言把这几个事儿给说清楚了,部分内容并不是官方的定义,而是用简单易懂的语言表达出我所理解的概念。

概念

执行上下文

首先明确一点,所有的js代码都是在某个执行上下文中运行的。

执行上下文可以看成以下对象:

executionContextObj = {
    scopeChain: { /* 作用域链:变量对象+ 所有父执行上下文的变量对象*/ },
    variableObject: { /*变量对象:函数 arguments/参数,内部变量和函数声明 */ },
    this: { /*运行这个函数的对象(动态的) */ }
}

js解释器实现了一个执行上下文堆栈,并且总是在栈顶的执行上下文中执行代码。

当js解释器初始化执行代码时,它首先默认压入一个全局执行上下文到栈中,在此基础上任何一次函数的调用都将压入一个新的执行上下文到栈中,函数执行结束后该执行上下文被弹出。

test

创建执行上下文是根据按照以下步骤创建的(有先后顺序):

  1. 创建当前执行上下文的变量对象(在函数中称为活跃对象):
    1. 创建arguments对象(如果是函数调用的话),初始化参数名称和值并创建引用的复制。其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined
    2. 扫描当前执行上下文内的所有函数声明:为发现的每一个函数,在变量对象上创建一个属性,属性名是函数的名字,并且指向函数在内存中的引用,如果变量对象已经包含了相同名字的属性,则替换他的值为当前函数在内存中的引用。(这里的函数扫描只扫描用函数声明定义的函数,不包括函数表达式定义的函数)
    3. 扫描当前执行上下文内的所有变量声明:为发现的每个变量声明,在变量对象上创建一个属性,属性名就是变量的名字,并且将变量的值初始化为undefined,如果变量的名字已经在变量对象里存在,则不会进行任何操作并继续扫描,
  2. 求出执行上下文内部“this”的值:在全局运行上下文中(在任何函数体外部),this 指代全局对象,无论是否在严格模式下;在函数内部,this的值取决于函数是如何调用的。(关于求this的值,本文后面再讲)
  3. 初始化作用域链:作用域链对执行上下文中的变量对象的有序访问的链表,包括当前执行上下文的变量对象,以及包含了所有上层变量对象的分层链(在函数创建时静态保存在函数中的)

整个js代码的执行机制就是上面所说的,上面的内容其实包括了执行上下文、作用域链、提升机制,有了以上知识储备,我们再来理解这些难懂的概念就比较容易了:

作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在js中,变量的作用域有全局作用域和局部作用域两种。

  • 全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域,有以下几种:
    • 在最外层定义的变量;
    • 全局对象的属性;
    • 任何地方隐式定义的变量(未定义直接赋值的变量),在任何地方隐式定义的变量都会定义在全局作用域中,即不通过 var 声明直接赋值的变量。
  • 局部作用域:JavaScript的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,称为函数(局部)作用域

this

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。 this 出现的场景分为四类,简单的说就是:

  • 有对象就指向调用对象
  • 没调用对象就指向全局对象
  • 用new构造就指向新对象
  • 通过 apply 或 call 或 bind 来改变 this 的所指。

闭包

闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。闭包是指函数有自由独立的变量。换句话说,定义在闭包中的函数可以“记忆”它创建时候的环境。定义在闭包中的函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

例子

//todo

总结

//todo

参考链接: