JavaScript的六种继承方式(推荐)

继承是面向对象编程中又一非常重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的。

原型链

首先得要明白什么是原型链,在一篇文章看懂proto和prototype的关系及区别中讲得非常详细

原型链继承基本思想就是让一个原型对象指向另一个类型的实例

function SuperType() {
 this.property = true
}
SuperType.prototype.getSuperValue = function () {
 return this.property
}
function SubType() {
 this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
 return this.subproperty
}
var instance = new SubType()
console.log(instance.getSuperValue()) // true 

代码定义了两个类型SuperType和SubType,每个类型分别有一个属性和一个方法,SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。

实现的本质是重写原型对象,代之以一个新类型的实例,那么存在SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。

我们知道,在创建一个实例的时候,实例对象中会有一个内部指针指向创建它的原型,进行关联起来,在这里代码SubType.prototype = new SuperType(),也会在SubType.prototype创建一个内部指针,将SubType.prototype与SuperType关联起来。

所以instance指向SubType的原型,SubType的原型又指向SuperType的原型,继而在instance在调用getSuperValue()方法的时候,会顺着这条链一直往上找。

添加方法

在给SubType原型添加方法的时候,如果,父类上也有同样的名字,SubType将会覆盖这个方法,达到重新的目的。 但是这个方法依然存在于父类中。

记住不能以字面量的形式添加,因为,上面说过通过实例继承本质上就是重写,再使用字面量形式,又是一次重写了,但这次重写没有跟父类有任何关联,所以就会导致原型链截断。

function SuperType() {
 this.property = true
}
SuperType.prototype.getSuperValue = function () {
 return this.property
}
function SubType() {
 this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype = {
 getSubValue:function () {
 return this.subproperty
 }
}
var instance = new SubType()
console.log(instance.getSuperValue()) // error 

问题

单纯的使用原型链继承,主要问题来自包含引用类型值的原型。

function SuperType() {
 this.colors = ['red', 'blue', 'green']
}
function SubType() {
}
SubType.prototype = new SuperType()
var instance1 = new SubType()
var instance2 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "blue", "green", "black"]
console.log(instance2.colors) // ["red", "blue", "green", "black"] 

在SuperType构造函数定义了一个colors属性,当SubType通过原型链继承后,这个属性就会出现SubType.prototype中,就跟专门创建了SubType.prototype.colors一样,所以会导致SubType的所有实例都会共享这个属性,所以instance1修改colors这个引用类型值,也会反映到instance2中。

借用构造函数

此方法为了解决原型中包含引用类型值所带来的问题。

这种方法的思想就是在子类构造函数的内部调用父类构造函数,可以借助apply()和call()方法来改变对象的执行上下文

function SuperType() {
 this.colors = ['red', 'blue', 'green']
}
function SubType() {
 // 继承SuperType
 SuperType.call(this)
}
var instance1 = new SubType()
var instance2 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "blue", "green", "black"]
console.log(instance2.colors) // ["red", "blue", "green"] 

在新建SubType实例是调用了SuperType构造函数,这样以来,就会在新SubType对象上执行SuperType函数中定义的所有对象初始化代码。

结果,SubType的每个实例就会具有自己的colors属性的副本了。

传递参数

借助构造函数还有一个优势就是可以传递参数

function SuperType(name) {
 this.name = name
}
function SubType() {
 // 继承SuperType
 SuperType.call(this, 'Jiang')
 this.job = 'student'
}
var instance = new SubType()
console.log(instance.name) // Jiang
console.log(instance.job) // student 

问题

如果仅仅借助构造函数,方法都在构造函数中定义,因此函数无法达到复用

组合继承(原型链+构造函数)

组合继承是将原型链继承和构造函数结合起来,从而发挥二者之长的一种模式。

