From 犀牛书
JavaScript中的所有数据类型都可以归结为对象,因此为了将所有对象联系起来,就需要继承机制。
首先JavaScript通过 new
,从原型对象生成实例对象。但是,JavaScript(一下简称JS)又没有引入“类”,因此JS的 new
调用的不是类,而是“构造函数”。
比如像这样,构建一个猫对象的原型:
function Cat(name){
this.name=name;
}
对此构造函数使用new
生成实例:
var catA=new Cat('小黑');
alert(catA.name);//小黑
然而,通过this
创建的属性会被实例对象独立继承拷贝,相对独立,并非共有,也会浪费内存。因此JS还提供了prototype模式,用来创建共有属性。每个构造函数都有一个prototype
属性,指向一个对象该对象的所有属性和方法,都会被构造函数的实例继承。
这样一来,实例对象一旦创建,会自动引用prototype
对象的属性和方法。共享属性和方法放在prototype
对象中,是引用的;不共享的属性和方法放在构造函数内部,是本地的:
Cat.prototype.type='猫科';
Cat.prototype.eat=function(){alert("吃鱼");};
生成实例则是这样:
alert(catA.type);//猫科
catA.eat();//吃鱼
如果再实例话一个对象,起共有属性和方法均指向prototype
对象,因此提高了运行效率:
var catB=new Cat('小白');
alert(catA.eat==catB.eat);//True
还有一些验证继承模式的方法:
1.实例会自动含有一个constructor
属性,指向它的构造函数:
alert(catA.constructor==Cat);//True
2.通过instanceof
运算符可以验证原型对象和实例对象的关系:
alert(catA instanceof Cat);//True
3.通过isPrototypeOf()
判断原型对象和实例对象的关系:
alert(Cat.prototype.isPrototypeOf(catA));//True
4.通过hasOwnProperty()
判断某一属性是来自本地还是继承引用:
alert(catA.hasOwnProperty("name"));//True
alert(catA.hasOwnProperty("type"));//False
5.通过in
运算符判断某个实例是否含有某个属性(无论本地还是引用):
alert("name" in catA);//True
alert("type" in catA);//True
构造函数的继承,其实就是生成一个继承了多个对象的实例
像下面这样,有两个构造函数,需要实现Cat
继承Animal
:
function Cat(name,color){
this.name=name;
this.color=color;
}
function Animal(){
this.type="动物";
}
有以下方法:
1.构造函数绑定。使用call
和apply
把父对象构造函数绑定在自对象上:
function Cat(name,color){
Animal.apply(this,arguments);
this.name=name;
this.color=color;
}
var catA=new Cat("小黑","黑色");
alert(catA.type);//动物
2.prototype模式。把子对象的prototype
属性指向父对象的实例:
Cat.prototype = new Animal();
这样prototype
对象原来的值被完全删除,包括指向其构造函数的constructor
属性,并赋予了一个新的值。
因此需要重新规定其构造函数的指向:
Cat.prototype.constructor=Cat;//
这样之后:
var catA=new Cat("小黑","黑色");
alert(catA.type);//动物
3.直接继承父对象的prototype
。可以让Cat跳过 Animal,直接继承Animal.prototype。
先设置父对象的prototype
:
function Animal(){ }
Animal.prototype.type = "动物";
再把Cat的prototype
指向Animal的prototype
,当然因为修改了prototype
,构造函数也要改:
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
这样做的结果是不错,但是因为把父对象和子对象的原型关联了,任何对Cat.prototype
的修改都会反映到Animal.prototype
,其实Animal.prototype
对象的constructor
属性也已经被修改成Cat
了。
因此最好的方法是利用空对象作为中介:
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
现将空对象的prototype
指向父对象的prototype
,再把子对象的prototype
属性指向空对象的实例,这其实是上面两种方法的结合。
4.prototype模式的封装函数,这是更有可用性的方法:
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
调用的时候像这样:
extend(Cat,Animal);
var catA = new Cat("小黑","黑色");
alert(catA.type); // 动物
5.纯粹拷贝方法,把父对象的所有属性和方法拷贝进子对象:
function extend(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;//这是为了方便子对象直接调用父对象的prototype属性
}
所谓非构造函数,就是普通对象,像下面这样,有两个普通对象,需要实现Cat
继承Animal
:
var Cat={
name:"猫"
}
var Animal={
type:"动物"
}
有以下方法:
1.使用object()函数。object()函数就是把子对象的prototype
属性指向父对象,使子对象与父对象连在一起:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
因此先在父对象的基础上生成子对象:
var Cat = object(Animal);
子对象随后继承了父对象的属性:
alert(Cat.type); //动物
2.浅拷贝。把父对象的属性全部拷贝给子对象,实现继承:
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
调用的时候像这样:
var Cat = extendCopy(Animal);
alert(Cat.type); // 动物
浅拷贝存在一个问题: 如果父对象的属性等于数组或另一个对象,那么子对象获得的只是一个内存地址引用,而不是真正拷贝,因此存在父对象可能被篡改。
3.深拷贝。用来完成包括数组和对象的拷贝。
function deepCopy(p,c){
var c||{};
for(var i in p){
if(typeof p[i]==='object'){//判断父对象的属性是否为对象
c[i]=(p[i].contructor===Array)?[]:{};//或者数组
deepCopy(p[i],c[i]);//递归调用,拷贝数组对象内部属性
}
else{
c[i]=p[i];
}
}
return c;
}
调用的时候依然这样:
var Cat = deepCopy(Animal);
alert(Cat.type); // 动物
先到这里~~