javascript面向对象三大特征之继承实例详解

本文实例讲述了javascript面向对象三大特征之继承。分享给大家供大家参考,具体如下:

继承

在JavaScript中的继承的实质就是子代可以拥有父代公开的一些属性和方法,在js编程时,我们一般将相同的属性放到父类中,然后在子类定义自己独特的属性,这样的好处是减少代码重复。继承是面向对象的基础,是代码重用的一种重要机制。

——此文整理自 《jQuery 开发从入门到精通》 ,这是本好书,讲的很详细,建议购买阅读。

继承的作用

实现继承的主要作用是:

① 子类实例可以共享超类属性和方法。
② 子类可以覆盖和扩展超类属性和方法。

继承的分类

在JavaScript中是不支持类的概念,使用构造器机制来实现类的特性。
JavaScript中类的继承不止一种,主要包括:类继承(构造函数继承),原型继承,实例继承,复制继承,克隆继承,混合继承,多重继承等。

类继承

类继承也叫构造函数继承,其表现形式是在子类中执行父类的构造函数。实现本质:比如把一个构造函数A的方法赋值为另一个构造函数B,然后调用该方法,使构造函数A在构造函数B内部执行,这是构造函数B就拥有了构造函数A中定义的属性和方法。这就是B类继承A类。示例如下:

function A(x){
  this.x = x;
  this.say = function() {
   console.log(this.x + ' say');
  }
}
function B(x,y) {
  this.m = A; // 把构造函数A作为一个普通函数引给临时方法m()
  this.m(x); // 把当前参数作为值传给构造函数A,并且执行
  delete this.m; // 清除临时方法
  this.y = y;
  this.call = function(){
   console.log(this.y);
  }
}
// 测试类继承
var a = new A(1);
var b = new B(2,3);
a.say(); // 1 say
b.say(); // 2 say
b.call(); // 3

上面的实现方式很巧妙对吧,但是这种设计方式太随意,缺乏严密性。严禁的设计模式应该考虑到各种可能存在的情况和类继承关系中的互相耦合性。所以一般我们尽可能把子类自身的方法放在原型里去实现。下面这种创建方式会更好:

function A(x){
 this.x = x;
 this.say = function() {
  console.log(this.x + ' say');
 }
}
function B(x,y){
 this.y = y;
 A.call(this,x); // 在构造函数B内调用超类A,实现绑定,用call来实现借用
}
B.prototype = new A(); // 设置原型链,建立继承关系
B.prototype.constructor = B; // 恢复B的原型对象的构造函数为B,如果不操作就会指向A
B.prototype.gety = function(){ // 给类B添加新的方法
 return this.y;
}
// 测试 类继承
var a = new A('A');
var b = new B('Bx','By');
a.say(); // A say
b.say(); // Bx say
console.log(b.gety()); // By
console.log(B.prototype.__proto__ === A.prototype); // true;
console.log(b.constructor === B); // true 这里也可以把B中的 B.prototype.constructor = B; 去除,结果为false

在js中实现类继承,需要设置3点:

① 在子类构造函数结构体内,使用函数call()调用父类构造函数,把子类的参数传递给调用函数如上面的例子:A.call(this,x) 这样子类可以继承父类的所有属性和方法。
② 在子类和父类之间建立原型链,如上例:B.prototype = new A() 为了实现类的继承必须保证他们原型链上的上下级关系。即设置子类的prototype 属性指向父类的一个实例即可。
③ 恢复子类原型对象的构造函数, 如上例:B.prototype.constructor = B

在类继承中,call() 和 apply() 方法被频繁使用,它们之间的功能和用法都是相同的,唯一区别就是第2个参数类型不同。如果深入,请看前面的文章:https://www.jb51.net/article/166093.htm

此处需要提一下,就是类的构造函数中的成员,一般称之为本地成员,而类的原型成员就是类的原型中的成员,此处我们只考虑原型中的成员继承。本地成员继承可以用call 和 apply。下面我们来看下类继承的原型成员的继承封装函数:

在函数体内,首先定义一个空函数F(),用来实现功能中转,把它的原型指向父类的原型,把空函数的实例传递给子类的原型,这样就避免了直接实例化父类引发的内存问题,因为实际开发中,父类可能很大,实例化后,会占用很大一部分内存。

