首页 > 代码库 > 6.23随笔

6.23随笔

原型对象

 

   在JavaScript 中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性。其中函数对象的一个属性就是原型对象 prototype。注:普通对象没有prototype,但有__proto__属性。

 

 

 

  原型对象其实就是普通对象(Function.prototype除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:

 

 function f1(){};

 

 console.log(f1.prototype) //f1{}

 

 console.log(typeof f1. prototype) //Object

 

 console.log(typeof Function.prototype) // Function,这个特殊

 

 console.log(typeof Object.prototype) // Object

 

 console.log(typeof Function.prototype.prototype) //undefined

 

 

 

 从这句console.log(f1.prototype) //f1 {} 的输出就结果可以看出,f1.prototype就是f1的一个实例对象。就是在f1创建的时候,创建了一个它的实例对象并赋值给它的prototype,基本过程如下:

 

 var temp = new f1();

 

 f1. prototype = temp;

 

 

 

  所以,Function.prototype为什么是函数对象就迎刃而解了,上文提到凡是new Function ()产生的对象都是函数对象,所以temp1是函数对象。

 

 var temp1 = new Function ();

 

 Function.prototype = temp1;

 

 

 

那原型对象是用来做什么的呢?主要作用是用于继承。举了例子:

 

  var person = function(name){

 

   this.name = name

 

  };

 

  person.prototype.getName = function(){

 

     return this.name; 

 

  }

 

  var zjh = new person(‘zhangjiahao’);

 

  zjh.getName(); //zhangjiahao

 

 

 

   从这个例子可以看出,通过给person.prototype设置了一个函数对象的属性,那有person实例(例中:zjh)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。

 

 

 

原型链

 

   JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子为例:

 

 

 

  console.log(zjh.__proto__ === person.prototype) //true

 

 

 

同样,person.prototype对象也有__proto__属性,它指向创建它的函数对象(Object)的prototype

 

 

 

  console.log(person.prototype.__proto__ === Object.prototype) //true

 

 

 

继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null

 

 

 

  console.log(Object.prototype.__proto__) //null

 

 

 

我们把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。如下图:

 

 

 

 

 

 

 

在开始摆弄代码之前,应该搞清楚使用继承的目的和能带来什么好处。一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化类之间的耦合。而要做到这两者都兼顾是很难的,我们需要根据具体的条件和环境下决定我们应该采取什么方法。根据我们对面向对象语言中继承的了解,继承会带类直接的强耦合,但js由于其特有的灵活性,可以设计出强耦合和弱耦合,高效率和低效率的代码。而具体用什么,看情况。

 

 

 

下面提供js实现继承的三种方法:类式继承,原型继承,掺元类。这里先简述类式继承,后两种在往后的随便中简述,请多多关注、指导,谢谢。

 

 

 

类式继承。

 

 

 

js类式继承的实现依靠原型链来实现的。什么是原型链?js中对象有个属性prototy,这个属性返回对象类型的引用,用于提供对象的类的一组基本功能。

 

 

 

貌似对prototype有印象,对了,我们经常这样用代码。

var Person = function(){    

 

    this.name = "liyatang";

 

};

 

Person.prototype = {

 

    //可以在这里提供Person的基本功能

 

    getName : function(){

 

        return this.name;

 

    }

 

}

 

我们把类的基本功能放在prototype属性里,表示Person这个对象的引用有XXX功能。

 

 

 

在理解原型后,需要理解下什么是原型链。在访问对象的某个成员(属性或方法)时,如果这个成员未见于当前对象,那么js会在prototype属性所指的那个对象中查找它,如果还没有找到,就继续到下一级的prototype所指的对象中查找,直至找到。如果没有找到就会返回undifined。

 

 

 

那么原型链给我们什么提示呢?很容易联想到,原型链意味着让一个类继承另一个类,只需将子类的prototype设置为指向父类的一个实例即可。这就把父类的成员绑定到子类上了,因为在子类上查找不到某个成员时会往父类中查找。(以上这两段用词不严谨,只在用通俗易懂的言语描述)

 

 

 

下面我们需要个Chinese类,需要继承Person类的name和getName成员。

var Chinese = function(name, nation){

 

        //继承,需要调用父类的构造函数,可以用call调用,this指向Chinese

 

    //使Person在此作用域上,才可以调用Person的成员

 

    Person.call(this,name);

 

    this.nation = nation;

 

};

 

Chinese.prototype = Person.prototype;

 

//这里不可和以前一样,因为覆盖掉了prototype属性

 

//Chinese.prototype = {

 

// getNation : function(){

 

// return this.nation;

 

// }

 

//};

 

//以后的方法都需要这样加

 

Chinese.prototype.getNation = function(){

 

        return this.nation;

 

};

 

继承关系就建立了,我们这样调用它

var c = new Chinese("liyatang","China");

 

alert(c.getName());// liyatang

 

于是类式继承就这样完成了。难道真的完成了嘛,用firebug在alert那里设断点,会发现原来的Person.prototype被修改了,添加了getNation方法。

这是因为在上面的代码Chinese.prototype = Person.prototype; 这是引用类型,修改Chinese同时也修改了Person。这本身就是不能容忍的,且使类之间形成强耦合性,这不是我们要的效果。

 

 

 

我们可以另起一个对象或实例化一个实例来弱化耦合性。

 

//第一种

 

//Chinese.prototype = new Person();

 

//第二种

 

//var F = function(){};

 

//F.prototype = Person.prototype;

 

//Chinese.prototype = F.prototype;

 

这两种方法有什么区别呢。在第二种中添加了一个空函数F,这样做可以避免创建父类的一个实例,因为有可能父类会比较庞大,而且父类的构造函数会有一些副作用,或者说会执行大量的计算任务。所以力荐第二种方法。

 

 

 

到此,完了嘛,还没有!在对象的属性prototype下面有个属性constructor,它保存了对构造特定对象实例的函数的引用。根据这个说法Chiese.prototype.constructor应该等于Chinese,实际上不是。

 

 

 

回忆之前在设置Chiese的原型链时,我们把Person.prototype 覆盖掉了Chiese.prototype。所以此时的Chiese.prototype.constructor是Person。我们还需要添加以下代码

//对这里的if条件不需要细究,知道Chinese.prototype.constructor = Chinese就行

 

if(Chinese.prototype.constructor == Object.prototype.constructor){

 

    Chinese.prototype.constructor = Chinese;

 

}

 

整理全部代码如下

var Person = function(name){

 

    this.name = name;

 

};

 

Person.prototype = {

 

    getName : function(){

 

        return this.name;

 

    }

 

};

 

 

 

var Chinese = function(name, nation){

 

    Person.call(this,name);

 

    this.nation = nation;

 

};

 

var F = function(){};

 

F.prototype = Person.prototype;

 

Chinese.prototype = F.prototype;

 

if(Chinese.prototype.constructor == Object.prototype.constructor){

 

    Chinese.prototype.constructor = Chinese;

 

}

 

Chinese.prototype.getNation = function(){

 

        return this.nation;

 

};

 

 

 

var c = new Chinese("liyatang","China");

 

alert(c.getName());

 

如果可以把继承的代码放在一个函数里,方便代码复用,最后整理代码如下

function extend(subClass,superClass){

 

    var F = function(){};

 

    F.prototype = superClass.prototype;

 

    subClass.prototype = new F();

 

    subClass.prototype.constructor = subClass;

 

    subClass.superclass = superClass.prototype; //加多了个属性指向父类本身以便调用父类函数

 

    if(superClass.prototype.constructor == Object.prototype.constructor){

 

        superClass.prototype.constructor = superClass;

 

    }

 

}

 

 

 

var Person = function(name){

 

    this.name = name;

 

};

 

Person.prototype = {

 

    getName : function(){

 

        return this.name;

 

    }

 

};

 

 

 

var Chinese = function(name, nation){

 

    Person.call(this,name);

 

    this.nation = nation;

 

};

 

extend(Chinese, Person);

 

Chinese.prototype.getNation = function(){

 

        return this.nation;

 

};

 

 

 

var c = new Chinese("liyatang","China");

 

alert(c.getName());

我对那个extend函数又有新的看法。之前在讨论如何设置原型链时提出了两种方法

//第一种

 

//Chinese.prototype = new Person();

 

//第二种

 

//var F = function(){};

 

//F.prototype = Person.prototype;

 

//Chinese.prototype = F.prototype;

 

虽然第二种减少了调用父类的构造函数这条路,但在设计Chinese类时用了Person.call(this,name);这里也相当于调用了父类的构造函数。

 

 

 

然而用第一种方法的话可以减少在Chinese中再写Person.call(this,name);,这部分代码在子类中往往会遗忘。不妨把这种功能代码放在了extend里。就只写

 

 

 

Chinese.prototype = new Person();也达到同样的目的:耦合不强。

 

 

 

但遗忘的一点是,Chinese.prototype = new Person();这样写对嘛。答案是不对!很明显 new Person()需要传一个name参数的。我们不可能在extend函数里做这部分工作,只好在Chinese类里调用父类的构造函数了。这样也符合面向对象的思路。

 

在开始摆弄代码之前,应该搞清楚使用继承的目的和能带来什么好处。一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化类之间的耦合。而要做到这两者都兼顾是很难的,我们需要根据具体的条件和环境下决定我们应该采取什么方法。根据我们对面向对象语言中继承的了解,继承会带类直接的强耦合,但js由于其特有的灵活性,可以设计出强耦合和弱耦合,高效率和低效率的代码。而具体用什么,看情况。

 

下面提供js实现继承的三种方法:类式继承,原型继承,掺元类。这里先简述类式继承,后两种在往后的随便中简述,请多多关注、指导,谢谢。

 

类式继承。

 

js类式继承的实现依靠原型链来实现的。什么是原型链?js中对象有个属性prototy,这个属性返回对象类型的引用,用于提供对象的类的一组基本功能。

 

貌似对prototype有印象,对了,我们经常这样用代码。

var Person = function(){    

    this.name = "liyatang";

};

Person.prototype = {

    //可以在这里提供Person的基本功能

    getName : function(){

        return this.name;

    }

}

 

 

我们把类的基本功能放在prototype属性里,表示Person这个对象的引用有XXX功能。

 

在理解原型后,需要理解下什么是原型链。在访问对象的某个成员(属性或方法)时,如果这个成员未见于当前对象,那么js会在prototype属性所指的那个对象中查找它,如果还没有找到,就继续到下一级的prototype所指的对象中查找,直至找到。如果没有找到就会返回undifined。

 

那么原型链给我们什么提示呢?很容易联想到,原型链意味着让一个类继承另一个类,只需将子类的prototype设置为指向父类的一个实例即可。这就把父类的成员绑定到子类上了,因为在子类上查找不到某个成员时会往父类中查找。(以上这两段用词不严谨,只在用通俗易懂的言语描述)

 

下面我们需要个Chinese类,需要继承Person类的name和getName成员。

var Chinese = function(name, nation){

        //继承,需要调用父类的构造函数,可以用call调用,this指向Chinese

    //使Person在此作用域上,才可以调用Person的成员

    Person.call(this,name);

    this.nation = nation;

};

Chinese.prototype = Person.prototype;

//这里不可和以前一样,因为覆盖掉了prototype属性

//Chinese.prototype = {

// getNation : function(){

// return this.nation;

// }

//};

//以后的方法都需要这样加

Chinese.prototype.getNation = function(){

        return this.nation;

};

 

 

继承关系就建立了,我们这样调用它

var c = new Chinese("liyatang","China");

alert(c.getName());// liyatang

于是类式继承就这样完成了。难道真的完成了嘛,用firebug在alert那里设断点,会发现原来的Person.prototype被修改了,添加了getNation方法。

 

?

 

这是因为在上面的代码Chinese.prototype = Person.prototype; 这是引用类型,修改Chinese同时也修改了Person。这本身就是不能容忍的,且使类之间形成强耦合性,这不是我们要的效果。

 

我们可以另起一个对象或实例化一个实例来弱化耦合性。

//第一种

//Chinese.prototype = new Person();

//第二种

//var F = function(){};

//F.prototype = Person.prototype;

//Chinese.prototype = F.prototype;

这两种方法有什么区别呢。在第二种中添加了一个空函数F,这样做可以避免创建父类的一个实例,因为有可能父类会比较庞大,而且父类的构造函数会有一些副作用,或者说会执行大量的计算任务。所以力荐第二种方法。

 

到此,完了嘛,还没有!在对象的属性prototype下面有个属性constructor,它保存了对构造特定对象实例的函数的引用。根据这个说法Chiese.prototype.constructor应该等于Chinese,实际上不是。

 

回忆之前在设置Chiese的原型链时,我们把Person.prototype 覆盖掉了Chiese.prototype。所以此时的Chiese.prototype.constructor是Person。我们还需要添加以下代码

//对这里的if条件不需要细究,知道Chinese.prototype.constructor = Chinese就行

if(Chinese.prototype.constructor == Object.prototype.constructor){

    Chinese.prototype.constructor = Chinese;

}

整理全部代码如下

var Person = function(name){

    this.name = name;

};

Person.prototype = {

    getName : function(){

        return this.name;

    }

};

 

var Chinese = function(name, nation){

    Person.call(this,name);

    this.nation = nation;

};

var F = function(){};

F.prototype = Person.prototype;

Chinese.prototype = F.prototype;

if(Chinese.prototype.constructor == Object.prototype.constructor){

    Chinese.prototype.constructor = Chinese;

}

Chinese.prototype.getNation = function(){

        return this.nation;

};

 

var c = new Chinese("liyatang","China");

alert(c.getName());

如果可以把继承的代码放在一个函数里,方便代码调出

function extend(subClass,superClass){

    var F = function(){};

    F.prototype = superClass.prototype;

    subClass.prototype = new F();

    subClass.prototype.constructor = subClass;

    subClass.superclass = superClass.prototype; //加多了个属性指向父类本身以便调用父类函数

    if(superClass.prototype.constructor == Object.prototype.constructor){

        superClass.prototype.constructor = superClass;

    }

}

 

var Person = function(name){

    this.name = name;

};

Person.prototype = {

    getName : function(){

        return this.name;

    }

};

 

var Chinese = function(name, nation){

    Person.call(this,name);

    this.nation = nation;

};

extend(Chinese, Person);

Chinese.prototype.getNation = function(){

        return this.nation;

};

 

var c = new Chinese("liyatang","China");

alert(c.getName());

发表后修改:

 

在一楼的评论下,我对那个extend函数又有新的看法。之前在讨论如何设置原型链时提出了两种方法

//第一种

//Chinese.prototype = new Person();

//第二种

//var F = function(){};

//F.prototype = Person.prototype;

//Chinese.prototype = F.prototype;

虽然第二种减少了调用父类的构造函数这条路,但在设计Chinese类时用了Person.call(this,name);这里也相当于调用了父类的构造函数。

 

然而用第一种方法的话可以减少在Chinese中再写Person.call(this,name);,这部分代码在子类中往往会遗忘。不妨把这种功能代码放在了extend里。就只写

 

Chinese.prototype = new Person();也达到同样的目的:耦合不强。

 

但遗忘的一点是,Chinese.prototype = new Person();这样写对嘛。答案是不对!很明显 new Person()需要传一个name参数的。我们不可能在extend函数里做这部分工作,只好在Chinese类里调用父类的构造函数了。这样也符合面向对象的思路。

6.23随笔