软件真谛

用水云般自在的禅心,书写诗情画意的程序人生...
posts - 28, comments - 1195, trackbacks - 33, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 ::  :: 管理

悟透JavaScript

Posted on 2008-02-25 13:32 李战 阅读(51943) 评论(340)  编辑 收藏 网摘 所属分类: 软件思想

博客园专题介绍:http://book.cnblogs.com/zt/wtjs/

博客园购买网址: http://www.hjbook.net/product/3411/

网络书店购买网址:http://www.china-pub.com/301666

 

亲笔签名书专门店:http://shop36954457.taobao.com/

 

引子


    编程世界里只存在两种基本元素,一个是数据,一个是代码。编程世界就是在数据和代码千丝万缕的纠缠中呈现出无限的生机和活力。

    数据天生就是文静的,总想保持自己固有的本色;而代码却天生活泼,总想改变这个世界。
 
   你看,数据代码间的关系与物质能量间的关系有着惊人的相似。数据也是有惯性的,如果没有代码来施加外力,她总保持自己原来的状态。而代码就象能量,他存在的唯一目的,就是要努力改变数据原来的状态。在代码改变数据的同时,也会因为数据的抗拒而反过来影响或改变代码原有的趋势。甚至在某些情况下,数据可以转变为代码,而代码却又有可能被转变为数据,或许还存在一个类似E=MC2形式的数码转换方程呢。然而,就是在数据和代码间这种即矛盾又统一的运转中,总能体现出计算机世界的规律,这些规律正是我们编写的程序逻辑。

    不过,由于不同程序员有着不同的世界观,这些数据和代码看起来也就不尽相同。于是,不同世界观的程序员们运用各自的方法论,推动着编程世界的进化和发展。
 
    众所周知,当今最流行的编程思想莫过于面向对象编程的思想。为什么面向对象的思想能迅速风靡编程世界呢?因为面向对象的思想首次把数据和代码结合成统一体,并以一个简单的对象概念呈现给编程者。这一下子就将原来那些杂乱的算法与子程序,以及纠缠不清的复杂数据结构,划分成清晰而有序的对象结构,从而理清了数据与代码在我们心中那团乱麻般的结。我们又可以有一个更清晰的思维,在另一个思想高度上去探索更加浩瀚的编程世界了。

    在五祖弘忍讲授完《对象真经》之后的一天,他对众弟子们说:“经已讲完,想必尔等应该有所感悟,请各自写个偈子来看”。大弟子神秀是被大家公认为悟性最高的师兄,他的偈子写道:“身是对象树,心如类般明。朝朝勤拂拭,莫让惹尘埃!”。此偈一出,立即引起师兄弟们的轰动,大家都说写得太好了。只有火头僧慧能看后,轻轻地叹了口气,又随手在墙上写道:“对象本无根,类型亦无形。本来无一物,何处惹尘埃?”。然后摇了摇头,扬长而去。大家看了慧能的偈子都说:“写的什么乱七八糟的啊,看不懂”。师父弘忍看了神秀的诗偈也点头称赞,再看慧能的诗偈之后默然摇头。就在当天夜里,弘忍却悄悄把慧能叫到自己的禅房,将珍藏多年的软件真经传授于他,然后让他趁着月色连夜逃走...

    后来,慧能果然不负师父厚望,在南方开创了禅宗另一个广阔的天空。而慧能当年带走的软件真经中就有一本是《JavaScript真经》!

回归简单

    要理解JavaScript,你得首先放下对象和类的概念,回到数据和代码的本原。前面说过,编程世界只有数据和代码两种基本元素,而这两种元素又有着纠缠不清的关系。JavaScript就是把数据和代码都简化到最原始的程度。

    JavaScript中的数据很简洁的。简单数据只有 undefined, null, boolean, number和string这五种,而复杂数据只有一种,即object。这就好比中国古典的朴素唯物思想,把世界最基本的元素归为金木水火土,其他复杂的物质都是由这五种基本元素组成。

    JavaScript中的代码只体现为一种形式,就是function。

    注意:以上单词都是小写的,不要和Number, String, Object, Function等JavaScript内置函数混淆了。要知道,JavaScript语言是区分大小写的呀!

    任何一个JavaScript的标识、常量、变量和参数都只是unfined, null, bool, number, string, object 和 function类型中的一种,也就typeof返回值表明的类型。除此之外没有其他类型了。

    先说说简单数据类型吧。

    undefined:   代表一切未知的事物,啥都没有,无法想象,代码也就更无法去处理了。
                      注意:typeof(undefined) 返回也是 undefined。
                              可以将undefined赋值给任何变量或属性,但并不意味了清除了该变量,反而会因此多了一个属性。

    null:            有那么一个概念,但没有东西。无中似有,有中还无。虽难以想象,但已经可以用代码来处理了。
                      注意:typeof(null)返回object,但null并非object,具有null值的变量也并非object。

    boolean:      是就是,非就非,没有疑义。对就对,错就错,绝对明确。既能被代码处理,也可以控制代码的流程。

    number:      线性的事物,大小和次序分明,多而不乱。便于代码进行批量处理,也控制代码的迭代和循环等。
                      注意:typeof(NaN)和typeof(Infinity)都返回number 。
                              NaN参与任何数值计算的结构都是NaN,而且 NaN != NaN 。
                              Infinity / Infinity = NaN 。

    string:         面向人类的理性事物,而不是机器信号。人机信息沟通,代码据此理解人的意图等等,都靠它了。

     简单类型都不是对象,JavaScript没有将对象化的能力赋予这些简单类型。直接被赋予简单类型常量值的标识符、变量和参数都不是一个对象。

    所谓“对象化”,就是可以将数据和代码组织成复杂结构的能力。JavaScript中只有object类型和function类型提供了对象化的能力。

没有类

    object就是对象的类型。在JavaScript中不管多么复杂的数据和代码,都可以组织成object形式的对象。

    但JavaScript却没有 “类”的概念!

    对于许多面向对象的程序员来说,这恐怕是JavaScript中最难以理解的地方。是啊,几乎任何讲面向对象的书中,第一个要讲的就是“类”的概念,这可是面向对象的支柱。这突然没有了“类”,我们就象一下子没了精神支柱,感到六神无主。看来,要放下对象和类,达到“对象本无根,类型亦无形”的境界确实是件不容易的事情啊。

    这样,我们先来看一段JavaScript程序:
    var life = {};
    
for(life.age = 1; life.age <= 3; life.age++)
    {
        
switch(life.age)
        {
            
case 1: life.body = "卵细胞";
                    life.say 
= function(){alert(this.age+this.body)};
                    
break;
            
case 2: life.tail = "尾巴";
                    life.gill 
= "";
                    life.body 
= "蝌蚪";
                    life.say 
= function(){alert(this.age+this.body+"-"+this.tail+","+this.gill)};
                    
break;
            
case 3delete life.tail;
                    
delete life.gill;
                    life.legs 
= "四条腿";
                    life.lung 
= "";
                    life.body 
= "青蛙";
                    life.say 
= function(){alert(this.age+this.body+"-"+this.legs+","+this.lung)};
                    
break;
        };
        life.say();
    };

    这段JavaScript程序一开始产生了一个生命对象life,life诞生时只是一个光溜溜的对象,没有任何属性和方法。在第一次生命过程中,它有了一个身体属性body,并有了一个say方法,看起来是一个“卵细胞”。在第二次生命过程中,它又长出了“尾巴”和“腮”,有了tail和gill属性,显然它是一个“蝌蚪”。在第三次生命过程中,它的tail和gill属性消失了,但又长出了“四条腿”和“肺”,有了legs和lung属性,从而最终变成了“青蛙”。如果,你的想像力丰富的话,或许还能让它变成英俊的“王子”,娶个美丽的“公主”什么的。不过,在看完这段程序之后,请你思考一个问题:

    我们一定需要类吗?

    还记得儿时那个“小蝌蚪找妈妈”的童话吗?也许就在昨天晚,你的孩子刚好是在这个美丽的童话中进入梦乡的吧。可爱的小蝌蚪也就是在其自身类型不断演化过程中,逐渐变成了和妈妈一样的“类”,从而找到了自己的妈妈。这个童话故事中蕴含的编程哲理就是:对象的“类”是从无到有,又不断演化,最终又消失于无形之中的...

    “类”,的确可以帮助我们理解复杂的现实世界,这纷乱的现实世界也的确需要进行分类。但如果我们的思想被“类”束缚住了,“类”也就变成了“累”。想象一下,如果一个生命对象开始的时就被规定了固定的“类”,那么它还能演化吗?蝌蚪还能变成青蛙吗?还可以给孩子们讲小蝌蚪找妈妈的故事吗?

    所以,JavaScript中没有“类”,类已化于无形,与对象融为一体。正是由于放下了“类”这个概念,JavaScript的对象才有了其他编程语言所没有的活力。

    如果,此时你的内心深处开始有所感悟,那么你已经逐渐开始理解JavaScript的禅机了。

函数的魔力

    接下来,我们再讨论一下JavaScript函数的魔力吧。

    JavaScript的代码就只有function一种形式,function就是函数的类型。也许其他编程语言还有procedure或 method等代码概念,但在JavaScript里只有function一种形式。当我们写下一个函数的时候,只不过是建立了一个function类型的实体而已。请看下面的程序:
    function myfunc()
    {
        alert(
"hello");
    };
    
    alert(
typeof(myfunc));

    这个代码运行之后可以看到typeof(myfunc)返回的是function。以上的函数写法我们称之为“定义式”的,如果我们将其改写成下面的“变量式”的,就更容易理解了:
    var myfunc = function ()
        {
            alert(
"hello");
        };
    
    alert(
typeof(myfunc));

    这里明确定义了一个变量myfunc,它的初始值被赋予了一个function的实体。因此,typeof(myfunc)返回的也是function。其实,这两种函数的写法是等价的,除了一点细微差别,其内部实现完全相同。也就是说,我们写的这些JavaScript函数只是一个命了名的变量而已,其变量类型即为function,变量的值就是我们编写的函数代码体。

    聪明的你或许立即会进一步的追问:既然函数只是变量,那么变量就可以被随意赋值并用到任意地方啰?

    我们来看看下面的代码:
    var myfunc = function ()
        {
            alert(
"hello");
        };
    myfunc(); 
//第一次调用myfunc,输出hello
    
    myfunc 
= function ()
        {
            alert(
"yeah");
        };    
    myfunc(); 
//第二次调用myfunc,将输出yeah

    这个程序运行的结果告诉我们:答案是肯定的!在第一次调用函数之后,函数变量又被赋予了新的函数代码体,使得第二次调用该函数时,出现了不同的输出。

    好了,我们又来把上面的代码改成第一种定义式的函数形式:
    function myfunc ()
    {
        alert(
"hello");
    };
    myfunc(); 
//这里调用myfunc,输出yeah而不是hello
    
    
function myfunc ()
    {
        alert(
"yeah");
    };    
    myfunc(); 
//这里调用myfunc,当然输出yeah

    按理说,两个签名完全相同的函数,在其他编程语言中应该是非法的。但在JavaScript中,这没错。不过,程序运行之后却发现一个奇怪的现象:两次调用都只是最后那个函数里输出的值!显然第一个函数没有起到任何作用。这又是为什么呢?

    原来,JavaScript执行引擎并非一行一行地分析和执行程序,而是一段一段地分析执行的。而且,在同一段程序的分析执行中,定义式的函数语句会被提取出来优先执行。函数定义执行完之后,才会按顺序执行其他语句代码。也就是说,在第一次调用myfunc之前,第一个函数语句定义的代码逻辑,已被第二个函数定义语句覆盖了。所以,两次都调用都是执行最后一个函数逻辑了。

    如果把这个JavaScript代码分成两段,例如将它们写在一个html中,并用<script/>标签将其分成这样的两块:
<script>
    
function myfunc ()
    {
        alert(
"hello");
    };
    myfunc(); 
//这里调用myfunc,输出hello
</script>

<script>
    
function myfunc ()
    {
        alert(
"yeah");
    };    
    myfunc(); 
