执行上下文、作用域链详解

41 阅读4分钟

 1.执行上下文

a. 变量声明提升和函数声明提升

包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

变量提升只提升变量声明,不提升变量赋值(函数声明会被提升,但是函数表达式却不会被提升)

//case1
console.log(add2(1,1));//2 函数声明和函数体都提升了
function add2(a,b){
  return a+b;
}
//case2
console.log(add1(1,1));//报错:add1 is not a function 
//此时只提升了变量add1的声明add1:undefined,所以报错
var add1 = function(){
  return a+b;
}

如果没有声明变量,则默认是在全局中声明的,不建议

//没有声明变量
function f(){
  console.log(a)//报错,没有声明变量
  a=1
}
function f2(){
  a=1;
  console.log(a)//1,默认在全局中声明
}

//有声明
function f(){
  console.log(a)//声明了a时,打印undefined
  var a=1
}

函数声明和变量声明提升 优先级

函数会首先被提升,然后才是变量。

          foo(); // 1

          var foo;

          function foo() {
              console.log(1);
          }

          foo = function() {
              console.log(2);
          };

注意,var foo尽管出现在function foo()...的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

变量声明在前,函数声明在后,变量声明会被忽略

应先函数声明,后变量声明,而且不能同名,否则被覆盖

一个普通块内部的函数声明通常会被提升到所在作用域的顶部,因此应该尽可能避免在块内部({}等)声明函数。

b. 可执行代码

可执行代码:全局代码,函数代码,eval代码

JS在执行一段可执行代码时,会创建对应的执行上下文

c. 执行上下文栈

function fun3(){
  console.log('fun3')
}
function fun2(){
  fun3()
}
function fun1(){
  fun2()
}
fun1()

下面例子的执行结果相同,但执行上下文栈的执行顺序不同

//case1
var scope = "global scope";
function checkscope(){
  var scope = "local scope";
  function foo(){
    return scope
  }
  return foo()
}
checkscope();//静态和动态执行结果都是local scope

//case2 形成闭包
var scope = "global scope";
function checkscope(){
  var scope = "local scope";//自由变量
  function foo(){
    return scope
  }
  return foo
}
checkscope()();//静态local scope,动态global scope

执行上下文栈:后进先出用于管理执行上下文的执行顺序,上面的例子,执行顺序如下图:

2.执行上下文的三个重要属性

变量对象(Variable object VO,activation object AO),作用域链(Scope chain),this

3.变量对象

变量对象:存储了上下文中定义的变量和函数声明

变量对象包含全局上下文的变量对象和函数上下文的变量对象

a. 全局上下文的变量对象VO

对JS而言,全局上下文中的变量对象(Variable object,VO)就是全局对象

b. 函数上下文的变量对象AO

函数内部的对象

活动对象(activation object,AO)表示变量对象

只有到当进入一个执行上下文中(函数被调用) ,这个执行上下文的变量对象才会被激活,所以才叫 activation object,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化(arguments会在后续更新函数传参中讲解 函数传参)

c. 执行过程

进入执行上下文

当进入执行上下文时,还没有执行代码,

变量对象此时包括:形式参数 function a(b,c){}、函数声明、变量声明,如下

function foo(a){
    var b=2;
    function c(){}
    var d = function(){};
    b=3;
}
foo(1);

AO={
  arguments:{
    0:1,
    length:1
  },
  a:1,
  b:undefined,
  c:reference to function c,
  d:undefined,
}

执行代码

AO={
  arguments:{
    0:1,
    length:1
  },
  a:1,
  b:3,
  c:reference to function c,
  d:reference to 函数表达式
}

4.作用域链(Scope chain)

  1. 一个变量在当前作用域没有被定义,但是被使用了(自由变量)
  2. 就会一层层向上级寻找,直到找到为止
  3. 如果找到全局作用域都没有找到,就报错 xxx is not defined 这种一层层的关系,就叫做作用域链

函数创建和函数执行

function foo(){
  function bar(){}
}

//函数创建:此时作用域链中只有函数外部的活动对象(AO)
foo.[[scope]] = [
  globalContext.VO
]
bar.[[scope]] = [
  foo.AO,
  globalContext.VO
]
//函数执行
foo.[[scope]] = [
  foo.AO
  globalContext.VO
]
bar.[[scope]] = [
  bar.AO,
  foo.AO,
  globalContext.VO
]
var scope = "global scope";
function checkscope(){
  var scope2 = "local scope";
  return scope2
}
checkscope();

//执行
checkScope.[[scope]] = [
  checkScope.AO,
  globalContext.VO
]
checkScopeContext = {//执行上下文重要的三个要素:变量对象,作用域链,this
  AO:{
    arguments:{
      length:0
    },
    scope2:undefined(声明)/local scope(执行)
  },
  Scope:[
    checkScope.AO,
    globalContext.VO
  ]
}

5.自由变量

自由变量:能够在函数中使用但既不是函数的参数,也不是局部变量的那些变量

所有的自由变量都应该在函数定义的地方,向上级作用域寻找,不是在执行的地方。

  

参考

  • 《你不知道的JavaScript》
  •  作用域链的描述引用自:掘金:iceychan

最后

这是JavaScript系列第3篇,下一篇更新自由变量和闭关。

小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!

我的CSDN