极品javascript进阶博客教程《ECMA-262-3 in detail》读书笔记

 前言:

 在我看来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: 

这个现象就是:如果一个函数中声明一个变量,且该变量的名称与函数的形参的名称相同,那么,这个变量和这个形参其实是同一个东西,看例子:

function foo(i){
     var i=5;
    alert(arguments[0]); 

 foo(10);//5,而不是10
 

 

而反过来也成立:

 

function foo(i){
    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)时候创建,并且并不会在函数执行其中改变,因此,才有闭包的机制:

var x=10;
function a(){
    alert(x);
}
function b(){
    var x=20;
    a();
}
b();//10,而不是20

 

 

 

 

第六章:闭包

 现在要解析闭包有点难,因为要说清楚闭包就必须要搞清楚scope/scope chain和 context,反正和前几章都有关联,要详细解析闭包可能还要写一遍比较完整的笔记。现在有些零碎的理解,先记下来吧,反正会有用到,先看一个闭包的经典例子:

function foo(){
   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中所有函数都是闭包)

 这里必须看看“动态作用域”和“静态作用域”。先看一个例子:

 

//假定这里是global
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语言也是静态作用域:

#include "stdafx.h"


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只使用静态作用域(语法作用域)

 

ECMAScript中,闭包指的是:

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
1.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
2.在代码中引用了自由变量

posted on 2012-02-11 21:09  wyman25  阅读(1058)  评论(0编辑  收藏  举报

导航