JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。

既然要实现继承,那么我们先定义一个父类:

// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
alert(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
alert(this.name + '正在吃:' + food);
};

1、原型链继承

核心: 将父类的实例作为子类的原型

function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat'; // Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.eat('fish'));
alert(cat.sleep());
alert(cat instanceof Animal); //true
alert(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的引用属性是所有实例共享的
  4. 创建子类实例时,无法向父类构造函数传参

2、借用构造函数继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
} // Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)(不完美,没有父类方法)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、组合继承

核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,继承父类属性和方法,实现函数复用

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal(); // Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // true
alert(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用
  6. 可以实现多继承(同上)

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)(仅仅多消耗了一点内存)

4原型式继承

核心:继承已有的对象而不是函数

//对传入的对象执行一次浅复制
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//例子
var Animal = {
name:"cat",
friend:["aaron","cassic"]
}
var cat=object(Animal);
cat.name="sss";
cat.friend.push("aseaff");
alert(cat.name);
alert(cat.friend);

特点:

  1. 没有必要创建构造函数,只是想让一个对象与另一个对象保持类似

缺点:

  1. 与原型模式一样,包含引用类型的属性值会共享

5寄生式继承

核心 与原型式继承紧密相关,只是多了方法

//对传入的对象执行一次浅复制
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//增强对象
function createobject(o){
var clone=object(o);
clone.sayname=function(){
alert("hi");
};
return clone;
}
//例子
var Animal = {
name:"cat",
friend:["aaron","cassic"]
}
var cat=createobject(Animal);
cat.sayname();

特点:

  1. 没有必要创建构造函数,只是想让一个对象与另一个对象保持类似
  2. 与原型式继承相比,有了自己的方法

缺点:

  1. 与原型式继承一样,包含引用类型的属性值会共享

6、寄生组合继承

核心:通过寄生方式,创建空函数,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
function aaron(o,y){
var Super = function(){};
Super.prototype=y.prototype;
o.prototype=new Super();
o.prototype.constructor=o;
}
aaron(Cat,Animal);
// Test Code
var cat = new Cat();
alert(cat.name);
cat.sleep();
alert(cat instanceof Animal); // true
alert(cat instanceof Cat); //true

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂
  2. 不能实现多继承(不完美,同上)

下面介绍多继承

7 拷贝继承

function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
} // Test Code
var cat = new Cat();
alert(cat.name);
cat.sleep();
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true

特点:

支持多继承

缺点:

效率较低,内存占用高(因为要拷贝父类的属性)(使用 for in 能遍历原型方法)

8 组合使用构造函数和拷贝实现多继承

function Parent1(name,age){
this.name = name;
this.age = age;
this.height=180;
}
Parent1.prototype.say = function(){
alert('hi');
}
function Parent2(name,age,weight){
this.name = name;
this.age = age;
this.weight = weight;
this.height = 170;
this.skin='yellow';
}
Parent2.prototype.walk = function(){
alert('walk');
} function Child(name,age,weight){
Parent1.call(this,name,age);
Parent2.call(this,name,age,weight);
} for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]}
for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]} var c1 = new Child('xiaoming',10,8);
console.log(c1); //Child { name="xiaoming", age=10, height=170, 更多...}
console.log(c1.constructor);//Child(name,age,weight)

特点:javascript的多继承其实就是前面介绍的js循环拷贝继承的多次使用

总结:javascript是可以利用call方法和prototype属性来实现多继承的。继承方法与单继承相似,只是将需要继承的多个父类依次实现,另外对于属性或共有方法重命的时候,以最后继承的属性和方法为主。因为会覆盖前面的继承。

最好的两个继承是寄生组合继承和循环拷贝继承,但是寄生组合继承不能完美实现多继承,会漏掉父类原型方法,只有循环拷贝继承可以完美实现多继承。

