执行上下文&作用域

PRE

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

引例

var value = 1

function foo() {
  console.log(value)
}

function bar() {
  var value = 2
  foo()
}

bar() // 1
复制代码

执行上下文 Execution Context

变量或者函数的上下文决定了他们可以访问哪些数据,以及它们的行为。

每个上下文都有一个关联的变量对象,该上下文的所有变量和函数都存在于这个对象上。

全局上下文是最外层的上下文。根据ECMAScript实现的宿主环境,会不一样。

浏览器runtime中,全局上下文就是window对象。

多个上下文层层嵌套,形成作用域链

function A() {
  function B() {
    function C() {
      ...
    }
  }
}
复制代码

在function C中执行的代码,其作用域链为

EC 表示 Execution Context

C EC -> B EC -> A EC -> Global EC

这也是标识符进行寻找的顺序

从实现来看,通过变量对象(VO)绑定相应EC的变量、函数

什么时候会创建新的执行上下文呢?

逻辑上来看,就是能够执行的代码要进行准备的时候

  1. 全局
  2. function
  3. eval

值得注意的是 函数的EC的实现VO在初始化的时候有 arguments 可称为活动对象(AO)

执行过程

处理有两个过程

  1. 进入执行上下文
  2. 代码执行

此处注意函数声明提升和变量提升

1

image-20210314090540488

此时foo函数的AO

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
复制代码

2

image-20210314090622029

此时函数的AO

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 2,
    c: 1,
    d: function
}
复制代码

由此可见函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。

静态作用域

又叫词法作用域

函数的EC 在进行创建和声明的时候就已经确定了

静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility);在这段区域以外该变量不可见(或无法访问)。词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定。

大多数现在程序设计语言都是采用静态作用域规则,如C/C++C#PythonJavaJavaScript……

相反,采用动态作用域的变量叫做动态变量。只要程序正在执行定义了动态变量的代码段,那么在这段时间内,该变量一直存在;代码段执行结束,该变量便消失。这意味着如果有个函数f,里面调用了函数g,那么在执行g的时候,f里的所有局部变量都会被g访问到。而在静态作用域的情况下,g不能访问f的变量。动态作用域里,取变量的值时,会由内向外逐层检查函数的调用链,并打印第一次遇到的那个绑定的值。显然,最外层的绑定即是全局状态下的那个值。

采用动态作用域的语言有PascalEmacs LispCommon Lisp(兼有静态作用域)、Perl(兼有静态作用域)。C/C++是静态作用域语言,但在宏中用到的名字,也是动态作用域。

作用域增强

image-20210313201703100

从作用域链的角度来看,就是把with 中的对象,作为EC 变量对象,加入在 {}语句中的标识符搜索的头部

这两种情况下,都会在作用域链前端添加一个变量对象。对 with 语句来说,会向作用域链前端添
加指定的对象;对 catch 语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明

闭包

image-20210313202238325

返回的匿名函数的作用域链上有name可以拿到

存在对于已经销毁的函数的foo的VO 中 name的饮用

故GC机制不会清除foo中的name所占用的空间

立刻调用的函数表达式(IIFE)

创建函数的方式有两种

  • function declaration

  • function expression

The main difference between a function expression and a function declaration is the function name, which can be omitted in function expressions to create anonymous functions. A function expression can be used as an IIFE (Immediately Invoked Function Expression) which runs as soon as it is defined.

;(function () {
  
})()
复制代码

类似于函数声明,但由于包含在括号中,会被解释为函数表达式

IIFE + 执行上下文机制可以用来创建隔离的作用域 -> 块级作用域

提两个应用场景

;(function () {
  for (var i = 0; i < 3; i++) {
    console.log(i)
  }
})()
 
// Uncaught ReferenceError: i is not defined
console.log(i)
复制代码
const lis = document.querySelectorAll('li')

for (var i = 0; i < 3; i++) {
  lis[i].addEventListener('click', function () {
    console.log(i)
  })
}
// 点每一个li都是3
复制代码
const lis = document.querySelectorAll('li')

for (var i = 0; i < 3; i++) {
  lis[i].addEventListener(
    'click',
    (function (frozenI) {
      return function () {
        console.log(frozenI)
      }
    })(i)
  )
}
复制代码

不过现在有了const let 已经不需要上面的方案了

不过感觉在引入一些包的时候,这样的机制还是在用

比如

image-20210314105557332

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享