从JavaScript执行上下文理解变量提升

一直在看JavaScript的书,有几个知识点对新手来说很疑惑:

  1. 执行上下文
  2. 作用域链
  3. this

JavaScript是一门解释性语言,不需要编译,是可以直接拿到运行环境中运行。

执行上下文

  要解释执行上下文就先得说说JavaScript的内存机制;虽然JavaScript的运行环境一般都自带了GC装置,所以不用太操心内存泄漏的这个蛋疼的问题。但是当我们每当声明一个变量时,这个变量到底存在内存中的哪里?一般来说在JavaScript中基础数据类型(null  undefined string number Boolean)是存在栈中,而引用类型(array object)是存在堆内存中。eg:

var a1 = 0;   // 变量对象
var a2 = 'this is string'; // 变量对象
var a3 = null; // 变量对象
var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中

从图中可以看出引用类型的的值是保存了一个堆内存中的一个地址,所以每次操作时都是对源数据进行操作!

而执行上下文呢?根据《JavaScript高级编程》中指出,EC(执行时环境)中主要有三部分  1:VO(变量对象)  2:SC(作用域链)   3:this

在运行JavaScript时最先入栈的就是全局EC,若有其他的新的执行环境创建则依次入栈,运行完时则出栈。

变量对象的创建,依次经历了以下几个过程。

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。

  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

总的来说就是function优先级比var高,var声明的变量永远都是undefined,而对于引用类型和函数则是堆内存地址! 


// demo01
function test() {    console.log(a);    console.log(foo());    var a = 1;    function foo() {  return 2;
    }
}
test();

在上例中,我们直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。为了便于理解,我们用如下的形式来表示

创建过程
testEC = {    // 变量对象    
VO: {},    
scopeChain: {},    
this: {}}// 因为本文暂时不详细解释作用域链和this,所以把变量对象专门提出来说明// 

VO 为 Variable Object的缩写,即变量对象
VO = {   
 arguments: {...},    
foo: <foo reference>  // 表示foo的地址引用 
a: undefined
}

未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

这样,如果再面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。

// 执行阶段VO ->  AO   // Active ObjectAO = {
    arguments: {...},
    foo: <foo reference>,
    a: 1}

因此,上面的例子demo1,执行顺序就变成了这样

function test() {    
function foo() {        return 2;   }   
 var a;    
console.log(a);   
console.log(foo());
 a = 1;
}

test();
 
原文地址:https://www.cnblogs.com/duyingxuan/p/6406380.html