//这里调用myfunc,输出yeah
</script>

    这时,输出才是各自按顺序来的,也证明了JavaScript的确是一段段地执行的。

    一段代码中的定义式函数语句会优先执行,这似乎有点象静态语言的编译概念。所以,这一特征也被有些人称为:JavaScript的“预编译”。

    大多数情况下,我们也没有必要去纠缠这些细节问题。只要你记住一点:JavaScript里的代码也是一种数据,同样可以被任意赋值和修改的,而它的值就是代码的逻辑。只是,与一般数据不同的是,函数是可以被调用执行的。

    不过,如果JavaScript函数仅仅只有这点道行的话,这与C++的函数指针,DELPHI的方法指针,C#的委托相比,又有啥稀奇嘛!然而,JavaScript函数的神奇之处还体现在另外两个方面:一是函数function类型本身也具有对象化的能力,二是函数function与对象 object超然的结合能力。

奇妙的对象

    先来说说函数的对象化能力。

    任何一个函数都可以为其动态地添加或去除属性,这些属性可以是简单类型,可以是对象,也可以是其他函数。也就是说,函数具有对象的全部特征,你完全可以把函数当对象来用。其实,函数就是对象,只不过比一般的对象多了一个括号“()”操作符,这个操作符用来执行函数的逻辑。即,函数本身还可以被调用,一般对象却不可以被调用,除此之外完全相同。请看下面的代码:
    function Sing()
    {
        
with(arguments.callee)
          alert(author 
+ "" + poem);
    };
    Sing.author 
= "李白";
    Sing.poem 
= "汉家秦地月,流影照明妃。一上玉关道,天涯去不归";
    Sing();
    Sing.author 
= "李战";
    Sing.poem 
= "日出汉家天,月落阴山前。女儿琵琶怨,已唱三千年";
    Sing();

    在这段代码中,Sing函数被定义后,又给Sing函数动态地增加了author和poem属性。将author和poem属性设为不同的作者和诗句,在调用Sing()时就能显示出不同的结果。这个示例用一种诗情画意的方式,让我们理解了JavaScript函数就是对象的本质,也感受到了JavaScript语言的优美。

    好了,以上的讲述,我们应该算理解了function类型的东西都是和object类型一样的东西,这种东西被我们称为“对象”。我们的确可以这样去看待这些“对象”,因为它们既有“属性”也有“方法”嘛。但下面的代码又会让我们产生新的疑惑:
    var anObject = {};  //一个对象
    anObject.aProperty = "Property of object";  //对象的一个属性
    anObject.aMethod = function(){alert("Method of object")}; //对象的一个方法
    //主要看下面:
    alert(anObject["aProperty"]);   //可以将对象当数组以属性名作为下标来访问属性
    anObject["aMethod"]();          //可以将对象当数组以方法名作为下标来调用方法
    forvar s in anObject)           //遍历对象的所有属性和方法进行迭代化处理
        alert(s + " is a " + typeof(anObject[s]));

    同样对于function类型的对象也是一样:
    var aFunction = function() {};  //一个函数
    aFunction.aProperty = "Property of function";  //函数的一个属性
    aFunction.aMethod = function(){alert("Method of function")}; //函数的一个方法
    //主要看下面:
    alert(aFunction["aProperty"]);   //可以将函数当数组以属性名作为下标来访问属性
    aFunction["aMethod"]();          //可以将函数当数组以方法名作为下标来调用方法
    forvar s in aFunction)           //遍历函数的所有属性和方法进行迭代化处理
        alert(s + " is a " + typeof(aFunction[s]));

    是的,对象和函数可以象数组一样,用属性名或方法名作为下标来访问并处理。那么,它到底应该算是数组呢,还是算对象?

    我们知道,数组应该算是线性数据结构,线性数据结构一般有一定的规律,适合进行统一的批量迭代操作等,有点像波。而对象是离散数据结构,适合描述分散的和个性化的东西,有点像粒子。因此,我们也可以这样问:JavaScript里的对象到底是波还是粒子?

    如果存在对象量子论,那么答案一定是:波粒二象性!

    因此,JavaScript里的函数和对象既有对象的特征也有数组的特征。这里的数组被称为“字典”,一种可以任意伸缩的名称值对儿的集合。其实, object和function的内部实现就是一个字典结构,但这种字典结构却通过严谨而精巧的语法表现出了丰富的外观。正如量子力学在一些地方用粒子来解释和处理问题,而在另一些地方却用波来解释和处理问题。你也可以在需要的时候,自由选择用对象还是数组来解释和处理问题。只要善于把握JavaScript的这些奇妙特性,就可以编写出很多简洁而强大的代码来。

放下对象

    我们再来看看function与object的超然结合吧。

    在面向对象的编程世界里,数据与代码的有机结合就构成了对象的概念。自从有了对象,编程世界就被划分成两部分,一个是对象内的世界,一个是对象外的世界。对象天生具有自私的一面,外面的世界未经允许是不可访问对象内部的。对象也有大方的一面,它对外提供属性和方法,也为他人服务。不过,在这里我们要谈到一个有趣的问题,就是“对象的自我意识”。

    什么?没听错吧?对象有自我意识?

    可能对许多程序员来说,这的确是第一次听说。不过,请君看看C++、C#和Java的this,DELPHI的self,还有VB的me,或许你会恍然大悟!当然,也可能只是说句“不过如此”而已。

    然而,就在对象将世界划分为内外两部分的同时,对象的“自我”也就随之产生。“自我意识”是生命的最基本特征!正是由于对象这种强大的生命力,才使得编程世界充满无限的生机和活力。

    但对象的“自我意识”在带给我们快乐的同时也带来了痛苦和烦恼。我们给对象赋予了太多欲望,总希望它们能做更多的事情。然而,对象的自私使得它们互相争抢系统资源,对象的自负让对象变得复杂和臃肿,对象的自欺也往往带来挥之不去的错误和异常。我们为什么会有这么多的痛苦和烦恼呢?
 
    为此,有一个人,在对象树下,整整想了九九八十一天,终于悟出了生命的痛苦来自于欲望,但究其欲望的根源是来自于自我意识。于是他放下了“自我”,在对象树下成了佛,从此他开始普度众生,传播真经。他的名字就叫释迦摩尼,而《JavaScript真经》正是他所传经书中的一本。

    JavaScript中也有this,但这个this却与C++、C#或Java等语言的this不同。一般编程语言的this就是对象自己,而 JavaScript的this却并不一定!this可能是我,也可能是你,可能是他,反正是我中有你,你中有我,这就不能用原来的那个“自我”来理解 JavaScript这个this的含义了。为此,我们必须首先放下原来对象的那个“自我”。

    我们来看下面的代码:
    function WhoAmI()       //定义一个函数WhoAmI
    {
        alert(
"I'm " + this.name + " of " + typeof(this));
    };
    
    WhoAmI();   
//此时是this当前这段代码的全局对象,在浏览器中就是window对象,其name属性为空字符串。输出:I'm of object

    
var BillGates = {name: "Bill Gates"};
    BillGates.WhoAmI 
= WhoAmI;  //将函数WhoAmI作为BillGates的方法。
    BillGates.WhoAmI();         //此时的this是BillGates。输出:I'm Bill Gates of object
    
    
var SteveJobs = {name: "Steve Jobs"};
    SteveJobs.WhoAmI 
= WhoAmI;  //将函数WhoAmI作为SteveJobs的方法。
    SteveJobs.WhoAmI();         //此时的this是SteveJobs。输出:I'm Steve Jobs of object

    WhoAmI.call(BillGates);     
//直接将BillGates作为this,调用WhoAmI。输出:I'm Bill Gates of object
    WhoAmI.call(SteveJobs);     //直接将SteveJobs作为this,调用WhoAmI。输出:I'm Steve Jobs of object
    
    BillGates.WhoAmI.call(SteveJobs);   
//将SteveJobs作为this,却调用BillGates的WhoAmI方法。输出:I'm Steve Jobs of object
    SteveJobs.WhoAmI.call(BillGates);   //将BillGates作为this,却调用SteveJobs的WhoAmI方法。输出:I'm Bill Gates of object

    WhoAmI.WhoAmI 
= WhoAmI;     //将WhoAmI函数设置为自身的方法。
    WhoAmI.name = "WhoAmI";
    WhoAmI.WhoAmI();            
//此时的this是WhoAmI函数自己。输出:I'm WhoAmI of function
        
    ({name: 
"nobody", WhoAmI: WhoAmI}).WhoAmI();    //临时创建一个匿名对象并设置属性后调用WhoAmI方法。输出:I'm nobody of object

    从上面的代码可以看出,同一个函数可以从不同的角度来调用,this并不一定是函数本身所属的对象。this只是在任意对象和function元素结合时的一个概念,是种结合比起一般对象语言的默认结合更加灵活,显得更加超然和洒脱。

    在JavaScript函数中,你只能把this看成当前要服务的“这个”对象。this是一个特殊的内置参数,根据this参数,您可以访问到“这个”对象的属性和方法,但却不能给this参数赋值。在一般对象语言中,方法体代码中的this可以省略的,成员默认都首先是“自己”的。但JavaScript却不同,由于不存在“自我”,当访问“这个”对象时,this不可省略!

    JavaScript提供了传递this参数的多种形式和手段,其中,象BillGates.WhoAmI()和SteveJobs.WhoAmI()这种形式,是传递this参数最正规的形式,此时的this就是函数所属的对象本身。而大多数情况下,我们也几乎很少去采用那些借花仙佛的调用形式。但只我们要明白JavaScript的这个“自我”与其他编程语言的“自我”是不同的,这是一个放下了的“自我”,这就是JavaScript特有的世界观。

对象素描

    已经说了许多了许多话题了,但有一个很基本的问题我们忘了讨论,那就是:怎样建立对象?

    在前面的示例中,我们已经涉及到了对象的建立了。我们使用了一种被称为JavaScript Object Notation(缩写JSON)的形式,翻译为中文就是“JavaScript对象表示法”。

    JSON为创建对象提供了非常简单的方法。例如,
    创建一个没有任何属性的对象:
var o = {};

    创建一个对象并设置属性及初始值:
var person = {name: "Angel", age: 18, married: false};

    创建一个对象并设置属性和方法:
var speaker = {text: "Hello World", say: function(){alert(this.text)}};

     创建一个更复杂的对象,嵌套其他对象和对象数组等:
    var company =
    {
        name: 
"Microsoft",
        product: 
"softwares",
        chairman: {name: 
"Bill Gates", age: 53, Married: true},
        employees: [{name: 
"Angel", age: 26, Married: false}, {name: "Hanson", age: 32, Marred: true}],
        readme: 
function() {document.write(this.name + " product " + this.product);}
    };

    JSON的形式就是用大括“{}”号包括起来的项目列表,每一个项目间并用逗号“,”分隔,而项目就是用冒号“:”分隔的属性名和属性值。这是典型的字典表示形式,也再次表明了 JavaScript里的对象就是字典结构。不管多么复杂的对象,都可以被一句JSON代码来创建并赋值。

    其实,JSON就是JavaScript对象最好的序列化形式,它比XML更简洁也更省空间。对象可以作为一个JSON形式的字符串,在网络间自由传递和交换信息。而当需要将这个JSON字符串变成一个JavaScript对象时,只需要使用eval函数这个强大的数码转换引擎,就立即能得到一个JavaScript内存对象。正是由于JSON的这种简单朴素的天生丽质,才使得她在AJAX舞台上成为璀璨夺目的明星。

    JavaScript就是这样,把面向对象那些看似复杂的东西,用及其简洁的形式表达出来。卸下对象浮华的浓妆,还对象一个眉目清晰!

构造对象
 
    好了,接下我们来讨论一下对象的另一种创建方法。

    除JSON外,在JavaScript中我们可以使用new操作符结合一个函数的形式来创建对象。例如:
    function MyFunc() {};         //定义一个空函数
    var anObj = new MyFunc();  //使用new操作符,借助MyFun函数,就创建了一个对象

    JavaScript的这种创建对象的方式可真有意思,如何去理解这种写法呢?
 
   其实,可以把上面的代码改写成这种等价形式:
    function MyFunc(){};
    
