前言:
在我看来javascript是一门非常松散灵活的语言,松散并不是贬义,而恰恰相反,是对js的一种赞美。javascript相对于c#等静态编译语言来说,就像一个骑自行车已经十分熟练的顽皮孩子,已经不再需要遵循上车必须从车右边扶车把,左脚踏踏板右脚蹬地几下的标准动作了,他可以做出若干不同的上车花式,但始终不会背离平衡这一原则。正式这样的灵活性甚至看上去有点怪异的行为令javascript与c#这些语言表现格格不入:它是面向对象,却没有类,能继承又搞出个prototype,“this”的神出鬼没难以捉摸,闭包,作用域,弱类型等概念更是让人摸不着头脑。
但是请不要忘记,我们觉得js怪异只是因为我们早已习惯类C语言的思维,js还是一门计算机语言,就像那个爱玩花式的骑车小男孩没有背离“平衡”这一原则一样,js并没有背离计算机语言的一个原则(就跟其他计算机语言一样):控制计算机去做事 。
从另一个角度来说,正是因为js的灵活与怪异,我们更应该像学其他静态语言一样深入学习它。在“学习另一种颇为不同的语言”的原则指导下, 深入学习js还是很有价值的。学习js能令人从一种与c# java非常不同的思维去认识计算机语言和编程,去认识一些万变不离其宗的核心东西和思想。
在我学习javascript的过程中,很自然地对很多概念迷惑不解,网上虽然有很多“深入理解javascript XXX”的教程,但始终觉得不够深入,就像你问别人栈是什么,别人只告诉你“先进后出”一样不痛不痒。
但幸运的是,前几天终于发现了俄国人Dmitry Soshnikov写的一个系列博客教程: 《ECMA-262-3 in detail》,在园子里也有精彩的“官方翻译”:http://www.cnblogs.com/justinw/archive/2010/04/16/1713086.html。这系列博客真的是非常的有用,从ECMA规范的角度来解析js更加之深入和彻底,而且涵盖了js进阶的所有话题。但虽然该系列博客写的非常好,例子恰当,但毕竟其描述的都是js中比较高级的内容,因此不免有很多点很难马上弄懂,需要反复阅读斟酌,因此本编博客便是我在学习的过程中的笔记,随时记录一些心得和其他需要铭记的点。
第一章:Execution Contexts
函数递归调用的时候也会产生execution context
execution context是逻辑堆栈结构
第二章:Variable Object
即使是在永远不会被执行的else分支中声明的变量,或者function定义,也会被存放在vo中function中的vo叫做Activation Object(AO) ,而global中的vo也就是global,VO只是泛称。
scope chain=list of parent's VOs + function's own AO
另外,有一个现象可能跟一句话有关:An activation object is created on entering the context of a function and initialized by propertyarguments
which value is the Arguments object:
这个现象就是:如果一个函数中声明一个变量,且该变量的名称与函数的形参的名称相同,那么,这个变量和这个形参其实是同一个东西,看例子:
var i=5;
alert(arguments[0]);
}
foo(10);//5,而不是10
而反过来也成立:
var i;
arguments[0]=10
alert(i);
}
foo(5);//10,而不是5
至于底层的实现是不是就是这样现在还在研究中,但好像以这样来解析也行得通。
第三章:this.
Identifier 是变量名,函数名(例如 function foo(){}中的"foo",函数参数名及在global中未识别的属性)
引用类型只出现在两种情况:处理Identifier的时候,和属性访问器。属性访问器就是"."和"[]",例如"obj.foo"和"obj["foo"]"
引用类型的base属性指向该对象的所有者
第四章:Scope chain.
“scope”其实就是[[scope]],通常说scope的时候是指函数对象的[[scope]]属性,而 scope chain是指函数对象中[[scope]]链表(当然不一定就是链表结构)
[[scope]]是在一个函数创建的时候创建的,而不是在一个函数被执行的时候(进入execution context)时候创建,并且并不会在函数执行其中改变,因此,才有闭包的机制:
function a(){
alert(x);
}
function b(){
var x=20;
a();
}
b();//10,而不是20
第六章:闭包
现在要解析闭包有点难,因为要说清楚闭包就必须要搞清楚scope/scope chain和 context,反正和前几章都有关联,要详细解析闭包可能还要写一遍比较完整的笔记。现在有些零碎的理解,先记下来吧,反正会有用到,先看一个闭包的经典例子:
var x=10;
return function(){
alert(x);
}
}
var bar=foo();
bar();//10
如果不是闭包的话,按逻辑来说,x是foo中的局部变量,当foo执行完返回的时候,x已经被释放掉。那么,从表象的层面来解析,闭包就是在函数返回后仍能访问该函数内部数据的一种机制,从原理来说,就是被调用的函数(这里是foo中的那个匿名函数)的scope包含了外部函数(foo)的数据(“数据”一词是故意用的,这样说比较笼统,因为我还不清楚这应该是什么,难道是VO?迟些再研究一下),而从作用域的层面去看,就是因为“js是基于静态作用域”的,所以《ecmascript in detail》中有一句话:Referencing to algorithm of functions creation, we see that all functions in ECMAScript are closures, since all of them at creation save scope chain of a parent context.(ecmascript中所有函数都是闭包)
这里必须看看“动态作用域”和“静态作用域”。先看一个例子:
var x=5;
function foo(){
alert(x);
}
function bar(){
var x=10;
foo();
}
bar();//5,而不是10
静态作用域,有些地方称词法作用域,是指:一个作用域在函数创建的时候,就已经确定好了,而不是在执行的时候。也可以用另外一句话说:函数作用域是在源代码中确定的。
通过上面例子来看看这个“源代码中确定“是什么意思:上面例子中,调用bar的时候,进入bar的上下文,bar里面声明了一个x,并赋值x=10,然后调用foo,如果是动态作用域,也就是在代码执行时刻确定作用域的话,那么,foo里面被alert的x应该是bar里面的x,也就是10,但是在静态作用域的原理下”函数作用域是在源代码中确定的”,也就是说,调用foo的时候,解析引擎会回到foo定义的地方(源代码的前4句)去找,而在这里,很明显,x是第一句源代码定义的那个值为5的x,而且在foo被定义的源代码中,值为10的x都还没有出世~~~。这个就是我自己对“静态作用域”的理解,不过对于动态作用域我理解不多,有空看看维基百科上关于动态作用域。
在这里顺便提一下,C语言也是静态作用域:
int x=5;
void fun1(){
printf("%d",x);
}
void fun2()
{
int x=10;
fun1();
}
int _tmain(int argc, _TCHAR* argv[])
{
fun2();//5
scanf("%d",&x);
}
ecmascript只使用静态作用域(语法作用域)