先前整理过js作用域和this关键字的用法,以及分析过js面向对象各种方式,这些都是实现js面向对象所必须的。忘记的话看这里:js面向对象
首先来分析一下js中实现面向对象的重要成员prototype,进而在此基础上讲解继承实现;
=====================================================================
javascript的原型模式:
js中创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象是包含由特定类型的所有实例共享
的属性和方法。也就是说prototype是通过调用构造函数而创建的那个对象实例的原型对象。通过原型对象可以让所有对象实例共享
其属性和方法。例如:
function Person(){
this.name="张三"
}
Person.prototype.age="26";
Person.prototype.say=function(){
alert(this.name);
alert(this.age);
}
var person_new=new Person();
var person_new1=new Person();
person_new.say();//张三,26;
用代码解释就是 Person是一个构造函数,person_new是调用构造函数创建的对象实例,name是构造函数的成员属性,age是通过
prototype原型对象中的一个原型属性。say是原型对象的一个可以由所有实例共享的方法。
通过prototype创建的属性和方法是由所有实例共享的,也就是说person_new.say==person_new1.say;
要理解原型模式首先要理解原型对象性质
---------------------------------------------------------------------------------------------
原型对象:
所有函数在创建时候都会根据一组特定规则为自己创建一个prototype属性,而该属性指向了函数的原型对象。这个原型对象默认情况下会取得constructor属性,这个属性包含一个指向prototype所在函数的指针,也就是Person.prototype.constructor=Person。调用构造函数创建一个新实例时候,该实例会包含一个指针指向构造函数的原型对象,一般来说是__proto__,它存在于实例与构造函数的原型对象中。利用isPrototypeOf()方法可以确定实例与原型对象的关系:Person.prototype.isPrototypeOf(person_new)//true。
如果我们在实例中添加一个属性,而该属性与原型中一个属性同名,那么我们就相当于在实例中创建了一个该属性,该属性会屏蔽原型中那个属性(不是覆盖)。例如:
person_new.age="27";
alert(person_new.age)//"27";
person_new.hasOwnProperty('age')//true
"age"in person_new//true
delete person_new.age;
alert(person_new.age)//"26";
person_new.hasOwnProperty('age')//false
"age" in person_new//true;
要检验一个属性或者方法是存在原型中还是存在实例中用hasOwnProperty方法:存在实例中则返回true,否则返回false;
用in操作符与hasOwnProperty一起检测可以判断是否存在该属性以及该属性存在实例中还是原型中;
更加优雅的""原型对象":
Person.prototype={
age:"26",
name:"lisi"
}
这种写法在实际应用中常见,其返回结果跟之前写法是一样的,但是需要注意的是这种写法其实是完全重写了默认的prototype对象(实际就是以对象字面量形式为prototype对象创建了一个新对象object),先前也说过每次创建一个函数都会创建一个prototype对象,该对象同时就获得constructor属性。而在我们重写了默认prototype对象后constructor就不再指向Person了,而是指向了object。如果想再次将constructor指向原函数,只需在object里增加:constructor:Person.
原型对象问题:
原型对象的作用是所有属性都是被很多实例共享的。也正是由于这个作用,使得一些引用类型例如array就不适合在原型对象中定义了:一个实例改变了该引用类型的值另一个实例访问时候也是改变后的值。
---------------------------------------------------------------------------------------------
构造方法+原型对象
利用构造函数存储定义实例属性,原型对象定义方法和共享的属性。这样每个实例都会有自己的一份实例属性副本,又同时共享原型对象的方法的引用。还支持向构造函数传递参数,最大节约了内存:
function Person(name age){
this.name=name;
this.age=age;
this.love=["basketBall","football"]
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name)
}
}
=======================================================================
继承实现:
ecmascript中实现继承的方式主要依靠原型链来实现,那么什么是原型链呢?上面讲到的原型对象跟原型链有什么关系么。
---------------------------------------------------------------------------------------------
原型链:
原型链基本思想如下:
上面说过每个构造函数都有一个原型对象prototype,原型对象都包含一个指向构造函数的指针constructor,每个实例都包含一个指向原型对象的指针__proto__;
如果我们让原型对象等于另一个类型的实例,那么结果将是此时的原型对象包含一个指向另一个原型的指针,而另一个原型中也包含着指向另一个构造函数的指针,加入另一个原型又是第三个类型的实例,那么上述关系依然成立。以此层层推进,就实现了实例与原型的链条,这就是所谓的原型链;其实现方式如下代码:
function Person(){
this.name="zhangsan";
this.love=["basketball","football"]
}
Person.prototype.getName=function(){
return this.name;
}
function man(){
this.age="26";
}
man.prototype=new Person();
man.prototype.getAge=function(){
return this.age;
}
var yuchao=new man();
上面代码中man和Person是两个类型,man通过创建Person的实例并将该实例赋给man.prototype实现继承了Person,此时yuchao指向
了man的原型,man的原型又指向Person的原型,yuchao.constructor现在指向的不是man而是Person(因为man的原型指向了Person
的原型)。需要注意的是 这时候的man原型是Person的实例赋给的,如果把getAge方法放到 man.prototype={getAge:function()
{}}这样写 就完全破坏了设想中的原型链,因为此对象字面量写法相当于重写了man的原型 使其指向一个object实例而不是Person的实例。
----------------------------------------------------------------------------------------
原型链问题及组合继承:
原型链问题 实际就是上面说的原型对象问题,上面说过,包含引用类型的原型属性会被所有实例共享,这也就是为什么上面要用构造方法+原型对象结合生成“js类”。拿上面代码做例子:
yuchao.love.push("music");
yuchao.love//basketball,football,music;
var xiaopu=new man();//新建另外的实例
xiaopu.love//basketball,football,music;
在第一个实例中我们将love数组push一个music,在另外的新建所有实例中都共享了这个新数组,这明显不是我们想要的结果;
在解决原型链问题时候我们可以组合使用借用构造函数+原型链这样一种组合继承方式,从而达到使用原型链实现对原型属性和方法
的继承,又能通过借用构造函数实现对实例属性继承。(以下为完整代码):
function Person(name){
this.name=name;
this.love=["basketball","football"]
}
Person.prototype.sayName=function(){
alert(this.name)
}
function man(name,age){
Person.call(this,name);//调用父类构造方法,使得子类的实例对象都是初始化代码
this.age=age;
}
man.prototype=new Person();
//再实例化Person为man的原型对象时候love就是构造函数的属性而不是直接添加到man原型对象中
man.prototype.sayAge=function(){
alert(this.age);
}
var yuchao=new man("zhangsan","26");
这样既能达到每个实例能实现原型的函数复用,又能保证每个实例都有自己属性,从而实现最好的继承方式