var anObj = {};     //创建一个对象
    MyFunc.call(anObj); //将anObj对象作为this指针调用MyFunc函数

    我们就可以这样理解,JavaScript先用new操作符创建了一个对象,紧接着就将这个对象作为this参数调用了后面的函数。其实,JavaScript内部就是这么做的,而且任何函数都可以被这样调用!但从 “anObj = new MyFunc()” 这种形式,我们又看到一个熟悉的身影,C++和C#不就是这样创建对象的吗?原来,条条大路通灵山,殊途同归啊!

    君看到此处也许会想,我们为什么不可以把这个MyFunc当作构造函数呢?恭喜你,答对了!JavaScript也是这么想的!请看下面的代码: 
 1     function Person(name)   //带参数的构造函数
 2     {
 3         this.name = name;   //将参数值赋给给this对象的属性
 4         this.SayHello = function() {alert("Hello, I'm " + this.name);};   //给this对象定义一个SayHello方法。
 5     };
 6 
 7     function Employee(name, salary)     //子构造函数
 8     {
 9         Person.call(this, name);        //将this传给父构造函数
10         this.salary = salary;       //设置一个this的salary属性
11         this.ShowMeTheMoney = function() {alert(this.name + " $" + this.salary);};  //添加ShowMeTheMoney方法。
12     };
13     
14     var BillGates = new Person("Bill Gates");   //用Person构造函数创建BillGates对象
15     var SteveJobs = new Employee("Steve Jobs"1234);   //用Empolyee构造函数创建SteveJobs对象
16 
17     BillGates.SayHello();   //显示:I'm Bill Gates
18     SteveJobs.SayHello();   //显示:I'm Steve Jobs
19     SteveJobs.ShowMeTheMoney();   //显示:Steve Jobs $1234
20 
21     alert(BillGates.constructor == Person);  //显示:true
22     alert(SteveJobs.constructor == Employee);  //显示:true
23     
24     alert(BillGates.SayHello == SteveJobs.SayHello); //显示:false

    这段代码表明,函数不但可以当作构造函数,而且还可以带参数,还可以为对象添加成员和方法。其中的第9行,Employee构造函数又将自己接收的this作为参数调用Person构造函数,这就是相当于调用基类的构造函数。第21、22行还表明这样一个意思:BillGates是由Person构造的,而SteveJobs是由Employee构造的。对象内置的constructor属性还指明了构造对象所用的具体函数!

    其实,如果你愿意把函数当作“类”的话,她就是“类”,因为她本来就有“类”的那些特征。难道不是吗?她生出的儿子各个都有相同的特征,而且构造函数也与类同名嘛!

    但要注意的是,用构造函数操作this对象创建出来的每一个对象,不但具有各自的成员数据,而且还具有各自的方法数据。换句话说,方法的代码体(体现函数逻辑的数据)在每一个对象中都存在一个副本。尽管每一个代码副本的逻辑是相同的,但对象们确实是各自保存了一份代码体。上例中的最后一句说明了这一实事,这也解释了JavaScript中的函数就是对象的概念。

    同一类的对象各自有一份方法代码显然是一种浪费。在传统的对象语言中,方法函数并不象JavaScript那样是个对象概念。即使也有象函数指针、方法指针或委托那样的变化形式,但其实质也是对同一份代码的引用。一般的对象语言很难遇到这种情况。

    不过,JavaScript语言有大的灵活性。我们可以先定义一份唯一的方法函数体,并在构造this对象时使用这唯一的函数对象作为其方法,就能共享方法逻辑。例如:
    function SayHello()     //先定义一份SayHello函数代码
    {
        alert(
"Hello, I'm " + this.name);
    };
    
    
function Person(name)   //带参数的构造函数
    {
        
this.name = name;   //将参数值赋给给this对象的属性
        this.SayHello = SayHello;   //给this对象SayHello方法赋值为前面那份SayHello代码。
    };

    
var BillGates = new Person("Bill Gates");   //创建BillGates对象
    var SteveJobs = new Person("Steve Jobs");   //创建SteveJobs对象
    
    alert(BillGates.SayHello 
== SteveJobs.SayHello); //显示:true

    其中,最后一行的输出结果表明两个对象确实共享了一个函数对象。虽然,这段程序达到了共享了一份方法代码的目的,但却不怎么优雅。因为,定义SayHello方法时反映不出其与Person类的关系。“优雅”这个词用来形容代码,也不知道是谁先提出来的。不过,这个词反映了程序员已经从追求代码的正确、高效、可靠和易读等基础上,向着追求代码的美观感觉和艺术境界的层次发展,程序人生又多了些浪漫色彩。

   显然,JavaScript早想到了这一问题,她的设计者们为此提供了一个有趣的prototype概念。

初看原型

    prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义。JavaScript中的prototype概念恰如其分地反映了这个词的内含,我们不能将其理解为C++的prototype那种预先声明的概念。

    JavaScript的所有function类型的对象都有一个prototype属性。这个prototype属性本身又是一个object类型的对象,因此我们也可以给这个prototype对象添加任意的属性和方法。既然prototype是对象的“原型”,那么由该函数构造出来的对象应该都会具有这个“原型”的特性。事实上,在构造函数的prototype上定义的所有属性和方法,都是可以通过其构造的对象直接访问和调用的。也可以这么说,prototype提供了一群同类对象共享属性和方法的机制。

    我们先来看看下面的代码:
    function Person(name)
    {
        
this.name = name;   //设置对象属性,每个对象各自一份属性数据
    };
    
    Person.prototype.SayHello 
= function()  //给Person函数的prototype添加SayHello方法。
    {
        alert(
"Hello, I'm " + this.name);
    }

    
var BillGates = new Person("Bill Gates");   //创建BillGates对象
    var SteveJobs = new Person("Steve Jobs");   //创建SteveJobs对象

    BillGates.SayHello();   
//通过BillGates对象直接调用到SayHello方法
    SteveJobs.SayHello();   //通过SteveJobs对象直接调用到SayHello方法

    alert(BillGates.SayHello 
== SteveJobs.SayHello); //因为两个对象是共享prototype的SayHello,所以显示:true

    程序运行的结果表明,构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。显然,把方法设置到prototype的写法显得优雅多了,尽管调用形式没有变,但逻辑上却体现了方法与类的关系,相对前面的写法,更容易理解和组织代码。

    那么,对于多层次类型的构造函数情况又如何呢?

    我们再来看下面的代码:
 1     function Person(name)   //基类构造函数
 2     {
 3         this.name = name;
 4     };
 5     
 6     Person.prototype.SayHello = function()  //给基类构造函数的prototype添加方法
 7     {
 8         alert("Hello, I'm " + this.name);
 9     };
10     
11     function Employee(name, salary) //子类构造函数
12     {
13         Person.call(this, name);    //调用基类构造函数
14         this.salary = salary;
15     };
16     
17     Employee.prototype = new Person();  //建一个基类的对象作为子类原型的原型,这里很有意思
18     
19     Employee.prototype.ShowMeTheMoney = function()  //给子类添构造函数的prototype添加方法
20     {
21         alert(this.name + " $" + this.salary);
22     };
23 
24     var BillGates = new Person("Bill Gates");   //创建基类Person的BillGates对象
25     var SteveJobs = new Employee("Steve Jobs"1234);   //创建子类Employee的SteveJobs对象
26 
27     BillGates.SayHello();       //通过对象直接调用到prototype的方法
28     SteveJobs.SayHello();       //通过子类对象直接调用基类prototype的方法,关注!
29     SteveJobs.ShowMeTheMoney(); //通过子类对象直接调用子类prototype的方法
30 
31     alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true,表明prototype的方法是共享的

    这段代码的第17行,构造了一个基类的对象,并将其设为子类构造函数的prototype,这是很有意思的。这样做的目的就是为了第28行,通过子类对象也可以直接调用基类prototype的方法。为什么可以这样呢?

    原来,在JavaScript中,prototype不但能让对象共享自己财富,而且prototype还有寻根问祖的天性,从而使得先辈们的遗产可以代代相传。当从一个对象那里读取属性或调用方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找;如果prototype没有,又会去prototype自己关联的前辈prototype那里寻找,直到找到或追溯过程结束为止。

    在JavaScript内部,对象的属性和方法追溯机制是通过所谓的prototype链来实现的。当用new操作符构造对象时,也会同时将构造函数的prototype对象指派给新创建的对象,成为该对象内置的原型对象。对象内置的原型对象应该是对外不可见的,尽管有些浏览器(如Firefox)可以让我们访问这个内置原型对象,但并不建议这样做。内置的原型对象本身也是对象,也有自己关联的原型对象,这样就形成了所谓的原型链。

    在原型链的最末端,就是Object构造函数prototype属性指向的那一个原型对象。这个原型对象是所有对象的最老祖先,这个老祖宗实现了诸如toString等所有对象天生就该具有的方法。其他内置构造函数,如Function, Boolean, String, Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些特征。

    这不就是“继承”吗?是的,这就是“继承”,是JavaScript特有的“原型继承”。

    “原型继承”是慈祥而又严厉的。原形对象将自己的属性和方法无私地贡献给孩子们使用,也并不强迫孩子们必须遵从,允许一些顽皮孩子按自己的兴趣和爱好独立行事。从这点上看,原型对象是一位慈祥的母亲。然而,任何一个孩子虽然可以我行我素,但却不能动原型对象既有的财产,因为那可能会影响到其他孩子的利益。从这一点上看,原型对象又象一位严厉的父亲。我们来看看下面的代码就可以理解这个意思了:
    function Person(name)
    {
        
this.name = name;
    };
    
    Person.prototype.company 
= "Microsoft"//原型的属性
    
    Person.prototype.SayHello 
= function()  //原型的方法
    {
        alert(
"Hello, I'm " + this.name + " of " + this.company);
    };
    
    
var BillGates = new Person("Bill Gates");
    BillGates.SayHello();   
//由于继承了原型的东西,规规矩矩输出:Hello, I'm Bill Gates
    
    
var SteveJobs = new Person("Steve Jobs");
    SteveJobs.company 
= "Apple";    //设置自己的company属性,掩盖了原型的company属性
    SteveJobs.SayHello = function() //实现了自己的SayHello方法,掩盖了原型的SayHello方法
    {
        alert(
"Hi, " + this.name + " like " + this.company + ", ha ha ha ");
    };

    SteveJobs.SayHello();   
//都是自己覆盖的属性和方法,输出:Hi, Steve Jobs like Apple, ha ha ha 
    
    BillGates.SayHello();   
//SteveJobs的覆盖没有影响原型对象,BillGates还是按老样子输出

    对象可以掩盖原型对象的那些属性和方法,一个构造函数原型对象也可以掩盖上层构造函数原型对象既有的属性和方法。这种掩盖其实只是在对象自己身上创建了新的属性和方法,只不过这些属性和方法与原型对象的那些同名而已。JavaScript就是用这简单的掩盖机制实现了对象的“多态”性,与静态对象语言的虚函数和重载(override)概念不谋而合。

    然而,比静态对象语言更神奇的是,我们可以随时给原型对象动态添加新的属性和方法,从而动态地扩展基类的功能特性。这在静态对象语言中是很难想象的。我们来看下面的代码:
    function Person(name)
    {
        
this.name = name;
    };
    
    Person.prototype.SayHello 
= function()  //建立对象前定义的方法
    {
        alert(
"Hello, I'm " + this.name);
    };
    
    
var BillGates = new Person("Bill Gates");   //建立对象
    
    BillGates.SayHello();
    
    Person.prototype.Retire 
= function()    //建立对象后再动态扩展原型的方法
    {
        alert(
"Poor " + this.name + ", bye bye!");
    };
    
    BillGates.Retire(); 
//动态扩展的方法即可被先前建立的对象立即调用

    阿弥佗佛,原型继承竟然可以玩出有这样的法术!

