JS之执行上下文

  执行上下文(execution context),是JS中的一个很重要的概念。它对于我们理解函数定义,执行时都做了什么有着很大的意义。理解它我们才能明白我们常说的函数声明提升,作用域链,闭包等原理。

  在解释之前,我们先来看看经常会看到这样一段代码。

console.log(a); //undefined
var a =1;

  这段代码我们都知道原因,也就是变量声明提升。

  再看下面一段代码

var a=10;

function b(){
   console.log(a);//undefiend
   var a=20;        
}

b();

可能会有人不明白,好像按照作用域链查找应该会是10而为什么还是undefine呢?要去解释这个问题,要去理解为什么变量会提升,我们还是要回到执行上下文中。

  行上下文存在于两种情景中,一段script脚本或者一个函数中。一段script脚本中,有全局上下文环境,在这个时候进行了变量的定义与函数的声明。在一个函数上下文环境中发生了什么,在下文中有分析。

  每一个执行上下文环境,都有一个与之对应的变量对象(variable object),我们也简称为VO。这个VO保存了一个执行上下文环境定义的所有的变量和函数。

VO:{

  变量,

  函数,

  arguments对象,

  参数

}.

一.上下文的创建过程发生了什么?

  上下文的创建过程可以分为两个阶段。

  1.上下文的建立阶段,即在一个函数被调用但是在执行该函数内部代码之前的这段时间。在这段时间中,上下文做了如下几件事。

  ①.建立函数,变量,arguments对象,参数。在这个时候除了arguments,函数声明,以及参数已被赋值,其他的变量属性都默认是undefined。如果没有传入参数,它也是undefined。

  ②建立作用域链,注意是作用域链而不是作用域。这是因为函数的作用域在定义它的时候就已经确定了。

  ③.确定this的值。

  2.代码的执行阶段。

  ①.变量赋值

  ②.函数引用

  ③.执行其它的代码。

  其实,这个时候把执行上下文理解成一个对象更直观。

  EC_Object={

  VO(变量对象):函数中的arguments对象,参数,内部变量以及函数,

  chain(作用域链):VO以及所有执行上下文中的VO,

  this:{}

}。

二.函数调用时发生了什么?

  每一个函数在被调用时,都有执行代码前,执行代码时,还有执行后这三个阶段。

  ①.执行代码前

  1.创建执行上下文,每一个函数在被调用时都会产生一个新的执行上下文环境。

  2.进入创建阶段。

  (1).建立VO对象

  ①.建立arguments对象,检查当前上下文中的参数。如果传入参数则给参数赋值,否则参数值为undefined。

  ②.检查当前上下文中的函数声明。每找到一个函数声明,就在VO下建立一个该函数名为属性名的属性值,该属性值就是指向该函数在内存中的地址的一个引用。如果函数名已经存在于VO中,则对应的属性值会被新的引用所覆盖。

  ③.检查当前上下文中的变量声明。每找到一个变量声明,就在VO下建立一个该变量名为属性名的属性值,属性值默认为undefied(注意:此时还没有赋值)。如果该变量名存在于VO中,则会直接跳过而不会覆盖。

function f(){
   var a=10;
   function a(){}; 
}
f();
VO:{
   a:function(){},//函数名覆盖了变量名
   arguments,  
}

function f(){
   function a(){}; 
   var a=10;
}
f();
VO:{
   a:function(){},//变量名跳过了
   arguments,
}

  (2)初始化作用域链。作用域中变量的值是在执行过程中产生而确定的,作用域却是在函数创建时就确定的。若要查找一个作用域下的某个变量的值,就要找到这个作用域对象的执行上下文,找到它的VO,再到其中去寻找变量的值。如果在该上下文环境没有找到,他会随着作用域链向上查找另一个上下文环境,找到该环境下的VO,直到全局环境,全局上下文环境中的VO始终是作用域链中的最后一个对象。

  (3)确定上下文中this的指向

  ②.执行代码时

  执行函数内部代码时,一步步运行,给VO中的变量属性赋值。

  ③.执行代码后

   当一个执行上下文环境中的代码执行完毕后,该环境会被销毁,保存在其中的所有变量和函数都会随之销毁。但是,有一个特殊情况,则是另一个我要去说明的问题——闭包。

了解了这么多。我们再回到之前提到的两段代码。

console.log(a);//undefiend
var a=1;

//这个时候是全局执行环境,在执行代码前,该环境已经为a定义了,全局中的VO已经有了a这个变量属性,值默认为undefined。
因为代码是一行行进行的,所有第一行代码执行后,a其实已经定义了,值为undefined。这就是所谓的变量声明提升了。

第二段代码

var a=10;

function b(){
   console.log(a);//undefiend
   var a=20;        
}

b();

//在调用b函数,执行代码之前。创建了一个b函数的执行上下文环境。在该环境下,VO中也已经有了a这个变量属性,值是undefined,
此时在执行代码第一句时,已经在这个函数执行上下文中的VO中找到了a,而不用再到全局中的VO中去找。所有仍然是undefined。
这就是所谓的变量查找就近原则,是不是很好理解。

关于上下文的说明就这么多,如果发现问题,还希望大家能帮我及时纠正,多多交流。

关于作用域与闭包的问题,可以看下我的另一篇文章。

posted @ 2017-09-11 10:05  Eric1997  阅读(821)  评论(0编辑  收藏  举报