JS继承的几种方式的更多相关文章

  1. js 继承的几种方式

    JS继承的实现方式: 既然要实现继承,那么首先我们得有一个父类,代码如下: function Animal(name) { // 属性 this.name = name || '小白'; // 实例方 ...

  2. 20. js继承的6种方式

    想要继承,就必须要提供个父类(继承谁,提供继承的属性) 一.原型链继承 重点:让新实例的原型等于父类的实例. 特点: 1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性.( ...

  3. 理解js继承的6种方式

    想要继承,就必须要提供个父类(继承谁,提供继承的属性) 一.原型链继承 重点:让新实例的原型等于父类的实例. 特点:1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性.(新 ...

  4. js继承的6种方式

    想要继承,就必须要提供个父类(继承谁,提供继承的属性) 一.原型链继承 重点:让新实例的原型等于父类的实例. 特点:1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性.(新 ...

  5. js实现继承的5种方式 (笔记)

    js实现继承的5种方式 以下 均为 ES5 的写法: js是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承 ...

  6. javascript(js)创建对象的模式与继承的几种方式

    1.js创建对象的几种方式 工厂模式 为什么会产生工厂模式,原因是使用同一个接口创建很多对象,会产生大量的重复代码,为了解决这个问题,产生了工厂模式. function createPerson(na ...

  7. js 实现继承的几种方式

    //js中实现继承的几种方式 //实现继承首先要有一个父类,先创造一个动物的父类 function Animal(name){ this.name = name; this.shoot = funct ...

  8. js 实现继承的6种方式(逐渐优化)

    <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...

  9. JS继承的原理、方式和应用

    概要: 一.继承的原理 二.继承的几种方式 三.继承的应用场景 什么是继承? 继承:子类可以使用父类的所有功能,并且对这些功能进行扩展.继承的过程,就是从一般到特殊的过程.要了解JS继承必须首先要了解 ...

随机推荐

  1. Codeforces Round #328 div2

    Problem_A(592A): 题意: 在一个8*8的棋盘上有黑白两种棋子,'W'代表白色,'B'代表黑色. 玩家A执白子,先走. 白子只能向上走,黑子只能向下走.如果有障碍物则不能走, 比如白色的 ...

  2. CQRS学习——IOC,配置,仓储隔离以及QueryEntry[其三]

    从IoC开始说起 博主最早开始用的IoC容器叫AutoFac,那时候用它主要是为了生命周期管理——将EF上下文的生命周期限定为每请求.当然也总是每每听到IoC的好处,但是仍然不能理解其优势.最近在学习 ...

  3. CSS3制作时钟

    这个效果是一个CSS3制作的时钟,不过并不是我们传统的时钟风格,分别用三块显示 时.分.秒三个部分,而且这个DEMO中藤藤还为其加上了一个js的效果,能让这个效 果和现实时钟的时间同步.这个效果运用到 ...

  4. 移动开发:初学 iOS-UIViewController 心得

    初学 iOS,本文翻译了一些 iOS 官网上的 UIViewController 的知识点,如有不到位或不正确的地方,还请指正: 本文所介绍的内容的目标: 理解content view control ...

  5. Jmeter 执行java脚本结束时提示:he JVM should have exitted but did not

    使用jmeter执行java协议测试结束时会提示:he JVM should have exitted but did not ,jmeter2.11以后的可以 通过设置:      jmeteren ...

  6. 8086 cpu为什么要把段地址*16+偏移量形成物理地址呢?

    8086 cpu为什么要把段地址*16+偏移量形成物理地址呢? 这是因为,8086地址线是20位,段寄存器是16位,将段寄存器*16实际上就是向左移动4位,形成20位和8086的二十位地址线匹配. I ...

  7. 面向XX编程

    [一篮饭特稀原创,转载请注明出自http://www.cnblogs.com/wanghafan/p/5033186.html ]  基于面向XX编程的个人理解 面向过程编程 Procedure Or ...

  8. QReadWriteLock 读写锁的区别

    QReadWriteLock 这个允许多个进程同时读,但是只有一个写.而且写读不能同时进行. 文档里语焉不详,这是我自己的理解: lockForWrite 为写而锁,就是要修改数据,外人连想进来读数据 ...

  9. Android Integer.decode()和Intger.valueof()

    decode合适用来分析数字 可以分析 8进:010=>分析后为 8 10进:10=>分析后为 10 16进:#10|0X10|0x10=>分析后是 16 而valueof    只 ...

  10. gzip [选项] 压缩(解压缩)

    减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间.gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用. 语法:gzip ...