javascript中继承的几种方式

为什么需要继承?

在实际编码的过程中,如果有很多类似的方法都存放于构造函数中,这样会导致内存的浪费(内存泄漏),在这种情况下我们就需要用到继承。

继承是什么?

所谓继承就是通过某种方式让一个对象可以访问到另一个对象中的属性和方法。

在JavaScript中常用的几种继承方式

  • 原型链继承
  • 借用构造函数继承
  • 组合模式继承
  • 共享原型继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承
  • ES6中class的继承(新)

一、原型链继承

通过实例化一个函数使子类的原型指向父类的实例,子类就可以调用到父类的属性和方法。代码实现如下:

function Parent() {this.parentName = '父类';
}
Parent.prototype.getParentName = function() {return this.parentName;
};function Child() {this.childName = '子类';
}
Child.prototype = new Parent();
Child.prototype.getChildName = function() {return this.childName
};var c = new Child();
console.log(c.getParentName()); // '父类'

要注意的问题
1.不要忘记默认的类型:
在js中所有的引用类型都继承了Object,而继承也是通过原型链实现的。所有存在于Object原型对象上的方法同样也存在。比如:hasOwnProperty()、toString()、valueOf()等;

2.确定原型和实例的关系:

  • 第一种:使用instanceof 操作符,这个操作符用于测试实例于原型链中的构造函数,如果存在则返会true反之返回false;
  • 第二种:使用isPrototyoeOf()方法。该方法测试原型链中的原型,只要是原型链中出现过的原型,都可以说是该原型链派生出的实例的原型,因此只要是出现在原型链中的方法用该方法都会返回true反之返回false

用代码打印上述代码的对比关系:


console.log(c instanceof Object); //true
console.log(c instanceof Parent); //true
console.log(c instanceof Child); //trueconsole.log(Object.prototype.isPrototypeOf(c)); //true
console.log(Parent.prototype.isPrototypeOf(c)); //true
console.log(Child.prototype.isPrototypeOf(c)); //true

3.子类要在继承后定义新方法:

因为,原型链的实质使重写原型对象。若在继承前为子类的prototype上定义方法,这些方法将在继承后被覆盖。

4.不能够使用对象字面量创建原型方法

如上所说,原型链的实质是重写原型对象。当使用字面量创建原型方法的时候,实质上相当于重写了原型链,导致原来的原型链被切断。


function Parent() {this.parentName = '父类';
}
Parent.prototype.getParentName = function() {return this.parentName;
};function Child() {this.childName = '子类';
}
// 继承 Parent
Child.prototype = new Parent();
// 使用对象字面量添加新方法,会导致上一行代码无效
Child.prototype = {getChildName: function() {return this.childName;},someOtherMethod: function() {return false;}
}var c = new Child()
console.log(c.getParentName) // undefined

5.注意父类包含引用类型的情况:

function Parent() {this.name = "父类";this.hobbies = ["sing", "dance", "rap"];
}
function Child() {}
// 继承 Parent
Child.prototype = new Parent();var c1 = new Child();
c1.name = "c1";
c1.hobbies.push("coding");
console.log(c1.name);
console.log(c1.hobbies);var c2 = new Child();
console.log(c2.name);
console.log(c2.hobbies);

上面代码运行结果如下:

"c1"
["sing", "dance", "rap", "coding"]
"父类"
["sing", "dance", "rap", "coding"]

原型链继承的优缺点:

  • 优点:写法方便简洁、容易理解;

  • 缺点:在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型原型上变成原型属性被所有子类型实例所共享。同时在创建子类型的实例时,不能向超类型的构造函数中传递参数。

二、借用构造函数继承(对象冒充)

在解决原型链继承中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术。

该技术的思路相当简单——在子类型的构造函数中调用超类型的构造函数。

function Parent(name) {this.name = name;this.hobbies = ["sing", "dance", "rap"];
}function Child(name) {Parent.call(this, name);this.age = 24
}var c1 = new Child('c1');
var c2 = new Child('c2');
c1.hobbies.push('coding');console.log(c1.hobbies)
console.log(c2.hobbies)
console.log(c1 instanceof Parent)
console.log(c1 instanceof Child)

上述代码的输出结果如下:

["sing", "dance", "rap", "coding"]
["sing", "dance", "rap"]
false
true

借用构造函数的基本思想就是利用 call 或者 apply 把父类中通过this指定的方法复制到子类创建的实例中。

因为this对象是在运行时基于函数的执行环境绑定的。那也就是说,在全局中,this等于window,而当函数被作为某个对象的方法调用时,this就等于那个对象。 callapply方法可以替代另一个对象调用一个方法。callapply方法可以将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象。
所以,这个借用构造函数就是,new 对象的时候,创建了一个新的实例对象,并且执行Child里面的代码,而Child里面用 call 调用了Parent,也就是说把this指向改成了指向新的实例,所以就会把Parent里面的this相关属性及方法赋值到新的实例上,而不是赋值到Child里面。所有实例中就拥有了父类定义的这些this里面的方法。

借用构造函数继承的优点:

  • 解决了引用类型的值被实例共享的问题
  • 可以向超类传递参数
  • 可以是按多继承(call若干个超类)

借用构造函数继承的缺点

  • 不能继承超类原型上的属性和方法
  • 无法实现函数复用,由于call有很多个父类实例的副本,性能损耗。
  • 原型链丢失

三、组合模式继承

组合式继承是将原型链继承和构造函数继承二者取其长处组合到一起而产生的继承模式。

function Parent(name){this.name = name;this.hobbies = ["sing", "dance", "rap"];
}
Parent.prototype.getName = function(){return this.name
}
function Child(name){Parent.call(this, name);this.age = 24
}Child.prototype = new Parent('父类')
var c1 = new Child('c1');
var c2 = new Child('c2');console.log(c1.hasOwnProperty('name')); // true
console.log(c1.getName()); // "c1"c1.hobbies.push('coding');
console.log(c1.hobbies); // ["sing", "dance", "rap", "coding"]
console.log(c2.hobbies); // ["sing", "dance", "rap"]

组合式继承的优点

  • 即通过在原型上定义方法实现了函数复用,又保证每个实例都有自己的属性
  • 解决了原型链继承和借用构造函数继承造成的影响。

组合式继承的缺点
导致无论在什么情况下都会调用两次超类型构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数的内部。

四、共享原型继承

该继承方式使得子类和父类共用一个原型。

function Parent(){}
Parent.prototype.hobbies = ["sing", "dance", "rap"];function Child(name, age){this.name = name;this.age = age;
}
Child.prototype = Parent.prototype;var c1 = new Child("c1", 20);
var c2 = new Child("c2", 24);c1.hobbies.push("coding");
console.log(c1.hobbies); // ["sing", "dance", "rap", "coding"]
console.log(c2.hobbies); // ["sing", "dance", "rap", "coding"]
console.log(c1.name); // "c1"
console.log(c2.name); // "c2"

共享原型继承的优点

  • 简单!!!

共享原型继承的缺点

  • 只能继承父类原型属性方法,不能继承构造函数属性方法
  • 与原型链继承一样0存在引用类型问题

五、原型式继承

该继承方法多用于基于当前已有对象创建新对象。在另一个函数o1内部创建一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上函数o1对传入的对象进行了一次浅拷贝。

ES5通过新增Object.create()方法将原型是继承进行了规范化。该方法接受两个参数:

  • 第一个参数:作为新对象原型的对象
  • 第二个参数(可选):给新对象定义额外属性的对象。
// 用法一:创建一个纯洁的对象:对象什么属性都没有
Object.create(null);// 用法二:创建一个子对象,它继承自某个父对象
var o1 = {name: '父对象',say: function() {}
}
var o2 = Object.create(o1);

原型式继承的优点

  • 不需要单独创建构造函数

原型式继承的缺点

  • 属性中包含的引用值始终会在相关对象间共享。

六、寄生式继承

寄生式继承是原型式继承的加强版,它结合原型式继承和工厂模式,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。

function createAnother(origin) {var clone = Object.create(origin); // 通过调用函数创建一个新对象clone.sayHi = function() { // 以某种方式来增强这个对象alert("Hi");};return clone; // 返回这个对象
}var o1 = {name: "父对象",hobbies: ["sing", "dance", "rap"]
};
var o2 = createAnother(o1);
o2.sayHi();

上述代码中,createAnother函数接受了一个参数,也就是将要被继承的对象。
o2是基于o1创建的一个新对象,新对象不仅具有o1的所有属性和方法,还有自己的方法。简单来说,寄生式继承在产生了这个继承父类的对象之后,为这个对象添加了一些增强方法。

寄生式继承的优点

写法简单,不需要单独创建函数。

寄生式继承的缺点

通过该方式给对象添加函数会导致函数难以复用。

七、寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来实现继承的方法。

本质上就是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型。

基本写法:

function inheritPrototype(SubType, SuperType) {var prototype = Object.create(SuperType.prototype);prototype.constructor = SubType;SubType.prototype = prototype;
}

兼容写法

function object(o) {function W() {}W.prototype = o;return new W;
}
function inheritPrototype(SubType, SuperType) {var prototype;if (typeof Object.create === 'function') {prototype = Object.create(SuperType.prototype);} else {prototype = object(SuperType.prototype);}         prototype.constructor = SubType;SubType.prototype = prototype;
}

完美写法:

function Parent(name) {this.name = name;this.hobbies = ["sing", "dance", "rap"];
}
Parent.prototype.getHobbies = function(){return this.hobbies
}
function Child(name) {Parent.call(this, name);this.age = 24
}Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;// 测试结果
var c1 = new Child('c1');
var c2 = new Child('c2');console.log(c1 instanceof Child); // true
console.log(c1 instanceof Parent); // true
console.log(c1.constructor); // Child
console.log(Child.prototype.__proto__ === Parent.prototype); // true
console.log(Parent.prototype.__proto__ === Object.prototype); // truec1.hobbies.push('coding');
console.log(c1.getHobbies()); // ["sing", "dance", "rap", "coding"]
console.log(c2.getHobbies()); // ["sing", "dance", "rap"]

寄生组合式继承的优点

高效率只调用一次父构造函数,并且因此避免了在子原型上面创建的不必要、多余的属性。与此同时,原型链还保持不变。

寄生组合式继承的缺点

代码复杂!!!

八、ES6——class继承

原理:ES5的继承,实质是先创造的子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质就是先将父类实例对象上的方法和属性,加到this上面(所以必须调用super()方法),然后再用子类的构造函数修改this。
需要注意的是:class关键字只是原型的语法糖,js继承依然是基于原型实现的。

class Parent{constructor(name) {this.name = name;this.hobbies = ["sing", "dance", "rap"];}getHobbies() {return this.hobbies;}static getCurrent() {console.log(this);}
}class Child extends Parent {constructor(name) {super(name);}
}var c1 = new Child('c1');
var c2 = new Child('c2');console.log(c1 instanceof Child); // true
console.log(c1 instanceof Parent); // true

class继承的优点

语法简单易懂,操作更加方便

class继承的缺点

不是每个浏览器都支持class关键字