原型扩展

    想必君的悟性极高,可能你会这样想:如果在JavaScript内置的那些如Object和Function等函数的prototype上添加些新的方法和属性,是不是就能扩展JavaScript的功能呢?

    那么,恭喜你,你得到了!

    在AJAX技术迅猛发展的今天,许多成功的AJAX项目的JavaScript运行库都大量扩展了内置函数的prototype功能。比如微软的ASP.NET AJAX,就给这些内置函数及其prototype添加了大量的新特性,从而增强了JavaScript的功能。

    我们来看一段摘自MicrosoftAjax.debug.js中的代码:

String.prototype.trim = function String$trim() {
    
if (arguments.length !== 0throw Error.parameterCount();
    
return this.replace(/^\s+|\s+$/g, '');
}

    这段代码就是给内置String函数的prototype扩展了一个trim方法,于是所有的String类对象都有了trim方法了。有了这个扩展,今后要去除字符串两段的空白,就不用再分别处理了,因为任何字符串都有了这个扩展功能,只要调用即可,真的很方便。

    当然,几乎很少有人去给Object的prototype添加方法,因为那会影响到所有的对象,除非在你的架构中这种方法的确是所有对象都需要的。

    前两年,微软在设计AJAX类库的初期,用了一种被称为“闭包”(closure)的技术来模拟“类”。其大致模型如下:
    function Person(firstName, lastName, age)
    {
        
//私有变量:
        var _firstName = firstName;
        
var _lastName = lastName;

        
//公共变量:
        this.age = age;

        
//方法:
        this.getName = function()
        {
            
return(firstName + " " + lastName);
        };
        
this.SayHello = function()
        {
            alert(
"Hello, I'm " + firstName + " " + lastName);
        };
    };
    
    
var BillGates = new Person("Bill""Gates"53);
    
var SteveJobs = new Person("Steve""Jobs"53);
    
    BillGates.SayHello();
    SteveJobs.SayHello();
    alert(BillGates.getName() 
+ " " + BillGates.age);
    alert(BillGates.firstName);     
//这里不能访问到私有变量

    很显然,这种模型的类描述特别象C#语言的描述形式,在一个构造函数里依次定义了私有成员、公共属性和可用的方法,显得非常优雅嘛。特别是“闭包”机制可以模拟对私有成员的保护机制,做得非常漂亮。

    所谓的“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层外层函数体中的临时变量。这使得只要目标对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的。的确很巧妙!

    但是前面我们说过,给每一个对象设置一份方法是一种很大的浪费。还有,“闭包”这种间接保持变量值的机制,往往会给JavaSript的垃圾回收器制造难题。特别是遇到对象间复杂的循环引用时,垃圾回收的判断逻辑非常复杂。无独有偶,IE浏览器早期版本确实存在JavaSript垃圾回收方面的内存泄漏问题。再加上“闭包”模型在性能测试方面的表现不佳,微软最终放弃了“闭包”模型,而改用“原型”模型。正所谓“有得必有失”嘛。

    原型模型需要一个构造函数来定义对象的成员,而方法却依附在该构造函数的原型上。大致写法如下:
    //定义构造函数
    function Person(name)
    {
        
this.name = name;   //在构造函数中定义成员
    };
    
    
//方法定义到构造函数的prototype上
    Person.prototype.SayHello = function()
    {
        alert(
"Hello, I'm " + this.name);
    };    
    
    
//子类构造函数
    function Employee(name, salary)
    {
        Person.call(
this, name);    //调用上层构造函数
        this.salary = salary;       //扩展的成员
    };
    
    
//子类构造函数首先需要用上层构造函数来建立prototype对象,实现继承的概念
    Employee.prototype = new Person()   //只需要其prototype的方法,此对象的成员没有任何意义!
    
    
//子类方法也定义到构造函数之上
    Employee.prototype.ShowMeTheMoney = function()
    {
        alert(
this.name + " $" + this.salary);
    };
    
    
var BillGates = new Person("Bill Gates");
    BillGates.SayHello();    
    
    
var SteveJobs = new Employee("Steve Jobs"1234);
    SteveJobs.SayHello();
    SteveJobs.ShowMeTheMoney();

    原型类模型虽然不能模拟真正的私有变量,而且也要分两部分来定义类,显得不怎么“优雅”。不过,对象间的方法是共享的,不会遇到垃圾回收问题,而且性能优于“闭包”模型。正所谓“有失必有得”嘛。

    在原型模型中,为了实现类继承,必须首先将子类构造函数的prototype设置为一个父类的对象实例。创建这个父类对象实例的目的就是为了构成原型链,以起到共享上层原型方法作用。但创建这个实例对象时,上层构造函数也会给它设置对象成员,这些对象成员对于继承来说是没有意义的。虽然,我们也没有给构造函数传递参数,但确实创建了若干没有用的成员,尽管其值是undefined,这也是一种浪费啊。

    唉!世界上没有完美的事情啊!

原型真谛

    正当我们感概万分时,天空中一道红光闪过,祥云中出现了观音菩萨。只见她手持玉净瓶,轻拂翠柳枝,洒下几滴甘露,顿时让JavaScript又添新的灵气。

    观音洒下的甘露在JavaScript的世界里凝结成块,成为了一种称为“语法甘露”的东西。这种语法甘露可以让我们编写的代码看起来更象对象语言。

    要想知道这“语法甘露”为何物,就请君侧耳细听。

    在理解这些语法甘露之前,我们需要重新再回顾一下JavaScript构造对象的过程。

    我们已经知道,用 var anObject = new aFunction() 形式创建对象的过程实际上可以分为三步:第一步是建立一个新对象;第二步将该对象内置的原型对象设置为构造函数prototype引用的那个原型对象;第三步就是将该对象作为this参数调用构造函数,完成成员设置等初始化工作。对象建立之后,对象上的任何访问和操作都只与对象自身及其原型链上的那串对象有关,与构造函数再扯不上关系了。换句话说,构造函数只是在创建对象时起到介绍原型对象和初始化对象两个作用。

    那么,我们能否自己定义一个对象来当作原型,并在这个原型上描述类,然后将这个原型设置给新创建的对象,将其当作对象的类呢?我们又能否将这个原型中的一个方法当作构造函数,去初始化新建的对象呢?例如,我们定义这样一个原型对象:

    var Person =  //定义一个对象来作为原型类
    {
        Create: 
function(name, age)  //这个当构造函数
        {
            
this.name = name;
            
this.age = age;
        },
        SayHello: 
function()  //定义方法
        {
            alert(
"Hello, I'm " + this.name);
        },
        HowOld: 
function()  //定义方法
        {
            alert(
this.name + " is " + this.age + " years old.");
        }
    };

    这个JSON形式的写法多么象一个C#的类啊!既有构造函数,又有各种方法。如果可以用某种形式来创建对象,并将对象的内置的原型设置为上面这个“类”对象,不就相当于创建该类的对象了吗?

    但遗憾的是,我们几乎不能访问到对象内置的原型属性!尽管有些浏览器可以访问到对象的内置原型,但这样做的话就只能限定了用户必须使用那种浏览器。这也几乎不可行。

    那么,我们可不可以通过一个函数对象来做媒介,利用该函数对象的prototype属性来中转这个原型,并用new操作符传递给新建的对象呢?

    其实,象这样的代码就可以实现这一目标:

    function anyfunc(){};           //定义一个函数躯壳
    anyfunc.prototype = Person;     //将原型对象放到中转站prototype
    var BillGates = new anyfunc();  //新建对象的内置原型将是我们期望的原型对象

    不过,这个anyfunc函数只是一个躯壳,在使用过这个躯壳之后它就成了多余的东西了,而且这和直接使用构造函数来创建对象也没啥不同,有点不爽。

    可是,如果我们将这些代码写成一个通用函数,而那个函数躯壳也就成了函数内的函数,这个内部函数不就可以在外层函数退出作用域后自动消亡吗?而且,我们可以将原型对象作为通用函数的参数,让通用函数返回创建的对象。我们需要的就是下面这个形式:

    function New(aClass, aParams)    //通用创建函数
    {
        
function new_()     //定义临时的中转函数壳
        {
            aClass.Create.apply(
this, aParams);   //调用原型中定义的的构造函数,中转构造逻辑及构造参数
        };
        new_.prototype 
= aClass;    //准备中转原型对象
        return new new_();          //返回建立最终建立的对象
    };
    
    
var Person =        //定义的类
    {
        Create: 
function(name, age)
        {
            
this.name = name;
            
this.age = age;
        },
        SayHello: 
function()
        {
            alert(
"Hello, I'm " + this.name);
        },
        HowOld: 
function()
        {
            alert(
this.name + " is " + this.age + " years old.");
        }
    };
    
    
var BillGates = New(Person, ["Bill Gates"53]);  //调用通用函数创建对象,并以数组形式传递构造参数
    BillGates.SayHello();
    BillGates.HowOld();

    alert(BillGates.constructor 
== Object);     //输出:true

    这里的通用函数New()就是一个“语法甘露”!这个语法甘露不但中转了原型对象,还中转了构造函数逻辑及构造参数。

    有趣的是,每次创建完对象退出New函数作用域时,临时的new_函数对象会被自动释放。由于new_的prototype属性被设置为新的原型对象,其原来的原型对象和new_之间就已解开了引用链,临时函数及其原来的原型对象都会被正确回收了。上面代码的最后一句证明,新创建的对象的constructor属性返回的是Object函数。其实新建的对象自己及其原型里没有constructor属性,那返回的只是最顶层原型对象的构造函数,即Object。

    有了New这个语法甘露,类的定义就很像C#那些静态对象语言的形式了,这样的代码显得多么文静而优雅啊!

    当然,这个代码仅仅展示了“语法甘露”的概念。我们还需要多一些的语法甘露,才能实现用简洁而优雅的代码书写类层次及其继承关系。好了,我们再来看一个更丰富的示例吧:

    //语法甘露:
    var object =    //定义小写的object基本类,用于实现最基础的方法等
    {
        isA: 
function(aType)   //一个判断类与类之间以及对象与类之间关系的基础方法
        {
            
var self = this;
            
while(self)
            {
                
if (self == aType)
                  
return true;
                self 
= self.Type;
            };
            
return false;
        }
    };
    
    
function Class(aBaseClass, aClassDefine)    //创建类的函数,用于声明类及继承关系
    {
        
function class_()   //创建类的临时函数壳
        {
            
this.Type = aBaseClass;    //我们给每一个类约定一个Type属性,引用其继承的类
            for(var member in aClassDefine)
                
this[member] = aClassDefine[member];    //复制类的全部定义到当前创建的类
        };
        class_.prototype 
= aBaseClass;
        
return new class_();
    };
    
    
function New(aClass, aParams)   //创建对象的函数,用于任意类的对象创建
    {
        
function new_()     //创建对象的临时函数壳
        {
            
this.Type = aClass;    //我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类
            if (aClass.Create)
              aClass.Create.apply(
this, aParams);   //我们约定所有类的构造函数都叫Create,这和DELPHI比较相似
        };
        new_.prototype 
= aClass;
        
return new new_();
    };

    
//语法甘露的应用效果:    
    var Person = Class(object,      //派生至object基本类
    {
        Create: 
function(name, age)
        {
            
this.name = name;
            
this.age = age;
        },
        SayHello: 
function()
        {
            alert(
"Hello, I'm " + this.name + "" + this.age + " years old.");
        }
    });
    
    
var Employee = Class(Person,    //派生至Person类,是不是和一般对象语言很相似?
    {
        Create: 
function(name, age, salary)
        {
            Person.Create.call(
this, name, age);  //调用基类的构造函数
            this.salary = salary;
        },
        ShowMeTheMoney: 
function()
        {
            alert(
this.name + " $" + this.salary);
        }
    });

    
var BillGates = New(Person, ["Bill Gates"53]);
    
var SteveJobs = New(Employee, ["Steve Jobs"531234]);
    BillGates.SayHello();
    SteveJobs.SayHello();
    SteveJobs.ShowMeTheMoney();
    
    
var LittleBill = New(BillGates.Type, ["Little Bill"6]);   //根据BillGate的类型创建LittleBill
    LittleBill.SayHello();
    
    alert(BillGates.isA(Person));       
//true
    alert(BillGates.isA(Employee));     //false
    alert(SteveJobs.isA(Person));       //true
    alert(Person.isA(Employee));        //false
    alert(Employee.isA(Person));        //true

    “语法甘露”不用太多,只要那么一点点,就能改观整个代码的易读性和流畅性,从而让代码显得更优雅。有了这些语法甘露,JavaScript就很像一般对象语言了,写起代码了感觉也就爽多了!

    令人高兴的是,受这些甘露滋养的JavaScript程序效率会更高。因为其原型对象里既没有了毫无用处的那些对象级的成员,而且还不存在constructor属性体,少了与构造函数间的牵连,但依旧保持了方法的共享性。这让JavaScript在追溯原型链和搜索属性及方法时,少费许多工夫啊。

    我们就把这种形式称为“甘露模型”吧!其实,这种“甘露模型”的原型用法才是符合prototype概念的本意,才是的JavaScript原型的真谛!

    想必微软那些设计AJAX架构的工程师看到这个甘露模型时,肯定后悔没有早点把AJAX部门从美国搬到咱中国的观音庙来,错过了观音菩萨的点化。当然,我们也只能是在代码的示例中,把Bill Gates当作对象玩玩,真要让他放弃上帝转而皈依我佛肯定是不容易的,机缘未到啊!如果哪天你在微软新出的AJAX类库中看到这种甘露模型,那才是真正的缘分!

编程的快乐

    在软件工业迅猛发展的今天,各式各样的编程语言层出不穷,新语言的诞生,旧语言的演化,似乎已经让我们眼花缭乱。为了适应面向对象编程的潮流,JavaScript语言也在向完全面向对象的方向发展,新的JavaScript标准已经从语义上扩展了许多面向对象的新元素。与此相反的是,许多静态的对象语言也在向JavaScript的那种简洁而幽雅的方向发展。例如,新版本的C#语言就吸收了JSON那样的简洁表示法,以及一些其他形式的JavaScript特性。

    我们应该看到,随着RIA(强互联应用)的发展和普及,AJAX技术也将逐渐淡出江湖,JavaScript也将最终消失或演化成其他形式的语言。但不管编程语言如何发展和演化,编程世界永远都会在“数据”与“代码”这千丝万缕的纠缠中保持着无限的生机。只要我们能看透这一点,我们就能很容易地学习和理解软件世界的各种新事物。不管是已熟悉的过程式编程,还是正在发展的函数式编程,以及未来量子纠缠态的大规模并行式编程,我们都有足够的法力来化解一切复杂的难题。

    佛最后淡淡地说:只要我们放下那些表面的“类”,放下那些对象的“自我”,就能达到一种“对象本无根,类型亦无形”的境界,从而将自我融入到整个宇宙的生命轮循环中。我们将没有自我,也没有自私的欲望,你就是我,我就是你,你中有我,我中有你。这时,我们再看这生机勃勃的编程世界时,我们的内心将自然生起无限的慈爱之心,这种慈爱之心不是虚伪而是真诚的。关爱他人就是关爱自己,就是关爱这世界中的一切。那么,我们的心是永远快乐的,我们的程序是永远快乐的,我们的类是永远快乐的,我们的对象也是永远快乐的。这就是编程的极乐!

    说到这里,在座的比丘都犹如醍醐灌顶,心中豁然开朗。看看左边这位早已喜不自禁,再看看右边那位也是心花怒放。

    蓦然回首时,唯见君拈花微笑...

原著:李战(leadzen).深圳 2008-2-23
【转载请注明作者及出处】
CNDEV提供PDF  下载

Feedback

评论共2页: 上一页 1 2 

#201楼    回复  引用  查看    

2008-04-07 15:52 by 狼Robot      
想问楼主一个问题.
function Hello(name){this.name = name;}
Hello.prototype.Start = function(){setTimeout("?.Show()",8000)}
Hello.prototype.Show = function(){alert(this.name);}

实际上我就是想实现8秒后执行对象的Show方法,但是我不知道问号(?)那里到底要怎么写,setTimeout后,this所代表的不是当前对象了,如果用this就会出错,望楼主能指点一下。万分感激。

#202楼 [楼主]   回复  引用  查看    

2008-04-07 20:06 by 李战      
@代码娇妻
上次到了青龙山,又说你去歌乐山了,师太,你找得我好苦啊!


@狼Robot
施主可能被代码困住了,请看好:

<script type="text/javascript">
function Hello(name){this.name = name;}

Hello.prototype.Start 
= function(){setTimeout(function(me){return function(){me.Show()}}(this), 5000);};
Hello.prototype.Show 
= function(){alert(this.name);}

var BillGates = new Hello("Bill Gates");
var SteveJobs = new Hello("Steve Jobs");

BillGates.Start();
SteveJobs.Start();

</script>

不好意思,胡乱写了一段,我自己都看不懂,天书啊!
望施主帮俺理一下。

@dudu
俺发现,博客园里不能象CNDEV那样建高楼,楼层超过200之后,楼号就不对了,第二页又从头开始了。不知道是否是BUG。
俺保证,俺真的不是无意的!


#203楼    回复  引用  查看    

2008-04-07 21:56 by 狼Robot      
高人就是高人,效果实现了,但我看了N遍试了N遍,就是悟不出其中的哲理,不知道楼主能不能再点化一下?
setTimeout(function(me){
return function(){
me.Show()
}
}(this), 5000);
这里我实在是看不明白。
开始我以为楼主是把Show的方法转换成一个字符串传给setTimeout,但是我试了一下,如果在Start后,没有输出alert期间,再改变name的值,最后的结果也是会变的。其中的奥妙,估计只有楼主明白。

#204楼    回复  引用  查看    

2008-04-07 22:25 by 天好蓝-崇崇      

顶200楼

#205楼    回复  引用  查看    

2008-04-07 22:44 by 狼Robot      
看来dudu得看看楼层的问题了,呵呵。

@天好蓝-崇崇
刚刚我看到的是你自己顶自己,现在你顶的是你发回复时的将来了。

#206楼    回复  引用  查看    

2008-04-07 22:52 by 狼Robot      
楼主,我等你等得好苦哇,上cndev也没看到你。

找了些资料,说setTimeout的第一个参数可以传function,但是我直接传this.Show的话,执行时的结果是空的。越来越迷糊了,楼主给我指个方向吧。苦海明灯啊。

#207楼    回复  引用  查看    

2008-04-08 00:13 by 狼Robot      
function Hello(name){this.name = name;}

Hello.prototype.Start = function(){
setTimeout(this.GetShow(this),3000);
}
Hello.prototype.Show = function(){alert(this.name);}
Hello.prototype.GetShow = function(variable){
return function()
{
variable.Show();
}
}

var hello = new Hello("robot");
hello.Start();
hello.name = "robot1";

似乎看出些眉目了,用GetShow方法取得一个function,传一个this代表当前对象,所以timeout时variable所代表的就是已声明的对象。但是还有点不明白,为什么我直接传this.Show作为setTimeout的第一个参数时结果是没有内容。

#208楼 [楼主]   回复  引用  查看    

2008-04-08 09:17 by 李战      
@狼Robot

嗯,嗯,嗯,

setTimeout和setInterval的第一个参数既可以传函数对象也可以传字符串,如果传字符串实际上也是相当于构造了一个匿名函数。

要让某个代码记住某个数据,并不留下痕迹的话,就是将该代码和该数据形成一个闭包。也就是把该代码写成函数内的一个函数,而该数据作为外层函数参数,这些数据又被内部函数引用了。当内部函数被当作返回值出来时,外层函数脱壳之后就消失了,而返回的函数还引用着原来的代码并间接引用着原来的数据,这就形成了闭包。

但在内层函数中,不能使用this,外层函数的那个this并没有隐含传递给内层函数,内层函数的this实际是window对象。因此,要给通过一个明确定义的参数来传this,比如me。

直接setTimeout(this.Show, 8000)呢,这个只是把this.Show函数体给了setTimeout,setTimeout又不会帮你记住this。所以,当时间条件触发时,执行引擎仅仅是调用Show(),而不是this.Show(),这时this是window对象,window对象确实有个name属性,但默认是“”,而不是undefined。


累死我了,一大早来回这么个长贴。

施主,化俺一顿斋饭吧。


#209楼    回复  引用  查看    

2008-04-08 10:42 by 李涛      
学习javascript,这篇文章足以!
李战,拜读你的大作了。
继续努力哦,希望有更好的文章出来,期待!

#210楼    回复  引用  查看    

2008-04-08 11:42 by 狼Robot      
看着楼主内层外层的,把我给弄得晕头转向了,但是依稀找到了些方向。先谢楼主了。呵呵。

#211楼    回复  引用  查看    

2008-04-08 11:51 by 狼Robot      
用楼主教的东西写的个简单的东西,晚上传个Demo给大家看看,代码就暂时不贴了,有些占版面。

#212楼    回复  引用  查看    

2008-04-08 11:51 by 狼Robot      
再次感谢楼主不吝赐教,在下感激不尽啊,斋饭就不在话下了,若楼主哪天有幸光临寒舍,我定亲自主厨,送楼主一餐美味。当然,前提是不能吃了之后赖着不走了,呵呵。我对自己的厨艺还是蛮有信心的。

#213楼    回复  引用  查看    

2008-04-15 00:09 by Flyingis      
上次看过忘记留言,好文!

#214楼    回复  引用    

2008-04-15 11:41 by Frank2008 [未注册用户]
受教了.
虽然在有的地方卡壳了..我想再研究一下...应该能过去....

哈哈..谢了..

#215楼    回复  引用  查看    

2008-04-15 13:20 by Confach      
收藏先!

#216楼    回复  引用  查看    

2008-04-16 10:23 by 王孟军!      
好文,毕竟精通JS的少

#217楼    回复  引用  查看    

2008-04-16 14:41 by e旋风      
太佩服了。

很久没有看到这么精彩的文章了

#218楼    回复  引用  查看    

2008-04-16 15:48 by 红色枫叶      
好东西,收藏了

#219楼    回复  引用    

2008-04-21 17:41 by 多了一点 [未注册用户]
这家伙...
这文章太好了。谢谢楼主的分享..

#220楼    回复  引用    

2008-04-22 09:46 by pigIdear [未注册用户]
众里寻他千百度,原来你在这里住

#221楼    回复  引用  查看    

2008-04-23 15:21 by ys_go      
好文章,好学问.

#222楼    回复  引用    

2008-04-23 19:09 by Tank [未注册用户]
這篇文章寫得太好了,使我明白了很多關於javascript對象的東西 ,謝謝!!

#223楼    回复  引用  查看    

2008-04-26 00:33 by 超晨      
终于把这篇文章看完了,只讲JS的本质,大呼过瘾啊,感觉自己基础差了些。原型还明白,到了甘露就看不下去了。看来需要找个例子来实践一下,要不光是B/S上简单的写个FUNCTION,两下又要忘了。

#224楼    回复  引用  查看    

2008-04-26 15:52 by 花非花      
先下个拜帖,起码的礼数还是要的。

#225楼    回复  引用  查看    

2008-04-29 16:00 by Phinecos(洞庭散人)      
还没涉及到自定义事件,还不够深入,继续努力

#226楼 [楼主]   回复  引用  查看    

2008-04-29 19:42 by 李战      
@Phinecos(洞庭散人)
您的教诲就是俺前进的动力。

#227楼    回复  引用  查看    

2008-04-29 21:18 by Phinecos(洞庭散人)      
@李战
惭愧,我也是近来读ThunderBird的源代码才发现人家把javascript用到如此地步,而我知道的不过皮毛而已

#228楼    回复  引用  查看    

2008-04-30 09:18 by 狼Robot      
期待李老师您的<悟透Javascript>正式出版.我一直在关注.哈哈.

#229楼    回复  引用    

2008-04-30 14:50 by 乔木 [未注册用户]
很幸运能够来到您的博客,
由衷的佩服您这种大话技术的风格,而且还可以把人带到了一个不可企及的地方,看完您的文章,有一种豁然开朗的感觉。
谢谢,希望您能够写出更好的文章,不断学习中。支持。。。

#230楼    回复  引用    

2008-05-07 17:09 by 陈镇 [未注册用户]
楼主真是高人,光是看了,楼主写的引子就受益匪浅,我正在学习JavaScript谢谢楼主的共享!!!!

#231楼    回复  引用  查看    

2008-05-09 09:59 by music000      
期待此书...

#232楼    回复  引用  查看    

2008-05-10 11:41 by 登山小熊      
太牛了,"甘露模型",谢谢楼主啊!

#233楼    回复  引用    

2008-05-10 19:33 by 小神啊 [未注册用户]
function New(aClass, aParams) //通用创建函数
{
function new_() //定义临时的中转函数壳
{
aClass.Create.apply(this, aParams); //调用原型中定义的的构造函数,中转构造逻辑及构造参数
};
new_.prototype = aClass; //准备中转原型对象
return new new_(); //返回建立最终建立的对象
};
var Person = //定义的类
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " + this.name);
},
HowOld: function()
{
alert(this.name + " is " + this.age + " years old.");
}
};
var BillGates = New(Person, ["Bill Gates", 53]); //调用通用函数创建对象,并以数组形式传递构造参数
BillGates.SayHello();
BillGates.HowOld();
alert(BillGates.constructor == Object); //输出:true
------------------------
为什么 最后的行的constructor 是object噢,我感觉应该是NEW_
??
望LZ给出点 constructor 的 解释
很佩服你

