JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待。

JS继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

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

1、原型链继承

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

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

特点:

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

缺点:

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

推荐指数:★★(3、4两大致命缺陷)

2017-8-17 10:21:43补充:感谢 MMHS 指出。缺点1中描述有误:可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。

2018-9-10 00:03:45补充:感谢 IRVING_J 指出。缺点3中的描述不够充分。更正为:来自原型对象的所有属性被所有实例共享。

2、构造继承

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

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

特点:

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

缺点:

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

推荐指数:★★(缺点3)

3、实例继承

核心:为父类实例添加新特性,作为子类实例返回

function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
} // Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

推荐指数:★★

4、拷贝继承

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();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

推荐指数:★(缺点1)

5、组合继承

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

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal(); // 感谢 @学无止境c 的提醒,组合继承也是需要修复构造函数指向的。 Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

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

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

推荐指数:★★★★(仅仅多消耗了一点内存)

6、寄生组合继承

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

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})(); // Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true 感谢 @bluedrink 提醒,该实现没有修复constructor。 Cat.prototype.constructor = Cat; // 需要修复下构造函数

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

推荐指数:★★★★(实现复杂,扣掉一颗星)

附录代码:

示例一:

function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
//实例引用属性
this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal(); var tom = new Cat('Tom');
var kissy = new Cat('Kissy'); console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // [] tom.name = 'Tom-New Name';
tom.features.push('eat'); //针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat'] 原因分析: 关键点:属性查找过程 执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。

JS继承的实现方式的更多相关文章

  1. js继承——扩展Object方式实现继承

    function Parent(name,sex){ this.name = name; this.sex = sex; this.sayName = function(){ console.log( ...

  2. js 继承的几种方式

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

  3. JS继承的一些见解

    JS继承的一些见解 js在es6之前的继承是五花八门的.而且要在项目中灵活运用面向对象写法也是有点别扭,更多的时候还是觉得面向过程的写法更为简单,效率也高.久而久之对js的继承每隔一段时间就会理解出现 ...

  4. 简单易懂的JS继承图解

    JS继承的实现方式一共有八种.下面我们来一个一个瞅一下.注意️:文章依据个人理解,难免有问题,还望各位及时指出!!!!! 原型链继承 借用构造函数继承 组合继承 原型式继承 寄生继承 寄生组合式继承 ...

  5. JS继承以及继承的几种实现方式总结

    传统面向对象语言:继承是类与类之间的关系. 而在js中由于es6之前没有类的概念,所以继承是对象与对象之间的关系. 在js中,继承就是指使一个对象有权去访问另一个对象的能力. 比如:比如对象a能够访问 ...

  6. JS继承方式详解

    js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现 ...

  7. js继承的方式

    深入理解继承的实现方式不仅仅有利于自己去造轮子,封装插件,更有利于我们去阅读一些框架的源码, 以下记录几种常见的继承方式 1. 原型链实现继承 function Father(){ this.name ...

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

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

  9. js继承的方式及其优缺点

    js继承方法 前因:ECMAScript不支持接口继承,只支持实现继承 一.原型链 概念:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,让 ...

随机推荐

  1. bit、位、byte、字节、B、KB、字符与网速

    一.存储单位bit和Byte 1.bit(比特) bit就是位,也叫比特位,是数据存储的最小单位.简写为小写字母“b” 二进制的一位,每个0或1是一个bit 2.Byte(字节) Byte是字节,也有 ...

  2. C#网络编程:Socket编程

    套接字简介:套接字最早是Unix的,window是借鉴过来的.TCP/IP协议族提供三种套接字:流式.数据报式.原始套接字.其中原始套接字允许对底层协议直接访问,一般用于检验新协议或者新设备问题,很少 ...

  3. ubuntu更改mysql的编码配置

    1.Ctrl+t打开终端 2.输入mysql -u root -p 命令,进入MySQL 输入 SHOW VARIABLES LIKE 'char%'; 查看MySQL编码,有两个不是utf8 3.在 ...

  4. 《学习bash》笔记--调试shell程序

    在shell中,最简单的调试助手时输出语句echo,能够通过把很多echo语句放到代码中进行调试,但必须花费足够的时间以定位 要查看的信息.可能必须通过很多的输出才干发现要查找的信息. 1.set选项 ...

  5. Linux下自动备份MySQL数据库详细操作步骤(转载)

    环境说明操作系统:CentOSIP:192.168.150.214Oracle数据库版本:Oracle11gR2用户:root 密码:123456端口:3306数据库:ts_0.ts_1.ts_2.t ...

  6. linux 木马

    参考 http://wangzan18.blog.51cto.com/8021085/1740113 http://www.cnblogs.com/jluzhsai/p/3756280.html  ( ...

  7. sql 时间相关

    1.常用日期方法(下面的GetDate() = '2006-11-08 13:37:56.233') (1)DATENAME ( datepart ,date ) 返回表示指定日期的指定日期部分的字符 ...

  8. inflate, findViewById与setContentView的差别与联系

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentV ...

  9. anaconda2下面安装opencv2.4.13.4完成----解决默认安装的问题----Thefunction is not implemented. Rebuild the library with Windows, GTK+ 2.x orCarbon support. If you are on Ubuntu or Debian, install libgtk2.0‑dev and pkg

    转载自:http://blog.csdn.net/qingyanyichen/article/details/73550924 本人下载编译安装了opencv2.4.9,oppencv2.4.10,o ...

  10. 智能手机的耗电特征及APP耗电量测试的两种方法

    文章陈述了手机发展趋势及耗电特性,集中讨论了时下最为关心的智能手机耗电问题,并介绍了测量手机软件耗电量的两种方法.此外还解释了为何运营商此前会提出收取微信的费用,心跳机制是什么. 美国著名手机公司Pa ...