// 定义一个继承函数
function extend(Sub,Sup){ // 有两个入口参数,Sub是子类,Sup是父类
 var F = function(){}; // 建立一个临时的构造函数
 F.prototype = Sup.prototype; // 把临时构造函数的原型指向父类的原型
 Sub.prototype = new F(); // 实例化临时类,此处相当于把子类的原型指向父类的实例
 Sub.prototype.constructor = Sub; // 恢复子类的构造函数
 Sub.sup = Sup.prototype; // 在子类中定义一个本地属性存储超类原型,可避免子类和超类耦合
 if(Sup.prototype.constructor === Object.prototype.constructor) { // 检测超类构造器是否为Object构造器
  Sup.prototype.constructor = Sup;
 }
}
// 下面定义两个类用来测试上面的继承函数
function A(x,y) {
 this.x = x;
 this.y = y;
}
A.prototype.add = function() {
 return (this.x-0) + (this.y-0); // 此处-0 的目的是确保字符串类型可转成数值型
}
A.prototype.minus = function() {
 return this.x - this.y;
}
function B(x,y) {
 A.apply(this,[x,y]);
}
// 开始实现类继承中的原型成员继承
extend(B,A);
// 为了不与A类中的代码耦合可以单独为B定义一个同名的add
B.prototype.add = function() {
 return B.sup.add.call(this); // 避免代码耦合
}
// 测试继承
var b = new B(1,2);
console.log(b.minus()); // -1
console.log(b.add()); // 3

原型继承

原型继承是js中最通用的继承方式,不用实例化对象,通过直接定义对象,并被其他对象引用,这样形成的一种继承关系,其中引用对象被称为原型对象。

function A(){
 this.color = 'red';
}
function B(){}
function C(){}
B.prototype = new A();
C.prototype = new B();
// 测试原型继承
var c = new C();
console.log(c.color); // red

原型继承显得很简单,不需要每次构造都调用父类的构造函数,也不需要通过复制属性的方式就能快速实现继承。但它也存在一些缺点:

① 每个类型只有一个原型,所以不支持多重继承
② 不能很好的支持多参数或动态参数的父类,显得不够灵活。
③ 占用内存多,每次继承都需要实例化一个父类,这样会存在内存占用过多的问题。

实例继承

实例化类可创建新的实例对象,这个实例对象将继承类的所有特征。

function Arr() {
 var a = new Array();
 return a;
}
var arr = new Arr();
arr[0] = 1;
arr[1] = 2;
console.log(arr); // [1,2]
console.log(Array.isArray(arr)); // true
console.log(arr instanceof Array); // true
console.log(arr instanceof Arr); // false

通过构造函数中完成对类的实例化操作,然后返回实例对象,这就是实例继承的由来。实例继承可实现对所有对象的继承,包括自定义类,核心对象和DOM对象。但是也有一些缺点

① 实例继承无法传递动态参数,它是封闭在函数体内试下你,不能通过call和apply来实现动态传参。
② 实例继承只返回一个对象,不支持多重继承
③ 实例继承对象它仍然保持与原对象的实例关系,无法实现继承对象是封装类的实例。如:console.log(arr instanceof Arr); // false

复制继承

复制继承就是利用for in 遍历对象成员,逐一复制给另一个对象。通过这种方式来实现继承。

function A(){
 this.color = 'red';
}
A.prototype.say = function() {
 console.log(this.color);
}
var a = new A();
var b = {};
// 开始拷贝
for(var item in a) {
 b[item] = a[item];
}
// 开始测试
console.log(b.color); // red
b.say(); // red.

我们把它封装一下:

Function.prototype.extend = function(obj){
 for(item in obj){
  this.constructor.prototype[item] = obj[item];
 }
}
function A(){
 this.color = 'green';
}
A.prototype.say = function(){
 console.log(this.color);
}
// 测试
var b = function(){};
b.extend(new A());
b.say(); // green

复制继承实际上是通过反射机制复制类对象中的可枚举属性和方法来模拟继承。这种可以实现多继承。但也有缺点:

① 由于是反射机制,不能继承非枚举类型的属性和方法。对于系统核心对象的只读方法和属性也无法继承。
② 执行效率差,这样的结构越庞大,低效就越明显。
③ 如果当前类型包含同名成员,这些成员会被父类的动态复制给覆盖。
④ 多重继承中,复制继承不能清晰描述父类和子类的相关性。
⑤ 在实例化后才能遍历成员,不够灵活,也不支持动态参数
⑥ 复制继承仅仅是简单的引用赋值,如果父类成员包含引用类型,那么也会带来很多副作用,如不安全,容易遭受污染等。

克隆继承

通过对象克隆方式继承,可以避免赋值对象成员带来的低效。
为Function对象扩展一个clone方法。该方法可把参数对象赋值给一个空的构造函数的原型对象,然后返回实例化后的对象,这样该对象就拥有构造哈数包含的所有成员了。

Function.prototype.clone = function(obj){
 function Temp(){};
 Temp.prototype = obj;
 return new Temp();
}
function A(){
 this.color = 'purple';
}
var o = Function.clone(new A());
console.log(o.color); // purple

混合继承

混合继承是把多种继承方式一起使用,发挥各个优势,来实现各种复杂的应用。最常见的就是把类继承和原型继承一起使用。

function A(x,y){
 this.x = x;
 this.y = y;
}
A.prototype.add = function(){
 return (this.x-0) + (this.y-0);
}
function B(x,y){
 A.call(this,x,y);
}
B.prototype = new A();
// 测试
var b = new B(2,1);
console.log(b.x); // 2
console.log(b.add()); // 3

多重继承

继承一般包括单向继承和多向继承,单向继承模式较为简单,每个子类有且仅有一个超类,多重继承是一个比较复杂的继承模式。一个子类可拥有多个超类。JavaScript原型继承不支持多重继承,但可通过混合模式来实现多重继承。下面让类C来继承类A和类B:

function A(x){
 this.x = x;
}
A.prototype.hi = function(){
 console.log('hi');
}
function B(y){
 this.y = y;
}
B.prototype.hello = function(){
 console.log('hello');
}
// 给Function增加extend方法
Function.prototype.extend = function(obj) {
 for(var item in obj) {
  this.constructor.prototype[item] = obj[item];
 }
}
// 在类C内部实现继承
function C(x,y){
 A.call(this,x);
 B.call(this,y);
};
C.extend(new A(1));
C.extend(new B(2));
// 通过复制继承后,C变成了一个对象,不再是构造函数了,可以直接调用
C.hi(); // hi
C.hello(); // hello
console.log(C.x); // 1
console.log(C.y); // 2

一个类继承另一个类会导致他们之间产生耦合,在js中提供多种途径来避免耦合的发生如 掺元类

掺元类是一种比较特殊的类,一般不会被实例化或者调用,定义掺元类的目的只是向其他类提供通用的方法。

// 定义一个掺元类
function F(x,y){
 this.x = x;
 this.y = y;
}
F.prototype = {
 getx:function(){
  return this.x;
 },
 gety:function(){
  return this.y;
 }
}
// 原型拷贝函数
function extend(Sub,Sup){ // Sub 子类 , Sup 掺元类
 for(var item in Sup.prototype){
  if(!Sub.prototype[item]){ // 如果子类没有存在同名成员
   Sub.prototype[item] = Sup.prototype[item]; // 那么复制掺元类成员到子类原型对象中
  }
 }
}
// 定义两个子类 A,B
function A(x,y){
 F.call(this,x,y);
}
function B(x,y){
 F.call(this,x,y);
}
// 调用extend函数实现原型属性,方法的拷贝
extend(A,F);
extend(B,F);
console.log(A.prototype);
// 测试继承结果
var a = new A(2,3);
console.log(a.x); // 2
console.log(a.getx()); // 2
console.log(a.gety()); // 3
var b = new B(1,2);
console.log(b.x); // 1
console.log(b.getx()); // 1
console.log(b.gety()); // 2

通过此种方式把多个子类合并到一个子类中,就实现了多重继承。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

(0)

相关推荐

  • 详解JavaScript基于面向对象之继承

    一.面相对象继承机制       这个实例使用UML很好的解释了继承机制.       说明继承机制最简单的方式是,利用一个经典的例子就是几何形状.实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形.矩形和五边形都是多边形的一种,具有不同数量的边.正方形是矩形的一种,所有的边等长.这就构成了一种完美的继承关系,很好的解释了面向对象的继承机制.        在这个例子中,形状是椭圆形和多边形的基类(通常我们也可以叫它父类,所有类都由

  • JS 面向对象之继承---多种组合继承详解

    这一次要讲 组合.原型式.寄生式.寄生组合式继承方式. 1. 组合继承:又叫伪经典继承,是指将原型链和借用构造函数技术组合在一块的一种继承方式. 下面来看一个例子: function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { alert(this.n

  • javascript 面向对象封装与继承

    整理一下js面向对象中的封装和继承. 1.封装 js中封装有很多种实现方式,这里列出常用的几种. 1.1 原始模式生成对象 直接将我们的成员写入对象中,用函数返回. 缺点:很难看出是一个模式出来的实例. 代码: 复制代码 代码如下: function Stu(name, score) {             return {                 name: name,                 score: score             }         }    

  • 彻底理解js面向对象之继承

    说道这个继承,了解object-oriented的朋友都知道,大多oo语言都有两种,一种是接口继承(只继承方法签名):一种是实现继承(继承实际的方法) 奈何js中没有签名,因而只有实现继承,而且靠的是原型链实现的.下面正式的说一说js中继承那点事儿 1.原型链 原型链:实现继承的主要方法,利用原型让一个引用类型继承另一个引用类型的属性和方法. 回顾:构造函数,原型,实例三者的关系 每一个构造函数都有一个原型对象(Person.prototype):原型对象都包含指向构造函数的指针(constru

  • JavaScript面向对象之Prototypes和继承

    一.前言 本文翻译自微软的牛人Scott Allen Prototypes and Inheritance in JavaScript ,本文对到底什么是Prototype和为什么通过Prototype能实现继承做了详细的分析和阐述,是理解JS OO 的佳作之一.翻译不好的地方望大家修改补充. 二.正文 JavaScript中的面向对象不同于其他语言,在学习前最好忘掉你所熟知的面向对象的概念.JS中的OO更强大.更值得讨论(arguably).更灵活. 1.类和对象 JS从传统观点来说是面向对象

  • JS实现面向对象继承的5种方式分析

    本文实例讲述了JS实现面向对象继承的5种方式.分享给大家供大家参考,具体如下: js是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承有以下通用的几种方式 1. 使用对象冒充实现继承(该种实现方式可以实现多继承) 实现原理:让父类的构造函数成为子类的方法,然后调用该子类的方法,通过this关键字给所有的属性和方法赋值 function Parent(firstname) { this.fname=firs

  • javascript 面向对象全新理练之继承与多态

    1 又是几个基本概念 为什么要说又呢? 在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系. 1.1 定义和赋值 变量定义是指用 var a; 这种形式来声明变量. 函数定义是指用 function a(...) {...} 这种形式来声明函数. var a = 1; 是两个过程.第一个过程是定义变量 a,第二个过程是给变量 a 赋值. 同样 var a = function(...) {};

  • javascript 面向对象,实现namespace,class,继承,重载

    由于组里项目大多的javascript,css等客户端工作是另一同事在负责,该同事又特忙无法重构,老大也就只是提建议并未立即实施重构.但是我前些日子也改过些许客户端的小bug,确实那代码看得让人有些云里雾里,不知身在哪山,轻易不敢动代码,于是就自己动手鼓捣起我曾又爱又恨的javascript来,自己写一个简单的js实现namespace,继承,重载等面向对象的特性.欢迎拍砖灌水 .定义namespace Namesapce.js 复制代码 代码如下: Namespace = new Object

  • Javascript面向对象编程(二) 构造函数的继承

    今天要介绍的是,如何生成一个"继承"多个对象的实例. 比如,现在有一个"动物"对象的构造函数, 复制代码 代码如下: function Animal(){ this.species = "动物"; } 还有一个"猫"对象的构造函数, 复制代码 代码如下: function Cat(name,color){ this.name = name; this.color = color; } 怎样才能使"猫"继承&

  • javascript 面向对象编程基础:继承

    我们看到这里继承的概念是多么的直白,"拷贝一个类的prototype 到另外一个类",好,Code is cheap,看代码: function class1() { } function class2() { } class2.prototype = class1.prototype; class2.moreProperty1 = " class 2 additional string " ; class2.moreMethod1 = function () {

随机推荐