DOM笔记(十三):JavaScript的继承方式

在Java、C++、C#等OO语言中,都支持两种继承方式:接口继承和实现继承。接口继承制继承方法签名,实现继承则继承实际的方法和属性。在SCMAScript中,由于函数没有签名,所以无法实现接口继承,只支持实现继承。

实现继承主要依靠原型链来实现。

一、原型链

原型链是利用原型让一个引用类型继承另一个引用类型的方法,在DOM笔记(十二):又谈原型对象中,描述了构造函数、实例和原型之间的关系:

每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而每个实例都包含一个指向原型对象的内部指针。如果将原型对象作为另一个构造函数的实例,会如何?

function SuperType()
{
    this.property = true;
}
SuperType.prototype.getSuperValue=function()
{
    return this.property;
};
function SubType()
{
    this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue=function()
{
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());  //true

DOM笔记(十一):JavaScript对象的基本认识和创建中提到了,Object类型是所有对象的根元素。所以,SubType也是继承了Object,内部有一个指针指向Object的原型。  现在,他们的关系变成了这样:

当instance调用getSuperValue()时,会进行搜索:1)在实例中搜索;2)在实例中找不到,就在SubType.prototype中搜索;3)在SubType.prototype中找不到,就在SuperType.prototype中找。找到后就停止搜索,返回结果值。但instance调用toString()时,就一直会搜索到原型链的末端—Object.prototype。

根据原型链,需要注意的是,instance.constructor不再指向SubType,而是指向了SuperType。

二、原型继承小结

  1、确定原型跟实例的关系

DOM笔记(十二):又谈原型对象中提到了isPrototypeOf()方法,另外也可以用instanceof确立这种关系的存在。

//均返回true
alert(instance instanceof Object);
alert(instance instanceof SuperType);
alert(instance instanceof SubType);
alert(Object.prototype.isPrototypeOf(instance));
alert(SuperType.prototype.isPrototypeOf(instance));
alert(SubType.prototype.isPrototypeOf(instance));

2、当在子类中定义与父类中同名的属性和方法时,父类中对应的属性和方法会被屏蔽。

function SuperType()
{
    this.property = true;
}
SuperType.prototype.getSuperValue=function()
{
    return this.property;
};
function SubType()
{
    this.property = false;    //同名属性
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue=function()
{
    return this.property;
};
var instance = new SubType();
alert(instance.getSuperValue());  //false

3、原型链实现继承,不能使用对象字面量创建原型方法,防止重写原型链。

function SuperType()
{
    this.property = true;
}
SuperType.prototype.getSuperValue=function()
{
    return this.property;
};
function SubType()
{
    this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
// 使用字面量添加新方法,导致上一行无效
SubType.prototype={
    getSubValue:function()
    {
        return this.subproperty;
    }
};
var instance = new SubType();
alert(instance.getSuperValue());  //error:undefined is not a function

  4、原型继承存在两个问题:1)引用类型的原型属性会被所有实例共享;2)原型继承时,不能通过子类的实例向父类的构造函数中传递参数。

function SuperType()
{
    this.colors = ["red","blue","white"];
}
function SubType(){ }
// 继承SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //red,blue,white,black
var instance2 = new SubType();
alert(instance2.colors);  //red,blue,white,black

instance1和instance2共享了colors数组属性(引用类型)。

三、其他继承方式

1、借用构造函数

利用原型继承时,引用类型的原型属性会被所有实例共享。但是借用构造函数就能避免这种问题,并且能够利用apply()和call()传递参数给父类的构造函数:

function SuperType()
{
    this.colors = ["red","blue","white"];
}
function SubType()
{
    // 继承SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //red,blue,white,black
var instance2 = new SubType();
alert(instance2.colors);  //red,blue,white

  2、组合继承(推荐)

组合继承即原型继承+构造函数继承:原型链实现对原型属性和方法的继承,构造函数实现对实例属性的继承。

function SuperType(name)
{
    this.name=name;
    this.colors = ["red","blue","white"];
}
SuperType.prototype.sayName=function()
{
    alert(this.name);
};
function SubType(name,age)
{
    // 继承SuperType属性,并传递参数给父类的构造函数
    SuperType.call(this,name);              //第一次调用SuperType()
    this.age=age;
}

// 继承SuperType方法
SubType.prototype = new SuperType();      //第二次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge=function()
{
    alert(this.age);
};
var instance1 = new SubType("dwqs",20);
instance1.colors.push("black");
alert(instance1.colors);  //red,blue,white,black
instance1.sayName();  //dwqs
instance1.sayAge();  //20
var instance2 = new SubType("i94web",25);
alert(instance2.colors);  //red,blue,white
instance2.sayName();  //i94web
instance2.sayAge();  //25

为了确保SuperType构造函数不会重写子类的原型,建议在调用父类构造函数之后再添加子类的属性。

组合继承的不足之处在于:1)无论什么情况,都会调用两次父类构造函数;2)子类需要重写父类中的属性,并且会包含两组name和colors属性(父类属性):一组在实例上,一组在原型上

  3、原型式继承

此种方式是借用原型,基于已有的对象创建新的对象,同时不必创建自定义类型。但是需要借助于如下的一个函数:

function object(o)
{
    function F(){}
    F.prototype=o;
    return new F();
}

o是已经存在的对象,返回的对象与o有相同的属性和方法。

var person={
name:“dwqs”,
colors:["red","blue","white"]
};
var per1 = object(person);
alert(per1.name); //dwqs
// 覆盖已有的属性
per1.name = “i94web”;

per1.age = 20; //新增属性
alert(per1.age); //20
per1.colors.push(“black”);

alert(per1.name); //i94web
alert(per1.colors); //red,blue,white,black
var per2 = object(person);
alert(per2.name); //dwqs
// 所有实例共享引用类型的属性
alert(per2.colors); //red,blue,white,black

其混乱关系如下:

per1
和per2会共享colors属性(引用类型)。在ECMAScript
5中,Object.create(obj,[props])规范了这种方式:obj是已经存在的对象,props可选,是一个包含额外属性的对象,格式
与Object.defineProperties()的第二个参数格式一样。

var person={
    name:"dwqs",
    colors:["red","blue","white"]
};
var per1 = Object.create(person,{
    age:{
        value:20
    }
});
alert(per1.age);  //20

4、寄生式继承

该方式与原型式继承差不多,也要借用object函数,然后在内部已某种方式来增强对象。

function object(o)
{
    function F(){}
    F.prototype=o;
    return new F();
}
var person={
    name:"dwqs",
    colors:["red","blue","white"]
};
function createObj(obj)
{
    var clone = object(obj);
    // 增强对象
    clone.age = 20;
    clone.Hello=function()
    {
        alert("Hello,my age is "+this.age);
    };
    return clone;
}
var per1 = createObj(person);
per1.Hello();   //Hello,my age is 20

  5、寄生组合式继承

在第2中方式,组合继承虽然时JavaScript钟最常用的方式,但是也存在不足,上面已经提到,不再赘述。而寄生组合式继承能解决这两个不足,其基本模式如下:

function object(o)
{
    function F(){}
    F.prototype=o;
    return new F();
}
function inheritPrototype(subType,superType)
{
    var obj = object(superType.prototype);  //创建对象
    obj.constructor = subType;                      //增强对象
    subType.prototype = obj;                        //指定对象
}

重写组合继承中的示例:

function object(o)
{
    function F(){}
    F.prototype=o;
    return new F();
}
function inheritPrototype(subType,superType)
{
    var obj = object(superType.prototype);  //创建对象
    obj.constructor = subType;                      //增强对象
    subType.prototype = obj;                        //指定对象
}
function SuperType(name)
{
    this.name=name;
    this.colors = ["red","blue","white"];
}
SuperType.prototype.sayName=function()
{
    alert(this.name);
};
function SubType(name,age)
{
    // 继承SuperType属性,并传递参数给父类的构造函数
    SuperType.call(this,name);
    this.age=age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function()
{
    alert(this.age);
};
var instance1 = new SubType("dwqs",20);
instance1.colors.push("black");
alert(instance1.colors);  //red,blue,white,black
instance1.sayName();  //dwqs
instance1.sayAge();  //20
var instance2 = new SubType("i94web",25);
alert(instance2.colors);  //red,blue,white
instance2.sayName();  //i94web
instance2.sayAge();  //25

只调用了一次SuperType()构造函数,并且避免了SuperType.prototype上创建多余和不必要的属性,原型链保持不变,能够使用instanceof和isPrototypeOf()方法。

原文首发:http://www.ido321.com/1381.html

时间: 2024-10-10 05:59:27

DOM笔记(十三):JavaScript的继承方式的相关文章

JavaScript中继承方式详解

继承一直是面向对象语言中的一个最为人津津乐道的概念,在JavaScript中,继承也是难点之一,下面我尽量用通俗的语言来介绍一下实现继承的几种方法. 原型链 ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.这个基本思想说的一点也不基本,那么先说一个在之前博文中提到的概念,原型与实例的关系.我们知道:每一个实例里包含了原型对象中的方法和属性.这是因为任何一个对象都有一个内部属性[[prototype]]

JavaScript各种继承方式和优缺点

好久没写博客啦,嘻嘻,这个月是2017年的最后一个月啦,大家应该都开始忙着写年终总结了吧,嘻嘻,小颖今天给大家分享下Javascript中的几种继承方式以及他们的优缺点. 1.借助构造函数实现继承 原理:通过call()函数修改 this 指向,从而实现将父类属性挂载到子类实例中. function parent1() { this.name = 'parent1'; } function child1() { parent1.call(this); this.type = 'child1';

javascript的继承方式总结

实现继承大致可分为两类 1.基于构造器工作模式 2.基于对象工作模式 ---------------------- A 是否使用原型 B 是否执行属性拷贝 C 两者都有(原型属性拷贝)1.原型链法 Children.prototype = new Parent()所属模式 1.A 1 function Shape() { 2 this.name="Shape"; 3 this.toString = function() { 4 return this.name; 5 }; 6 } 7

学习笔记:JavaScript传参方式———ECMAScript中所有函数的参数都是按值传递

我们把命名参数(arguments)视为局部变量,在向参数传递基本类型值时,如同基本类型变量的复制一样,传递一个副本,参数在函数内部的改变不会影响外部的基本类型值.如: 1 function add10(num){ 2 num += 10 ; 3 return num ; 4 } 5 var count = 10 ; 6 var result = add10(count); 7 alert(count);// 10 8 alert(result); //20 在向参数传递引用类型的值时,会把这个

JavaScript各种继承方式(六):寄生组合继承

一 原理 用寄生继承来改造组合继承. function Fruit(name){ this.name = name; } Fruit.prototype.eat = function(){ console.log('eat'); } function Mango(name,level){ Fruit.call(this,name); this.level = level; } function create(obj){ let instance = Object.create(obj); //

JavaScript中的几种继承方式对比

转自:http://blog.csdn.net/kkkkkxiaofei/article/details/46474069 从'严格'意义上说,JavaScript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似Java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言. 为何需要利用JavaScript实现继承? 早期pc机器的性能确实不敢恭维,所有的压力全在服务器端,

Javascript中的几种继承方式

开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究过javascript的继承方式,故特撰此文以供交流. 为何需要利用javascript实现

Javascript中的几种继承方式比较

开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究过javascript的继承方式,故特撰此文以供交流. 为何需要利用javascript实现

Javascript原型继承容易忽略的错误

编写Javascript的开发者都知道,JS虽然没有类(ES6添加了class语法),但是可以模拟出OOP语言的类和面向对象的概念,比如我们都知道的一句话,Javascript中处处是对象,而面向对象语言的特性是继承,封装,多态,抽象,而本文讨论的是Javascript的继承,Javascript的继承方式有原型继承,组合继承,寄生继承等等,在日常开发中,哪种继承方式更好用在于开发者对于程序的结果以及性能的考虑.笔者在下面列举出原型继承中经常容易被忽略的错误. 常见错误一: function F