一直在看JavaScript的书,有几个知识点对新手来说很疑惑:
- 执行上下文
- 作用域链
- 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,若有其他的新的执行环境创建则依次入栈,运行完时则出栈。
变量对象的创建,依次经历了以下几个过程。
-
建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
-
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
-
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为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();