再读《悟透javascript》之二、JavaScript原型

前言

      JavaScript原型是JavaScript的精髓所在,理解了JavaScript原型对于我们理解JavaScript有着重大的意义。

 

 

一、构造对象引出的问题

1.       构造对象

在JavaScript中创建一个新的对象,可以如下:

    function test(){}

    var t1 = new test();

           而上面的方法等价于如下:

    function test(){}

var t1 = {};  //创建一个空的对象(此时t1指向这个新创建的对象)
t1.__proto__ = test.prototype;   //将t1的内置对象指针指向test的prototype对象,t1的内置对象指针是无法直接访问到的,但是内置对象指针所指向的对象的方法和属性是可以被直接使用的
test.call(t1);

先创建一个空的对象(t1),然后将t1的内置对象指针指向test的prototype对象,最后调用test函数,并将该对象(t1)作为test函数的this。

理解上面的三个步骤对于理解后面的代码有着非常关键的意义,我相信会有很多人觉得读到这理解了,但是当往后读就会发现,他们的理解是不够深入的,但没有问题,到时记得再回来读读就好。(注:JavaScript中所有的obj对象都有__proto__属性,但是该属性无法被访问,只有在new xxx的时候,会将其指向xxx的prototype对象)

注意:三个步骤的第一步,创建一个空的对象,假如t1是原本指向另外的对象的话,之后t1就会改为指向这个新创建的对象了。例:

    <script type="text/javascript">

    var luo = new Object();

    luo.name = "soldierluo";

    luo.age = 23;

    alert(luo.name+" is "+luo.age+" years old");     //此时的luo所指向的对象拥有两个属性
   

    function person(){

        this.salary = "33333";

    }

    luo = new person();     //这之后,luo所指向的对象替代了此前的luo所指向的对象(就是所luo的指向改变了)
    alert(luo.name+" is "+luo.age+" years old");     //所有这时访问不到原来的对象的属性了
    alert(luo.salary);

</script>

 

 

二、模拟继承

我们可以通过call来模拟继承,例:

    <script type="text/javascript">

    function person(name){

        this.name = name;

        this.say = function(){alert("i'm "+this.name)};

    }

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary)};

    }

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say(); w1.show();

w2.say(); w2.show();

alert(w1.say==w2.say);

</script>

上面我们将person和worker看成两个类,而worker类继承了person类,这样worker类就获得了person类的属性和方法。

而继承的实现是通过在worker中调用person方法,并将worker的this传递给person,而不为person的this,这样,person和worker就拥有了同样的this,从而实现了继承。

 

 

但是。。。。。。有一个不小的问题——函数没能复用

上面代码的最后一句,返回的是false,这说明w1和w2对象使用的不是同一个say函数体。这是因为say方法在w1和w2两个对象中都有一份say函数体的拷贝。同一个类的对象各自拥有一套函数体显然是一种浪费。

 

 

三、使用原型(prototype)杜绝浪费

1.       JavaScript中所有function类型的对象都有一个prototype属性,这个prototype属性又指向一个object对象,所以我们可以任意给它添加属性和方法。通过同一函数声明的对象,在该函数的prototype属性上的方法和属性都是公用的,并且可以直接使用。例:

    <script type="text/javascript">

    function person(name){this.name = name;}

    person.prototype.say = function(){alert("i'm "+this.name);}

    var p1 = new person("luo");

    var p2 = new person("soldier");

alert(p1.say==p2.say);

p1.say();p2.say();

</script>

返回结果为true,说明p1和p2对象使用的是同一个say方法体,而prototype上的say方法也可以直接调用。

 

 

2.       将prototype应用于继承,这样继承的类就可以使用同一个方法体了,例:

    <script type="text/javascript">

    function person(name){

        this.name = name;

    }

    person.prototype.say = function(){alert("i'm "+this.name)};

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary);};

    }

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say();

    w2.say();

    alert(w1.say);

    alert(w2.say);

</script>

执行上面的代码,发现报错,如果注释掉w1.say();w2.say();,我们会惊奇地发现,w1.say和w2.say方法居然为undefined,这真是奇怪了。

来看上面的代码,worker类通过person.call(this,name);来继承了person类,这样它们就拥有了同样的this,但person的say方法并不在this上,而在prototype上。那如何才能使用到person的prototype上的方法呢?最重要的一步:worker.prototype = new person();,通过这句,我们可以将worker的prototype与person的prototype关联起来。当worker的prototype中找不到对应的属性或方法时,它会根据上面的关联找到person的prototype中去,这就是所谓的“原型链继承”。

 

 

最终代码如下:

    <script type="text/javascript">

    function person(name){

        this.name = name;

    }

    person.prototype.say = function(){alert("i'm "+this.name)};

    function worker(name, salary){

        person.call(this,name);

        this.salary = salary;

        this.show = function(){alert(this.name+"'s salary is "+this.salary);};

    }

    worker.prototype = new person();

   

    var w1 = new worker("luo",10000);

    var w2 = new worker("soldierluo",333333);

    w1.say();

w2.say();

</script>

 

分析上面的继承,可以发现两个最重要的点
1)       通过person.call(this);来实现this的继承(或者叫扩展,我觉得更合适)
2)       通过worker.prototype=new person();(内部实现为:worker.prototype.__proto__=person.prototype)来实现prototype的继承(或者叫扩展,我觉得更适合)
 

 

四、闭包——原型扩展

微软曾经使用了一种称为“闭包”的技术来模拟类,其大致模型如下:

    <script type="text/javascript">

    function person(name, age){

        var name = name;    //私有变量
        var getName = function(){alert("my name is "+name);}   //私有方法
       

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

        this.say = function(){alert(name+" is "+this.age+" years old");}  //公共方法
    }

   

    var p1 = new person("luo",23);

    p1.say();     alert(p1.age);    //调用公共方法、获取公共变量
    p1.age=100;   alert(p1.age);    //修改公共变量值
    p1.say();     alert(p1.age);    //调用公共方法、获取公共变量
   

    p1.getName(); alert(p1.name);   //调用私有方法、获取私有变量
</script>

上面可以看到,凡是通过var声明的,不论是函数还是属性,外部是无法访问或更改的,而挂靠在this对象后的属性和方法则可以在外部访问和更改。通过这样的属性,也就模拟了面向对象中的私有和公有属性,这就称之为“闭包”。

 

 

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

 

 

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

    <script type="text/javascript">

    function person(name, age){

        this.name = name;

        this.age = age;

    }

    person.prototype.say = function(){alert(this.name+" is "+this.age+" years old");}

   

    function worker(name, age, salary){

        person.call(this,name,age);

        this.salary = salary;

    }

    worker.prototype = new person();

    worker.prototype.show = function(){alert(this.name+"'s salary is "+this.salary);}

   

    var w1 = new worker("luo",23,5000);

    var w2 = new worker("soldier",32,50000);

    w1.show();  w1.say();

    w2.show();  w2.say();

</script>

这里的原型模型其实就是上面说的模拟继承,没有私有变量,并且要分两部分来定义类,显得不够简介,不过对象的方法是共享的,并且不论是在垃圾回收还是性能方面都要优于闭包模型。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章