JavaScript深度解析
Copyright (2013) 郭龙仓. All Rights Reserved.
变量声明与表达式(declarations and expressions)
代码示例:
1 var a; // declaration 2 var a = 2; // declaration and expression 3 var func1 = function(){ alert(3) }; // declaration and expression 4 function func2(){ alert(4) }; // declaration a function 5 (function(){ alert(5) }); // expression
1 alert(c); // 'undefined' 2 alert(a); // 'undefined' 3 alert(b); // 'undefined' 4 alert(d); // 'undefined' 5 alert(func); // 'function func(){}' 6 var c = a; 7 var a =1; 8 var b = function(){alert(2);} 9 var d = a;; 10 function func(){}
下面这段话可能有助于你理解变量声明与表达式的区别:
- Anything to the right of an = sign (or : on object literals).
- Anything in braces ().
- Parameters to functions (this is actually already covered by 2).
变量声明的结果是创建一个标识符,表达式的结果是生成一个值或者对象。
编译与执行(compilation and execution)
JavaScript代码执行分为两个阶段:
阶段一,编译(compilation)
在这个阶段代码被编译为执行树(字节码或二进制的形式,具体依赖于不同的JavaScript引擎)。
阶段二,执行(execution)
解释并执行阶段一生成的代码
编译阶段处理变量声明,执行阶段处理表达式。因此,在编译阶段完成后和执行阶段开始前,所有已声明变量的值都为undefined。
可执行代码类型(Executable Code)
JavaScript的可执行代码分为三种类型(此处就不进行代码示例了,大家可以从名字猜测每种类型所对应的代码):
- 全局代码(Global Code)
- 函数代码(Function Code)
- Eval 代码(Eval Code)
执行上下文(Execution Contexts)
每当JavaScript引擎进入一段可执行代码,就会生成一个对应的执行上下文。
在为函数代码生成执行上下文之后,JavaScript引擎还会创建一个对应的Arguments对象。
JavaScript引擎通过一个栈(Stack)来维护执行上下文。
在为函数代码生成执行上下文之后,JavaScript引擎还会创建一个对应的Arguments对象。
JavaScript引擎通过一个栈(Stack)来维护执行上下文。

图1 Environment Record

图2 Lexical Environment

图3 Execution Context
执行上下文由三个部分组成:
Lexical Environment 与 Variable Environment 均是 Lexical Environment 类型,都用来存储外部词法环境引用(outerLexicalEnvironment,请参考图2)、本地参数/本地变量(environmentRecord,请参考图2)。它们的区别在于:Variable Environment 在代码执行过程中是不变的,而 Lexical Environment 会随着代码执行进入/退出with或者catch语句块的时候动态改变(with语句块和catch语句块会引起变量环境的临时改变)。
Environment Record用来存储本地参数/本地变量。有两种类型的Environment Record存在:
一般情况下,Environment Record 是 Declarative Environment Record 类型的,但是也有少数情况下是 Object Environment Record 类型(比如浏览器环境下,window对象的属性之所以成为全局的,就是因为它的全局 Environment Record 是 Object Environment Record 类型的,还有一种情况就是 with 语句)。
- Lexical Environment(词法环境,也可以称之为Variable Object,用于构造作用域链)
- Variable Environment(变量环境,也可以称之为Activation Object, 用于存储本地变量)
- This Binding(this 绑定)
Lexical Environment 与 Variable Environment 均是 Lexical Environment 类型,都用来存储外部词法环境引用(outerLexicalEnvironment,请参考图2)、本地参数/本地变量(environmentRecord,请参考图2)。它们的区别在于:Variable Environment 在代码执行过程中是不变的,而 Lexical Environment 会随着代码执行进入/退出with或者catch语句块的时候动态改变(with语句块和catch语句块会引起变量环境的临时改变)。
Environment Record用来存储本地参数/本地变量。有两种类型的Environment Record存在:
- Declarative Environment Record
- Object Environment Record
一般情况下,Environment Record 是 Declarative Environment Record 类型的,但是也有少数情况下是 Object Environment Record 类型(比如浏览器环境下,window对象的属性之所以成为全局的,就是因为它的全局 Environment Record 是 Object Environment Record 类型的,还有一种情况就是 with 语句)。
执行上下文栈代码示例:
// 执行上下文栈(ECStack)状态变化示例 /* ECStack = [ globalContext ]; */ eval('var x = 10'); /* eval执行上下文入栈 ECStack = [ evalContext, callingContext:globalContext ]; */ // eval执行上下文出栈 /* ECStack = [ globalContext ]; */ (function foo(){ alert(10) })(); // function执行上下文入栈 /* ECStack = [ <foo> functionContext, globalContext ]; */ // function执行上下文出栈 /* ECStack = [ globalContext ]; */ (function outfool() { function infoo1(){ alert(20) } eval('var y = 30') (function infoo2(){ alert(40) })(); return infoo1() })()(); // function执行上下文入栈 /* ECStack = [ <outfool> functionContext, globalContext ]; */ // eval执行上下文入栈 /* ECStack = [ evalContext, callingContext:<outfool> functionContext, globalContext ]; */ // eval执行上下文出栈 /* ECStack = [ <outfool> functionContext, globalContext ]; */ // function执行上下文入栈 /* ECStack = [ <infoo2> functionContext, <outfool> functionContext, globalContext ]; */ // function执行上下文出栈 /* ECStack = [ <outfool> functionContext, globalContext ]; */ // function执行上下文出栈 /* ECStack = [ globalContext ]; */ // function执行上下文入栈 /* ECStack = [ <infool1> functionContext, globalContext ]; */ // function执行上下文出栈 /* ECStack = [ globalContext ]; */
作用域链(Scope Chain)
Lexical Environment通过持有一个外部词法环境(outerLexicalEnvironment,请参考图2)的引用来形成作用域链。
闭包(Closure)
当函数B是函数A的内部函数,且函数A的返回值为函数B。那么,每调用一次函数A,并把A的返回值赋值给一个变量,就完成了一个闭包的构造。
闭包代码示例:
1 function buildClosure(arg1, arg2){ 2 var localVar = 8; 3 function innerFunc(innerArg){ 4 return ((arg1 + arg2)/(innerArg + localVar)); 5 } 6 7 return innerFunc; 8 } 9 10 var closureVar = buildClosure(2, 4);
new 关键字做了什么?
new 代码示例:
function Dog( host ) { this.host = host; } var dog = new Dog( "Jack" );
当 newDog("Jack")被调用的时候,JavaScript引擎做了下面这些事情:
任何明确声明的变量会成为Variable Object的属性,并且具备DontDelete特性。任何通过赋值的方式声明的对象属性都不具备DontDelete特性,因此可以成功delete。
另外,一些内置对象属性也具备DontDelete特性。
- 创建一个空的Object对象
- 将这个Object对象的原型设置为Dog
- 准备执行Dog函数,并用这个Object作为this参数,字符串"Jack"作为host参数
- 执行Dog函数
- 返回该Object对象
用不用 var 关键字有什么区别?
如果在全局作用域,用不用 var 关键字没有任何区别。
如果在局部作用域,使用 var 关键字会使JavaScript引擎创建一个局部变量,不使用 var 关键字会导致JavaScript沿着作用域链一直向上查找,直到找到该变量,如果最终没找到该变量,JavaScript引擎会在全局作用域创建该变量。
this 关键字怎么绑定的?
this值是在JavaScript引擎执行过程中动态绑定的,与源代码的位置无关,与代码执行过程有关(请参考图3)。
如果执行的是全局代码,那么this指向的是全局对象。如果执行的是函数代码,那么this指向的则是函数的调用者。如果执行的是Eval代码,那么默认情况下this的值与Eval代码外部环境中this的值一致(除非你在执行Eval代码的时候通过参数明确指定eval的执行上下文)。
以下方法可以修改默认的this值:
- Function.prototype.apply( thisArg, argArray )
- Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
- Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
- Array.prototype.every( callbackfn [ , thisArg ] )
- Array.prototype.some( callbackfn [ , thisArg ] )
- Array.prototype.forEach( callbackfn [ , thisArg ] )
- Array.prototype.map( callbackfn [ , thisArg ] )
- Array.prototype.filter( callbackfn [ , thisArg ] )
delete 为什么会返回 false ?
JavaScript的对象属性具有(Property)一些特性(Attribute),包括DontDelete特性。具有DontDelete特性的属性在使用delete删除的时候会返回false。任何明确声明的变量会成为Variable Object的属性,并且具备DontDelete特性。任何通过赋值的方式声明的对象属性都不具备DontDelete特性,因此可以成功delete。
另外,一些内置对象属性也具备DontDelete特性。

浙公网安备 33010602011771号