思路就是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SuperType(name) {
 this.name = name
 this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
 console.log(this.name)
}
function SubType(name, job) {
 // 继承属性
 SuperType.call(this, name)
 this.job = job
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SuperType
SubType.prototype.sayJob = function() {
 console.log(this.job)
}
var instance1 = new SubType('Jiang', 'student')
instance1.colors.push('black')
console.log(instance1.colors) //["red", "blue", "green", "black"]
instance1.sayName() // 'Jiang'
instance1.sayJob() // 'student'
var instance2 = new SubType('J', 'doctor')
console.log(instance2.colors) // //["red", "blue", "green"]
instance2.sayName() // 'J'
instance2.sayJob() // 'doctor' 

这种模式避免了原型链和构造函数继承的缺陷,融合了他们的优点,是最常用的一种继承模式。

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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

在object函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

本质上来说,object对传入其中的对象执行了一次浅复制。

var person = {
 name: 'Jiang',
 friends: ['Shelby', 'Court']
}
var anotherPerson = object(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court'] 

这种模式要去你必须有一个对象作为另一个对象的基础。

在这个例子中,person作为另一个对象的基础,把person传入object中,该函数就会返回一个新的对象。

这个新对象将person作为原型,所以它的原型中就包含一个基本类型和一个引用类型。

所以意味着如果还有另外一个对象关联了person,anotherPerson修改数组friends的时候,也会体现在这个对象中。

Object.create()方法

ES5通过Object.create()方法规范了原型式继承,可以接受两个参数,一个是用作新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object一样,除了object不能接受第二个参数以外。

var person = {
 name: 'Jiang',
 friends: ['Shelby', 'Court']
}
var anotherPerson = Object.create(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court'] 

寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数。

function createAnother(o) {
 var clone = Object.create(o) // 创建一个新对象
 clone.sayHi = function() { // 添加方法
 console.log('hi')
 }
 return clone // 返回这个对象
}
var person = {
 name: 'Jiang'
}
var anotherPeson = createAnother(person)
anotherPeson.sayHi() 

基于person返回了一个新对象anotherPeson,新对象不仅拥有了person的属性和方法,还有自己的sayHi方法。

在主要考虑对象而不是自定义类型和构造函数的情况下,这是一个有用的模式。

寄生组合式继承

在前面说的组合模式(原型链+构造函数)中,继承的时候需要调用两次父类构造函数。

父类

function SuperType(name) {
 this.name = name
 this.colors = ['red', 'blue', 'green']
} 

第一次在子类构造函数中

function SubType(name, job) {
 // 继承属性
 SuperType.call(this, name)
 this.job = job
} 

第二次将子类的原型指向父类的实例

// 继承方法
SubType.prototype = new SuperType() 

当使用var instance = new SubType()的时候,会产生两组name和color属性,一组在SubType实例上,一组在SubType原型上,只不过实例上的屏蔽了原型上的。

使用寄生式组合模式,可以规避这个问题。

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

基本思路:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本。

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

function inheritPrototype(subType, superType) {
 var prototype = Object.create(superType.prototype)
 prototype.constructor = subType
 subType.prototype = prototype
} 

该函数实现了寄生组合继承的最简单形式。

这个函数接受两个参数,一个子类,一个父类。

第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三部将子类的原型指向这个副本。

function SuperType(name) {
 this.name = name
 this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
 console.log(this.name)
}
function SubType(name, job) {
 // 继承属性
 SuperType.call(this, name)
 this.job = job
}
// 继承
inheritPrototype(SubType, SuperType)
var instance = new SubType('Jiang', 'student')
instance.sayName() 

补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示可以更容易理解。

function SuperType(name) {
 this.name = name
 this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
 console.log(this.name)
}
function SubType(name, job) {
 // 继承属性
 SuperType.call(this, name)
 this.job = job
}
// 继承
SubType.prototype = Object.create(SuperType.prototype)
// 修复constructor
SubType.prototype.constructor = SubType
var instance = new SubType('Jiang', 'student')
instance.sayName() 

ES6新增了一个方法,Object.setPrototypeOf,可以直接创建关联,而且不用手动添加constructor属性。

// 继承
Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
console.log(SubType.prototype.constructor === SubType) // true 

以上所述是小编给大家介绍的JavaScript的六种继承方式(推荐),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Javascript中的几种继承方式对比分析

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

  • JavaScript中的继承方式详解

    js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现 在面向对象的语言中,我们使用类来创建一个自定义对象.然而js中所有事物都是对象,那么用什么办法来创建自定义对象呢?这就需要用到js的原型: 我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模

  • Javascript编程中几种继承方式比较分析

    本文实例分析了Javascript编程中几种继承方式比较.分享给大家供大家参考,具体如下: 开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究

  • javascript学习笔记(九)javascript中的原型(prototype)及原型链的继承方式

    在使用面向对象编程时,对象间的继承关系自然少不了!而原型正是实现javascript继承的很重要的一种方法! 我们首先来看以下代码: 复制代码 代码如下: function person(name, age) { this.name = name; this.age = age; } person.prototype.getInfo = function() { alert("My name is "+this.name+", and I have "+this.a

  • JavaScript继承方式实例

    复制代码 代码如下: function parent(){ this.x=10; } function child(){ var parentObj=new parent(); for(var p in parentObj)this[p]=parentObj[p]; } var childObj=new child(); alert(childObj.x); 复制代码 代码如下: function parent(){ this.x=10; } function child(){ this.par

  • 浅谈js中的三种继承方式及其优缺点

    第一种,prototype的方式: //父类 function person(){ this.hair = 'black'; this.eye = 'black'; this.skin = 'yellow'; this.view = function(){ return this.hair + ',' + this.eye + ',' + this.skin; } } //子类 function man(){ this.feature = ['beard','strong']; } man.pr

  • js的2种继承方式详解

    js中继承可以分为两种:对象冒充和原型链方式 一.对象冒充包括三种:临时属性方式.call()及apply()方式1.临时属性方式 复制代码 代码如下: function Person(name){     this.name = name;     this.say = function(){          alert('My name is '+this.name);     }}function F2E(name,id){     this.temp = Person;     thi

  • 浅析2种JavaScript继承方式

    js继承方法最主要的是2种,一种是通过原型的方式,一种是通过借用call&apply的构造函数方式. 1.原型(prototype): function Body(name,age){// 创建一个Body类 this.name = name;// 赋予基础属性name.age this.age = age; } Body.prototype.sayName =function() {// 给原型定义一个sayName的方法 console.log(this.name); } var a = n

  • JavaScript是如何实现继承的(六种方式)

    前言:大多OO语言都支持两种继承方式: 接口继承和实现继承 ,而ECMAScript中无法实现接口继承,ECMAScript只支持实现继承,而且其实现继承主要是依靠 原型链 来实现. 1.原型链 基本思想:利用原型让一个引用类型继承另外一个引用类型的属性和方法. 构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针. 原型链实现继承例子: function SuperType() { this.property

  • JavaScript的六种继承方式(推荐)

    继承是面向对象编程中又一非常重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的. 原型链 首先得要明白什么是原型链,在一篇文章看懂proto和prototype的关系及区别中讲得非常详细 原型链继承基本思想就是让一个原型对象指向另一个类型的实例 function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function () { return thi

  • 重新理解JavaScript的六种继承方式

    类式继承(构造函数) JS中其实是没有类的概念的,所谓的类也是模拟出来的.特别是当我们是用new 关键字的时候,就使得"类"的概念就越像其他语言中的类了.类式继承是在函数对象内调用父类的构造函数,使得自身获得父类的方法和属性.call和apply方法为类式继承提供了支持.通过改变this的作用环境,使得子类本身具有父类的各种属性. var father = function() { this.age = 52; this.say = function() { alert('hello

  • JavaScript 常见的继承方式汇总

    原型链机制: 在ECMAscript中描述了原型链的概念,并将原型链作为实现继承的主要方法,其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法. 构造函数和原型还有实例之间的关系: 每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针 ( __propto__ ) .关系图如下图所示: 每一个Function都是Object基类的一个实例,所以每一个Function上都有一个__

  • JS中的六种继承方式以及优缺点总结

    目录 前言 原型链继承 构造函数继承 组合继承(原型链继承和构造函数继承组合) 寄生式继承 组合寄生式继承 extends继承 总结 前言 继承是JS世界中必不可少的一个环节,号称JS的三座大山之一,使用这种方式我们可以更好地复用以前的开发代码,缩短开发的周期.提升开发效率 在ES6之前,JS中的类都是通过构造函数模拟的,并不存在真正意义上的类,虽然ES6的类知识一个语法糖

  • 一文弄懂JavaScript的继承方式

    目录 JavaScript中的继承方式 问:JavaScript中有几种继承方式呢 问:每种继承方式是怎么实现的呢 盗用构造函数 组合继承 原型链式继承 寄生式继承 寄生时组合继承 JavaScript中的继承方式 问:JavaScript中有几种继承方式呢 emmm...六种?五种?还是四种来着... 这次记清楚了 一共有五种继承方式 盗用构造函数 (经典继承方式 ) 组合继承 原型链式继承 寄生式继承 寄生式组合继承 问:每种继承方式是怎么实现的呢 盗用构造函数 基本思路很简单:在子类构造函

  • JS高级ES6的6种继承方式

    目录 1.原型链继承 2.借助构造函数继承 3.组合式继承 4.原型式继承 5.寄生式继承 6.寄生组合式继承 前言: 继承是面向对象中老生常谈的一个内容,在ECMAScript6之前,JavaScript中的继承可谓是非常的繁琐的,有各种各样的继承,本质上所有的继承都是离不开原型链的,ES6新增的extends关键字也是通过原型链实现的继承,但是语法相对来说就简单了很多. 关于原型链的内容,可以参考上篇文章两张图搞懂原型链 本篇文章就来介绍一下在ECMAScript6之前是怎么实现继承的. 1

  • 详述JavaScript实现继承的几种方式(推荐)

    ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的. 原型链 原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针.如果:我们让原型对象A等于另一个类型B的实例,那么原型对象A就会有一个指针指向B的原型对象,相应的B的原型对象中保存着指向其构造函数的指针.假如B的原型对象又是另一个类型的实例,那么上述的关系依旧成立,如此层层递进,就构成了实例与原型的

随机推荐