javascript中继承的几种方式相关推荐

  1. javascript实现继承的七种方式(from 红宝书)

    继承是面向对象语言的基础概念,一般OO语言支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.ECMAScript中函数没有签名,因此无法实现接口继承.ECMAS ...

  2. JavaScript 实现继承的5种方式

    js是一个面向对象的语言,所以具备一些面向对象的方式----------例如继承.接下来介绍5种js的继承方式.注意:js 中的函数其实是对象,函数名是对 Function 对象的引用. 1.采用ca ...

  3. JavaScript实现继承的几种方式

    1.原型链 基本思想:利用原型让一个引用类型继承另外一个引用类型的属性和方法. 构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原 ...

  4. javascript中创建对象的几种方式

    虽然new Object( )和对象字面量的都可以用来创建单个对象,但有个缺点:如果使用同一接口创建许多对象,那么会产生冗余代码. 一.工厂模式 function createPerson(name, ...

  5. Django中Model继承的三种方式

    Django中Model继承的三种方式 Django中Model的继承有三种: 1.抽象继承 2.多表继承 3.proxy model(代理model) 1.抽象继承 第一种抽象继承,创建一个通用父类 ...

  6. JS 总结之原型继承的几种方式

    在之前的总结中,我们详细分析了原型<JS 总结之原型>,原型很大作用用于模拟继承,这一次,我们来聊原型继承的几种方式. function Person (age) {this.age = ...

  7. js中继承的几种用法总结(apply,call,prototype)

    本篇文章主要介绍了js中继承的几种用法总结(apply,call,prototype) 需要的朋友可以过来参考下,希望对大家有所帮助 一,js中对象继承 js中有三种继承方式 1.js原型(proto ...

  8. 继承有几种方式,分别是什么,想要实现继承可以使用哪些方法

    这里是修真院前端小课堂,每篇分享文从 [背景介绍][知识剖析][常见问题][解决方案][编码实战][扩展思考][更多讨论][参考文献] 八个方面深度解析前端知识/技能,本篇分享的是: [继承有几种方式 ...

  9. JavaScript事件处理程序的3种方式

    最近这段时间因为每天要修改网站,为网站做特效,所以看了很多的js接触事件,自己只会使用一小部分,有时用的时候也比较混乱,现在系统的整理了一下,首先跟大家在马海祥博客上跟大家分享的是JavaScript ...

  10. Javascript事件绑定的几种方式

    Javascript事件绑定的几种方式 来源:http://www.cnblogs.com/rainman/archive/2009/02/11/1387955.html 上篇文章讲到了事件绑定的3中 ...

最新文章

  1. Linux实用命令总结
  2. Android数据存储之SQLite
  3. 【jzoj】2018.2.7NOIP普及组——某【BC】组模拟赛
  4. Volley网络请求框架简析——Android网络请求框架(三)
  5. linux解压覆盖命令
  6. 用nohup执行python程序时,print无法输出
  7. jquery 加载显示loading图标_王者荣耀100强什么意思?loading界面左上角100强有什么含义...
  8. Apache 基金会宣布 Apache Kylin 成为顶级项目
  9. XNA 如何使用字体绘制文字,Windows Phone 游戏开发
  10. PPT精品教程隐私政策
  11. uniapp h5在线预览word文档ppt等
  12. 小白教程系列——MultiDesk连接服务器
  13. 利用nginx搭建静态资源服务器,把服务器本地文件对外可直接访问
  14. [大数据、Hadoop、数据采集、MySQL、计算机基础、Windows、练习题库、面试]
  15. 无显卡本地运行katago围棋程序
  16. 【已解决】WPS/OFFICE中word文件可以打印,excel打印后无响应
  17. 在线阅读Linux内核源代码
  18. python爬取小说写入txt_燎原博客—python爬取网络小说存储为TXT的网页爬虫源代码实例...
  19. 大数据分析了50万条拼多多商品数据, 得出了这样的结论
  20. FTP协议的工作流程

热门文章

  1. 手持式频谱分析仪帮助实施毫米波无线信道调查
  2. DirectX游戏开发之3D角色动起来(上)
  3. Intel 系统级架构概述
  4. HttpClient4
  5. 在Qt中使用大漠插件示例(版本3.1233),包含下载、注册、使用教程
  6. 【问】SQL 2000 中文版打不上SP4,提示用户验证没有通过
  7. android应用图标的尺寸大小,Android APP LOGO尺寸
  8. 佳能mp236打印机驱动 官方版
  9. 基于GraphQL的数据网关实现
  10. SpringMVC 工作原理