execution context/execution context stack

当函数运行时,会创建一个执行环境, 这个执行环境就叫做执行上下文 (execution context)

变量对象(variable object) VO中存放着上下文中定义的所有变量和对象

作用域链(scope chain) this

JavaScript引擎创建了执行上下文栈(execution context stack)来管理执行上下

内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西,每个上下文都可以到上一个上下文中去搜索变量和函数,但任何一个上下文都不能到下一级的上下文中去搜索

变量对象和活动对象
函数执行时,每个执行上下文都会有一个包含其中变量的对象,全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。

  1. var a = 10
  2. function test(x) {
  3. var b = 20
  4. }
  5. test(30)
  6. // 全局上下文的变量对象
  7. VO(globalContext) {
  8. a: 10,
  9. test: <reference to function>
  10. }
  11. // test函数上下文的变量对象
  12. VO(functionContext) {
  13. x: 30,
  14. b: 20
  15. }

AO

  1. 1. 在函数执行上下文中,VO是不能直接访问的,

执行栈压栈顺序
一开始执行代码的时候,确定一个global execution context 作为默认值,如果你的全局环境中,调用了其他函数,程序将会再创建一个新的EC,然后将此EC推入到执行栈中的execution stack, 一旦一个EC执行完成,会从执行栈中推出(pop)

  1. ECStack = [
  2. globalContext
  3. ]
  4. function fun() {
  5. console.log('fun3)
  6. }
  7. 栈顶的
  8. ECStack = [
  9. fun,
  10. globalContext
  11. ]

scope/scope chain

作用域是根据名称查找变量的一套规则

词法作用域(lexical scoping)
JavaScript采用的是词法作用域(lexical scoping),也就是静态作用域
函数的作用域在函数定义的时候就决定了
主要区别:词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的(this也是!)

closure

闭包产生的本质就是,当前环境中存在指向父级作用域的引用
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使当前函数在词法作用域外执行

  1. function foo(){
  2. var a = 2
  3. function bar() {
  4. console.log(a)
  5. }
  6. return bar
  7. }

在foo()执行后,通常会期待foo()的整个内部作用域都被销毁,由于看上去foo()的内容不会再被使用,所以很自然的会考虑对其进行垃圾回收
而闭包正式可以阻止这件事发生。事实上内部作用域依然存在,一次没有被回收
bar()依然持有对该作用域的引用,而这个引用就是闭包

因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存

循环和闭包

  1. for (var i = 1; i <= 5; i++) {
  2. setTimeout(function () {
  3. console.log(i)
  4. }, i*1000)
  5. }

根据作用域的工作原理,尽管循环中的5个函数是在各自的迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

  1. for (var i = 1; i <= 5; i++) {
  2. (function() {
  3. setTimeout(function () {
  4. console.log(i)
  5. }, i*1000)
  6. })()
  7. }

IIFE(Immediately Invoked Function Expression)会通过声明并立即执行一个函数来创建作用域
但是这样不行,虽然我们拥有了更多的词法作用域了,每个延迟函数都会将IIFE在每次迭代中创建的作用域封闭起来
如果作用域是空的,那么仅仅将它们进行封闭是不够的,我们IIFE只是一个什么都没有的空作用域,它需要包含一点实质内容才能为我们所用
它需要有自己的变量,用来在每个迭代中存储i的值

  1. for (var i = 1; i <= 5; i++) {
  2. (function(k) {
  3. setTimeout(function () {
  4. console.log(k)
  5. }, i*1000)
  6. })(i)
  7. }

reference

Execution context, Scope chain and JavaScript internals
JavaScript深入之词法作用域和动态作用域
学习Javascript闭包(Closure)
JavaScript深入之执行上下文
mdn闭包