#234楼    回复  引用    

2008-05-11 14:38 by 小神啊 [未注册用户]
嘿嘿
我想明白了
真的很佩服 LZ
你的 那句
用水云般自在的禅心,书写诗情画意的程序人生...
很 good
才知道 程序 也可以 诗情画意

#235楼    回复  引用  查看    

2008-05-12 15:59 by 镜涛      
讲解的深刻透彻,文笔顺畅,几近完美!!

#236楼    回复  引用  查看    

2008-05-14 01:14 by xiaobaov2      
受教了

#237楼    回复  引用    

2008-05-18 15:22 by wanglei341125 [未注册用户]
绝了```

#238楼    回复  引用  查看    

2008-05-22 02:38 by KevinDiao      
楼主 强人呀!!

#239楼    回复  引用  查看    

2008-05-24 10:56 by 情缘      
<script type="text/javascript">
function Hello(name){this.name = name;}

Hello.prototype.Start = function(){
setTimeout(this.GetShow(this),3000);
}
Hello.prototype.Show = function(){alert(this.name);}

Hello.prototype.GetShow = function(variable){
return function()
{
variable.Show();
}
}

var hello = new Hello("robot");
hello.Start();
hello.name = "robot1";
</script>
博主的文章很不错,我还是有点不明白,将setTimeout(this.GetShow(this),3000); 改为setTimeout(this.Show(this),3000); 结果为什么不一样,请博主指点一下.

#240楼    回复  引用  查看    

2008-05-28 17:58 by KymoWang      
pdf太漂亮了!

#241楼    回复  引用    

2008-05-30 00:15 by 体彩 [未注册用户]
写的真精辟,受教了!

#242楼    回复  引用    

2008-06-01 16:17 by javascript爱好者 [未注册用户]
if (arguments.length !== 0) throw Error.parameterCount();

#243楼    回复  引用  查看    

2008-06-02 09:35 by 中华八爪      
PhoenixClass = {
/**
* Phoenix自定义Class,用于类的定义和继承定义。
* @param {Class} 基类(若无基类用于继承可省略)
* @param {Functions} 类成员集合
* @return {Class}
*/
create: function(){
var Class = function(){
return(this.initialize.apply(this, arguments));
};

var extended = {};
var parent;
for (var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] == "function") {
// 获取基类的prototype
parent = arguments[i].prototype;
/**
* 保存继承来源树
*/
extended.baseClass = arguments[i];
}
else {
parent = arguments[i];
}
SpatialBrowser.Class.extend(extended, parent);
}
Class.prototype = extended;
return Class;
},

/**
* 将来源对象的公开成员复制到目标对象
* @param {Class} destination
* @param {Class} source
* @return {Class}
*/
extend: function(destination, source){
if (destination && source) {
for (var property in source) {
destination[property] = source[property];
}
/**
* 对包含toString方法的特殊处理
*/
if (source.hasOwnProperty && source.hasOwnProperty('toString')) {
destination.toString = source.toString;
}
}
return destination;
}
};

Admire = PhoenixClass.create({
hello:"",
sayHello:function(){alert(this.hello);},
//构造函数
initialize:function(){}
});

AdmireLZ = PhoenixClass.create(Admire,{
hello:"LZ,PFPF!:)"
});

var admireLZ = new AdmireLZ();
admireLZ.sayHello();

#244楼    回复  引用  查看    

2008-06-02 09:38 by 中华八爪      
呵呵,不好意思上面有一个笔误:
SpatialBrowser.Class.extend(extended, parent);
->
PhoenixClass.extend(extended, parent);

#245楼 [楼主]   回复  引用  查看    

2008-06-02 14:50 by 李战      
    //完美语法甘露:

    function Class()

    
{

        
var aDefine = arguments[arguments.length-1]; //最后一个参数是类定义

        if(!aDefine) return;

        
var aBase = arguments.length>1 ? arguments[0] : object; //解析基类

        function prototype_(){}//构造prototype的临时函数

        prototype_.prototype = aBase.prototype;  //准备传递prototype

        var aPrototype = new prototype_();    //构造本类prototype

        for(var member in aDefine)  //复制类定义到当前类的prototype

            if(member!="Create")

                aPrototype[member] 
= aDefine[member];



        aPrototype.prototype 
= aBase.prototype; //设置基类型,以便追溯类型继承关系



        
var aType = aDefine.Create || function()

        
{

            
this.base();

        }
;



        aType.prototype 
= aPrototype;   //设置类(构造函数)的prototype

        aType.prototype.Type = aType;   //为本类对象扩展一个Type属性

        return aType;   //返回构造函数作为类

    }
;



.


@中华八爪




以上是《悟透JavaScript》一书中的高级甘露模型的语法甘露,希望能看明白。

#246楼    回复  引用  查看    

2008-06-03 10:20 by 中华八爪      
完美中的两个小瑕疵
1.对某类进行继承后的类定义中如没有Create,this.base()的调用是进行某些处理?
2.定义类中重载toString的问题,for in不能获取toString。

testClass1 = Class({
hello:"",
sayHello:function(){alert(this.hello)},
toString:function(){alert(this.hello)},
Create:function(txt){this.hello = "hi,test1," + txt;}
});

testClass2 = Class(testClass1,{
hello:"hi,test2"
});

var t1 = new testClass1("t1");
t1.toString(); //toString不能通过for in 枚举
var t2 = new testClass2();//this.base() = 某个处理?
t2.sayHello();

#247楼 [楼主]   回复  引用  查看    

2008-06-03 16:19 by 李战      
@中华八爪
又被你挑了几个瑕疵出来。

toString不能被枚举,还有toLocaleString, valueOf, hasOwnProperty, isPrototypeOf, propertyIsEnumerable都不能枚举。

关于这些属性的继承问题,“甘露模型”一节中也有描述和解决办法。只是出于性能考虑,建议读者自行决定是否采用这些解决办法。不过,与您那个例子中特殊处理toString的方式略有不同,因为您用了hasOwnProperty方法。如果有人在其他地方给Object.prototype覆写了hasOwnProperty方法又该怎么办呢?钻一回牛角尖。

其中的默认构造函数调用this.base()是为了即使没有定义构造函数,也能保证构造函数自底向顶的调用不被中断,以保证正确初始化对象。而且,构造函数可以用这个this.base()来调用上层构造函数,还可以带参数,几乎完全和C#的base用法相似。

不瞒您说,光是这个base函数,就包含了JavaScript的精髓,虽然只有十来行代码,一般人未必能读懂。不信,俺故意去掉注释,贴给您看看:

    object.prototype.base = function()  //调用基类构造函数
    {
        var Base = this.Type.Base;
        if(!Base.Base)
            Base.apply(this, arguments)
        else
        {
            this.base = MakeBase(Base);
            Base.apply(this, arguments);
            delete this.base;
        };
        function MakeBase(Type)
        {
            var Base = Type.Base;
            if(!Base.Base) return Base;
            return function()
            {
                this.base = MakeBase(Base);
                Base.apply(this, arguments);
            };
        };
    };

事实上,这个base函数原本可以写得非常简单,只因Opera不支持caller属性,才多写了十来行代码。

俺这“甘露模型”已经在IE, Firefox, Opera, Safari四大浏览器中测试通过。利用这个“甘露模型”可以打造非常优雅的AJAX类库。


#248楼    回复  引用  查看    

2008-06-04 10:27 by mythzz      
甘露模型 很好很强大,谢谢楼主

#249楼    回复  引用  查看    

2008-06-04 13:30 by alisx      
平生有幸看到李老师的佳作,值!学习了很多东西,期待更好的佳作。李老师辛苦了!

#250楼    回复  引用  查看    

2008-06-05 09:18 by ypq      
楼主真是太强了,学到不少,希望再多出强文:)

#251楼    回复  引用    

2008-06-05 13:35 by fooler [未注册用户]
超渡经~~~

#252楼    回复  引用  查看    

2008-06-06 09:33 by 赵俊      
评论都翻页,就冲这个收藏。

#253楼    回复  引用  查看    

2008-06-07 00:27 by 张树坤      
高人就是高人,语出惊人,分析透彻。

#254楼    回复  引用  查看    

2008-06-11 02:16 by 朱扬谷      
书什么时候可以买到?

#255楼    回复  引用  查看    

2008-06-12 17:16 by Robert Lee      
真是常啖常新啊...其实我已经忘了上次是否回帖了:D
今天又看了一遍,还是觉得受益匪浅。

#256楼    回复  引用  查看    

2008-06-17 13:02 by 叶子的博客      
看了后,很震撼。

这是我第二次看见有朋友站在高处以清醒的眼光看类,看面向对象。不迷信,不夸大。实事求是,参透悟透。

超喜欢这样的作者。看了你的文章,如沐春风,心情很好。

对了,第一次看见是在《UNIX编程艺术中》作者对类以及面向对象编程提出了自己的看法。

不象现在,尤其是身边,学校里,面向对象就是一切,教师不仅没有引导学生去思考面向对象到底是什么,它为什么这样,它是完美的吗,抑或只是相对比较完美,它有其局限性吗,抑或它就是万能的,甚至于,教师们自己也从来就不曾思考过这背后的问题吧。

赞博主!哈哈~

#257楼    回复  引用  查看    

2008-06-17 22:56 by 孤独的灵魂      
Hello.prototype.Start = function(){
var self = this;
setTimeout("self.Show()",8000);
}
这样可行吧..?

貌似碰到过这东西..脑子不好使..悲哀

大师可收徒??

#258楼    回复  引用  查看    

2008-06-17 22:57 by 孤独的灵魂      
?!
刚才从上面看到有人说"200楼"了
怎么是我.??
传说中的先知?

#259楼    回复  引用  查看    

2008-06-17 22:58 by 孤独的灵魂      
汗...原来是这样.
还好不是250

#260楼    回复  引用  查看    

2008-06-23 15:00 by DreamsHunter      
牛人. 牛文. 呵呵 受教了

#261楼    回复  引用    

2008-06-25 09:48 by 刘永辉 [未注册用户]
写得不错 看了一半 下次再继续看 不过应该分一个章节的

#262楼    回复  引用  查看    

2008-07-01 14:06 by 逆水行船      
博客园能不能把类似的好文章统一列在一个地方,方便大家查看经典啊?

#263楼    回复  引用    

2008-07-08 14:23 by Bill Gates [未注册用户]
太一般了。过两天我发表一篇.让你们开开眼界!!
My Name Is Bill Gates!!!


#264楼    回复  引用  查看    

2008-07-10 09:03 by ︷起↘嚸.      
文章写的太好了,技术更是强,看了一篇还没完全看懂,继续努力,说不定哪天会比楼主厉害呢。

老赵的视频我全下了,希望看完他的视频后再来看你这篇文章能搞定。

#265楼    回复  引用    

2008-07-14 15:12 by zhangf [未注册用户]
从来上网看东西,不留言,但今天潜心静读之后,情不自禁要留下一句:
学无止境,气宇浩然!楼主,再接再厉!向你致敬!

#266楼    回复  引用    

2008-07-16 14:03 by szh5000 [未注册用户]
mark mark mark 收藏,慢慢看,舒服啊

#267楼    回复  引用  查看    

2008-07-17 21:38 by zhuds      
写的太精彩了 不得不得 顶一下!

#268楼    回复  引用    

2008-07-21 14:32 by 小贝壳 [未注册用户]
太精彩了,我连续在一个群里发了2遍网址向大家推荐
。。。。

#269楼    回复  引用    

2008-07-22 15:45 by hejin [未注册用户]
我是在javaeye看到可能是楼上的那为发的帖,看到内容才找到这里的 ,看完后,很是佩服,真是就想你说的那样 既讲解简单道理,又有浅显的实例来领悟其中的道理,故事中的插曲也是相当精彩,真是犹如醍醐灌顶,心中豁然开朗。先是早已喜不自禁,又是心花怒放

#270楼    回复  引用    

2008-07-22 16:04 by 张雅锋 [未注册用户]
突然想起,看完要顶。。刚看了个开头。已经被吸引了。。

#271楼    回复  引用    

2008-07-24 09:59 by 张雅锋 [未注册用户]
是在惟有顶礼膜拜 打印下来。每天拜读一遍了。。。

#272楼    回复  引用    

2008-07-28 13:23 by 李德银 [未注册用户]
受益终生也。

#273楼    回复  引用    

2008-07-29 17:41 by shohokuf [未注册用户]
学习了

#274楼    回复  引用    

2008-08-13 10:40 by 不是一个人在战斗 [未注册用户]
学习了不少~~楼主可以自己开发一个JS框架了吧

#275楼    回复  引用    

2008-08-21 14:03 by 马猴 [未注册用户]
虽然不完全正确,但是不失为一篇好文章,已达到普渡众生之功。
赞一个!

#276楼    回复  引用    

2008-08-23 13:10 by west2008 [未注册用户]
楼主说得太精彩了,真的没话说
只是,我这里还有点5明白。楼主你太强了。
//语法甘露:
var object = //定义小写的object基本类,用于实现最基础的方法等
{
isA: function(aType) //一个判断类与类之间以及对象与类之间关系的基础方法
{
var self = this;
while(self)
{
if (self == aType)
return true;
self = self.Type;
};
return false;
}
};

function Class(aBaseClass, aClassDefine) //创建类的函数,用于声明类及继承关系
{
function class_() //创建类的临时函数壳
{
this.Type = aBaseClass; //我们给每一个类约定一个Type属性,引用其继承的类
for(var member in aClassDefine)
this[member] = aClassDefine[member]; //复制类的全部定义到当前创建的类
};
class_.prototype = aBaseClass;
return new class_();
};

function New(aClass, aParams) //创建对象的函数,用于任意类的对象创建
{
function new_() //创建对象的临时函数壳
{
this.Type = aClass; //我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类
if (aClass.Create)
aClass.Create.apply(this, aParams); //我们约定所有类的构造函数都叫Create,这和DELPHI比较相似
};
new_.prototype = aClass;
return new new_();
};

//语法甘露的应用效果:
var Person = Class(object, //派生至object基本类
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " + this.name + ", " + this.age + " years old.");
}
});

var Employee = Class(Person, //派生至Person类,是不是和一般对象语言很相似?
{
Create: function(name, age, salary)
{
Person.Create.call(this, name, age); //调用基类的构造函数
this.salary = salary;
},
ShowMeTheMoney: function()
{
alert(this.name + " $" + this.salary);
}
});

var BillGates = New(Person, ["Bill Gates", 53]);
var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();

var LittleBill = New(BillGates.Type, ["Little Bill", 6]); //根据BillGate的类型创建LittleBill
LittleBill.SayHello();

alert(BillGates.isA(Person)); //true
alert(BillGates.isA(Employee)); //false
alert(SteveJobs.isA(Person)); //true
alert(Person.isA(Employee)); //false
alert(Employee.isA(Person)); //true

#277楼    回复  引用  查看    

2008-08-29 11:04 by .NET学徒      
连俺这种看帖几乎不回的都不得不在此强帖留名了。实在是写得好啊!看完了一遍,有些没看懂,等会再看一遍。以俺这种菜鸟水平,此帖真正已经让俺的境界得以提升了。不多废话,继续研究去了!

#278楼    回复  引用  查看    

2008-09-03 18:54 by robinli      
楼主,太有才了,IT界真是高手如云啊

#279楼    回复  引用    

2008-09-09 11:26 by - -|| [未注册用户]
崇拜代码娇妻..........

#280楼    回复  引用  查看    

2008-09-11 14:42 by 菩提树下的杨过      
大师啊!N年前自从看过"悟透Delphi--Delphi的原子世界"后,很久没见大师露面了,正在遗憾今生还能不能见到大师佳作之时,今天老天有眼,又得见大师真迹,想当年,大师的"悟透Delphi",李维的"Borland传奇20年",曾经是多少Borland迷案头的必备精品。

如今,时过境迁,大师也不玩delphi了,但是很欣赏大师这种触类旁通,一通百通的悟性,不知什么时候也能达到大师水平的万分之一,我等凡夫,只能望其项背

#281楼    回复  引用  查看    

2008-09-12 11:20 by 逖靖寒      
好文!!!

#282楼    回复  引用    

2008-09-19 05:55 by Solox [未注册用户]
本来在网上找个script editor好分析coca-cola的一个bug, 一不小心跑到这里读了大半天, 不错。 天晚了, 打包回家。 明天再说。

#283楼    回复  引用    

2008-09-28 14:39 by always Rocky Wang [未注册用户]
棒的没话说.... 太 棒了.... :)

#284楼    回复  引用    

2008-09-29 19:10 by 方来 [未注册用户]
谢谢老兄的好文章
祝愿你能天天开心

#285楼    回复  引用    

2008-10-02 05:42 by wuzi [未注册用户]
非常佩服,学习了

#286楼    回复  引用  查看    

2008-10-07 17:23 by AlexChen      
一直以来对JavaScript都是一知半解的,总是在心里无法让自己对JavaScript这个东东有个很清楚的形象.
现在看到这篇文章,感受颇多啊,
收藏了回去好好的研究下

#287楼    回复  引用    

2008-10-23 20:49 by 八步33 [未注册用户]
Employee.protoType = new Person();
Employee.protoType仅仅是{}person对象.而不是JS内置的prototype.
e = new Employee{};
e读Employee的prototyp内容,就是读一个普通person对像,一个普通person对象能访问就能读Perosn的prototype,

问题:JS中放在prototype中的属性将访问错误的属性,如consturctor

解决办法:
for(var arg in Person.prototype)
Employee[arg] = Person[arg]

#288楼    回复  引用    

2008-10-24 15:33 by xlei [未注册用户]
好文.
看了好些遍,大有受益

文中多了个"有"字 : "阿弥佗佛,原型继承竟然可以玩出有这样的法术!"

#289楼    回复  引用    

2008-11-04 16:34 by 建友 [未注册用户]
写的真好!
初看原型中:
1 function Person(name) //基类构造函数
2 {
3 this.name = name;
4 };
5
6 Person.prototype.SayHello = function() //给基类构造函数的prototype添加方法
7 {
8 alert("Hello, I'm " + this.name);
9 };
10
11 function Employee(name, salary) //子类构造函数
12 {
13 Person.call(this, name); //调用基类构造函数
14 this.salary = salary;
15 };
16
17 Employee.prototype = new Person(); //建一个基类的对象作为子类原型的原型,这里很有意思
18
19 Employee.prototype.ShowMeTheMoney = function() //给子类添构造函数的prototype添加方法
20 {
21 alert(this.name + " $" + this.salary);
22 };
23
24 var BillGates = new Person("Bill Gates"); //创建基类Person的BillGates对象
25 var SteveJobs = new Employee("Steve Jobs", 1234); //创建子类Employee的SteveJobs对象
26
27 BillGates.SayHello(); //通过对象直接调用到prototype的方法
28 SteveJobs.SayHello(); //通过子类对象直接调用基类prototype的方法,关注!
29 SteveJobs.ShowMeTheMoney(); //通过子类对象直接调用子类prototype的方法
30
31 alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true,表明prototype的方法是共享的
在第17行Employee.prototype = new Person();之后,最好添加
Employee.prototype.constructor = Employee;
以保持原型链和构造函数的完整(与javascript内置对象保持一致的风格,例如Array.prototype.constructor为function Array(){[native code]})

#290楼    回复  引用    

2008-11-04 16:42 by 建友 [未注册用户]
对了,如果不添加Employee.prototype.constructor = Employee;
Employee.prototype.constructor将会是Person, 很明显,javascript内置对象Array归根结底是从Object继承来的,但Array.prototype.constructor
为function Array(){[native code]}),Function.prototype.constructor为自身,Boolean.prototype.constructor为function Boolean(){[native code]}),我们不妨也Employee.prototype.constructor = Employee; 以保持一致

#291楼    回复  引用  查看    

2008-11-16 22:54 by 王国金      
厉害。。学习了!!收藏一下!

#292楼    回复  引用    

2008-11-21 22:34 by 无语xxx [未注册用户]
厉害是厉害啊,可惜啊可惜,能把简单的东西讲得如此复杂,够牛。随便一门编译原理或者程序设计语言概念的课程都能比他讲的简单直接。还有这么多人跟帖,难怪中国的编程水平是这样的低。大家的编程都是无根之木啊。

#293楼 [楼主]   回复  引用  查看    

2008-11-22 08:57 by 李战      
@无语xxx
君真可谓“前无古人,后无来者,念天地之悠悠,独怆然而涕下...”,从君的身上仿佛有看到了“人生自古谁无死,留取丹心照汗青”的大义凛然。

君的一席话顿时让我等凡夫俗子翻然醒悟,这么多年真是白活了。痛定思痛!从今往后,我等唯有追随君之左右,义不容辞地加入振兴中国编程水平的大业之中。

......

不好意思,俺要去打酱油了......

#294楼    回复  引用    

2008-11-23 15:18 by 沈 [未注册用户]
抓紧拷下来,打印出来好好研读!

没准过几天就要收“门票”了

实在精彩

#295楼    回复  引用    

2008-11-24 22:29 by eason007 [未注册用户]
书里的几个错处

1、37页的代码,把script的声明头重复了一次
2、124页“为了简化ajax编程,一些组织和个人退出了不少优秀的ajax框架”

#296楼 [楼主]   回复  引用  查看    

2008-11-25 08:28 by 李战      
@eason007
多谢指出错误,真不好意思。
我改,我一定改!

#297楼    回复  引用    

2008-11-25 14:07 by 3232 [未注册用户]
看了头晕!!!!

#298楼    回复  引用    

2008-12-04 11:13 by 网络时空 [未注册用户]
精彩,实在是精彩。我花一上午就看完了,收益非浅,感觉比我过去开发当中学到的还要经典。

在网上看到推荐来不及购买就看了个电子版的,但是只有22页。想必没有完吧?

#299楼    回复  引用    

2008-12-06 09:31 by 饭桶 [未注册用户]
--引用--------------------------------------------------
网络时空: 精彩,实在是精彩。我花一上午就看完了,收益非浅,感觉比我过去开发当中学到的还要经典。

在网上看到推荐来不及购买就看了个电子版的,但是只有22页。想必没有完吧?
--------------------------------------------------------

你是受益非常浅吧。。。

#300楼    回复  引用    

2008-12-12 17:40 by xyly [未注册用户]
还没悟透,苦修中......

#301楼    回复  引用    

2008-12-14 09:46 by 今天路过 [未注册用户]
书是不错,但能否发个勘误表,刚开始就发现错误啊!

#302楼    回复  引用  查看    

2008-12-14 23:34 by arsenal      
写得不错啊,对我这个初学者来说很受用,就是有点短了。

#303楼    回复  引用  查看    

2008-12-15 10:50 by binglingshui      
太受用了。
多年的Js经验,终于有机会整理一下了。

好书,只是到外流浪,太不方便呀。。。
谢谢楼主。。

#304楼    回复  引用  查看    

2008-12-15 19:20 by 张挚      
尽管来晚了.还是要留下名!

#305楼    回复  引用  查看    

2008-12-17 14:46 by PatrickChen      
买了一本来看。本来想,哪天蹲厕所无聊时看,结果看了一眼就一口气看到23页了。
发现23页小错误:
第一个例子的页面地址错了。应该是
http://www.leadzen.cn/books/wutoujavascript/1/js08.htm
写成
http://www.leadzen.cn/books/wutoujavascript/1/js09.htm
了,这个绝对不是你的错误,是编辑的错误了吧?

#306楼    回复  引用  查看    

2008-12-17 16:37 by PatrickChen      
李兄你好!讨教个问题:原型链中:
function Person(name) //基类构造函数
{
this.name = name;
};

Person.prototype.SayHello = function() //给基类构造函数的prototype添加方法
{
alert("Hello, I'm " + this.name);
};

function Employee(name, salary) //子类构造函数
{
Person.call(this, name); //调用基类构造函数
this.salary = salary;
};

Employee.prototype = new Person(); //建一个基类的对象作为子类原型的原型(原型继承)

最后一句,为什么不用 Employee.prototype =Person.prototype来替代呢?我试过IE7和firefox3,没问题啊。并且new Person()会产生无用的成员,也不太好哇。

#307楼    回复  引用    

2008-12-18 19:03 by thanyou [未注册用户]
大师讲得不错啊。。。越看越想看

#308楼    回复  引用  查看    

2008-12-19 14:50 by PatrickChen      
这两天一直拜读这本书,看完珍珑棋局了。
不过里面,为了播放声音,用bgsound,正如lz文中所说,实在是下策。
虽说IE默认集成mediaplyer,但是firefox播放声音也需要插件,如果不装任何插件,我试过,你的程序没有声音。

我感觉最好指定好播放器,比如IE用windows meidaplayer,firefox指定用quicktime。
写一个js播放器类来处理这些东西,包括停止,快进,后退,暂停。我们公司现在就是这么做得,不错哦

#309楼    回复  引用  查看    

2008-12-19 21:13 by 彭仁夔      
悟,
一字妙!!

#310楼    回复  引用    

2008-12-26 14:04 by rmn190 [未注册用户]
一口气读完了, 真是醍醐灌顶, 豁然开朗!

自己对JavaScript真是又爱又恨, 爱的是她寥寥数语足以表达我们的意思,恨的是别人用JavaScript写的框架自己怎么看都看不懂.

现在强了, 有了这个悟透的指引,我更加坚定了自己的编程之路!

#311楼    回复  引用    

2008-12-30 16:33 by zhangyp [未注册用户]
好东西,楼主是高人啊!收藏了!!!

#312楼    回复  引用  查看    

2008-12-31 10:50 by 老鼠坐龟      
新人,
昨天上班看到全部模型 就下班了

今天继续刚看完.

一直不知道javascript是个什么东西的我,现在也算了解点了.

可以讲是function可以改成class ,直接内部类...,房主的模型还可以,不过我也没见过别的.

apply,prototype,call,这几个东西以前没见过,了解之后,这模型好象并不难构思. 我相信房主应该还有东西没发表出来

#313楼    回复  引用  查看    

2009-01-12 18:33 by 八一精神      
我轻轻的来了,正如我轻轻的走~
挥一挥鼠标,不带走一行代码~





全部都带走~~~



:-)

#314楼    回复  引用    

2009-01-21 13:13 by wuhuiqiang [未注册用户]
我买过书了,去那里能下载到悟透JavaScript 相关配套资料

#315楼    回复  引用    

2009-01-23 12:59 by goguoguo [未注册用户]
写的实在太好了,比jquery那本书作者写得好要清晰。不过没有见怎么突出javascript的hash的数据结构。
javascript构建在hash数据结构之上,存储和获取一切都很方便。
灵活的使用方式比起c++ java这些语言来说实在是太方便了。
敏捷的才是快速的,才是高效的。
只选对的,不选效率高的。
支持js。

#316楼    回复  引用    

2009-01-25 11:23 by SoftZ [未注册用户]
我认为文章中有个重大问题是错误的。
…………
17 Employee.prototype = new Person(); //建一个基类的对象作为子类原型的原型,这里很有意思
…………
然后,作者引入了“prototype链”与“原型继承”的概念来解释这个问题。
我认为不对,因为这根本与这两个概念没关系。

因为prototype是对象,所以在new Person()时,就把Person函数的prototype中的成员“共用”给了Emplyee,也就是说,这时原Person.prototype函数的成员(函数或属性),已经是Employee.prototype的成员了!
也就是说,他根本不用什么“直到找到或追溯过程结束为止”,他自已的东西有什么必要在什么“prototype链”中追溯呢?

#317楼    回复  引用  查看    

2009-02-04 15:50 by 水韵悠然      
这样学习,学起来挺有意思的。初学不久,说不出什么见解。支持下~

#318楼    回复  引用    

2009-02-05 15:05 by RunUpwind [未注册用户]
买一本,值!

#319楼    回复  引用    

2009-02-11 11:27 by 钟星斗 [未注册用户]
function myfunc ()
{
alert("hello");
};
myfunc(); //这里调用myfunc,输出yeah而不是hello

function myfunc ()
{
alert("yeah");
};
myfunc(); //这里调用myfunc,当然输出yeah

作者说两次输出都是yeah,而不是hello,实际情况是第一次是hello,第二次是yeah,第二次定义只是覆盖了第一次的定义,js还是按顺序执行的;
我知道的js的段的概念是段内对象只能定义一次,无论你var多少次同名对象,变量名都指向最后定义的那个,但是在被重定义之前,还是按顺序调用的。
function myfunc ()
{
alert("hello");
};
myfunc(); //输出hello

function hello(){
//这个语句位置与这个function外面的语句属于不同的段,因此myfunc指向的是外面这个段中最后一次定义的那个myfunc
myfunc()
}
hello(); //输出hello
function myfunc ()
{
alert("yeah");
};
hello(); //输出yeah();
myfunc(); //输出yeah

#320楼    回复  引用    

2009-02-11 11:34 by 钟星斗 [未注册用户]
不好意思,作者举的例子没有问题,我测试失败是因为我使用了firebug来测试,当我没有说

#321楼    回复  引用  查看    

2009-02-15 12:20 by cnkim      
看了楼主写的书,觉得很收益哈,原来程序也可以这样学习的。不过指出过小错误,在直捣AJAX那个小节里,阿贾克斯是荷兰的球队啊,不是意大利的

#322楼    回复  引用  查看    

2009-02-18 15:22 by RawMan      
路过...

#323楼    回复  引用    

2009-02-20 11:40 by 鄙视 [未注册用户]
写的太差了 自己创建了好多的词语 看不明白 更糊涂了啊

#324楼    回复  引用  查看    

2009-03-06 15:03 by Rmania      
大师,且助老夫一把:
function New(aClass,aparams)
{
function _new()
{
if(aClass.defulte)
{
aClass.defulte.apply(this,aparams);
}
}
_new.prototype = aClass;
return new _new();
}
var ajax =
{
defulte:function(web,func)
{
this.web = web;
this.functions = func;
},
createXMLHttpRequest: function()
{
var xmlHttp;
if (window.ActiveXObject)
{
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest)
{
xmlHttp = new XMLHttpRequest();
}
return xmlHttp;
},
handlereadychange:function()
{
if(xmlHttp.readystate == 4)
{
if(xmlHttp.status == 200)
{
this.functions;
}
}
},
getSend:function()
{
var xmlHttp = this.createXMLHttpRequest();
xmlHttp.onreadystatechange = this.handlereadychange();
xmlHttp.open("GET", this.web , true);
xmlHttp.send(null);
}
}
window.onload = function()
{
var _ajax = New(ajax,['ajax.htm',Func]);
_ajax.getSend();
function Func()
{
alert('=-=');
}
}

我被那个xmlHttp 值的传递给弄得好苦! 请大师指点一二,要怎么样处理的好。
我偿试过在defulte里这样处理this.xmlHttp ='';然后后面的都用this.xmlHttp
还是失败啊。 值的传递还很晕啊!

#325楼    回复  引用  查看    

2009-03-18 23:59 by NingDev      
再来学习一下!

#326楼    回复  引用    

2009-03-23 20:29 by 牛不顿 [未注册用户]
我也属于看帖从来不留名的主,今天破例

#327楼    回复  引用  查看    

2009-03-26 15:35 by academus      
虽然看了两次才明白8成,不过收获很大,谢谢

#328楼    回复  引用  查看    

2009-04-15 00:49 by xcb_isgreat      
不错 很好

#329楼    回复  引用    

2009-04-27 16:52 by 蓝河泣血 [未注册用户]
花了大半天的时间看开头懂一点点,到原型的时候基本看不懂了。虽然看不懂,但心总觉得很牛。一直觉得js这东西很复杂,一个是没好点的开发环境,二是原理性的东西,太过灵活,使自己理解有困难,没长时间研究整不好这玩意儿。我平时用,也就是在提交表单的时候检查检查表单哪些必填的没填,提示一下而已,高级点的基本上不会,只能慢慢再研究了。这里感谢一个lz。
顺路说一下:为了让自己以后方便找到lz所讲述的内容,我会将楼主的文章转到自己的blog,转时会留作者的名,blog地址。望楼主同意。不同意我也转了。
哈哈哈

#330楼    回复  引用  查看    

2009-04-29 10:58 by Jack Dong .NET      
lz
我看了两遍这个,
在 初看原型中讲到的 使用prototype 较之前者以达到形式上优雅,我怎么看 都没觉得形式上有什么区别。 请稍加 解惑...

#331楼    回复  引用  查看    

2009-05-05 17:53 by ruibo      
看这本书在枯燥中略带一丝幽默。
呵呵,我也是一个JS初学者啊,这本书看了两遍,第一遍糊里糊涂的,第二遍看了个八成,但是那个“甘露模型”怎么也看不懂啊,看了几遍了。
这半个月天天在看JS,看的头都大了啊。虽然懂得一些,但用的时候有一些小困难啊。
这本书真的很容易让人看懂。
说实在的开始不知道李站这个人到底是谁,但看完这本书之后,终于知道了。

呵呵,谢谢你的书啊!

#332楼    回复  引用  查看    

2009-05-05 19:39 by 万万有      
这社会多有这样的书就好了 看了书找到这里 呵呵
不知道各位能不能介绍我一本类似的 关于asp的书

#333楼    回复  引用    

2009-05-15 11:35 by jevoly [未注册用户]
呵呵,强就一个字儿,我只说一次。谢谢

#334楼    回复  引用    

2009-05-30 15:22 by ahui2008 [未注册用户]
收益非浅啊

#335楼    回复  引用    

2009-06-05 23:06 by zzzzz [未注册用户]
整篇文章看到的都是在玩JavaScript的语法游戏。离普通JavaScript编程太远。

#336楼    回复  引用    

2009-06-11 12:25 by jrzs [未注册用户]
最近看到一个很有意思的代码,大概是:

var U=new (function(){

一些代码

})();

请教楼主什么意思啊,有什么作用?谢谢了

#337楼    回复  引用  查看    

2009-06-21 22:34 by yuan198094      
太精辟拉!顶

#338楼    回复  引用  查看    

2009-07-01 16:06 by xiao_p      
偶像啊,我帮楼主盖楼来了。

说实话,这篇文章我看了第二次的时候才有所感悟。。。
评论共2页: 上一页 1 2 

发表评论



姓名 [登录] [注册] 
主页
Email (仅博主可见) 
验证码 *  验证码看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论   新用户注册   返回页首      

导航: 网站首页 社区 新闻 博问 闪存 网摘 招聘 .NET频道 知识库 找找看 Google站内搜索



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
China-Pub 计算机绝版图书按需印刷服务

相关文章:

相关链接: