美高梅平台下载-美高梅娱乐平台登录

热门关键词: 美高梅平台下载,美高梅娱乐平台登录

寄生组合继承基于Object.create,不支持接口继承

日期:2019-11-26编辑作者:美高梅娱乐平台登录

一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

2018/08/02 · JavaScript · 继承

原文出处: 这是你的玩具车吗   

说实在话,以前我只需要知道“寄生组合继承”是最好的,有个祖传代码模版用就行。最近因为一些事情,几个星期以来一直心心念念想整理出来。本文以《JavaScript高级程序设计》上的内容为骨架,补充了ES6 Class的相关内容,从我认为更容易理解的角度将继承这件事叙述出来,希望大家能有所收获。

JavaScript 六种继承方式

2017/06/20 · JavaScript · 继承

原文出处: Xuthus Blog   

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

1. 继承分类

先来个整体印象。如图所示,JS中继承可以按照是否使用object函数(在下文中会提到),将继承分成两部分(Object.create是ES5新增的方法,用来规范化这个函数)。

其中,原型链继承和原型式继承有一样的优缺点,构造函数继承与寄生式继承也相互对应。寄生组合继承基于Object.create, 同时优化了组合继承,成为了完美的继承方式。ES6 Class Extends的结果与寄生组合继承基本一致,但是实现方案又略有不同。

下面马上进入正题。

图片 1

原型链

首先得要明白什么是原型链,在一篇文章看懂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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"]

1
2
3
4
5
6
7
8
9
10
11
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中。

2. 继承方式

上图上半区的原型链继承,构造函数继承,组合继承,网上内容比较多,本文不作详细描述,只指出重点。这里给出了我认为最容易理解的一篇《JS中的继承(上)》。如果对上半区的内容不熟悉,可以先看这篇文章,再回来继续阅读;如果已经比较熟悉,这部分可以快速略过。另,上半区大量借用了yq前端的一篇继承文章[1]。

借用构造函数

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

这种方法的思想就是在子类构造函数的内部调用父类构造函数,可以借助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"]

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
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

问题

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

2.1 原型式继承

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

SubType.prototype = new SuperType() // 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。 SubType.prototype.constructor = SubType;

1
2
3
SubType.prototype = new SuperType()
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
SubType.prototype.constructor = SubType;

优点:父类方法可以复用

缺点:

  • 父类的引用属性会被所有子类实例共享
  • 子类构建实例时不能向父类传递参数

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

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

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

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

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'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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'

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

2.2 构造函数继承

核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。

SuperType.call(SubType);

1
SuperType.call(SubType);

优点:和原型链继承完全反过来。

  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。

原型式继承

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

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

1
2
3
4
5
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']

1
2
3
4
5
6
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']

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

2.3 组合继承

核心:原型式继承和构造函数继承的组合,兼具了二者的优点。

function SuperType() { this.name = 'parent'; this.arr = [1, 2, 3]; } SuperType.prototype.say = function() { console.log('this is parent') } function SubType() { SuperType.call(this) // 第二次调用SuperType } SubType.prototype = new SuperType() // 第一次调用SuperType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SuperType() {
    this.name = 'parent';
    this.arr = [1, 2, 3];
}
 
SuperType.prototype.say = function() {
    console.log('this is parent')
}
 
function SubType() {
    SuperType.call(this) // 第二次调用SuperType
}
 
SubType.prototype = new SuperType() // 第一次调用SuperType

优点:

  • 父类的方法可以被复用
  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:

调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。

寄生式继承

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

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()

1
2
3
4
5
6
7
8
9
10
11
12
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方法。

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

本文由美高梅平台下载发布于美高梅娱乐平台登录,转载请注明出处:寄生组合继承基于Object.create,不支持接口继承

关键词:

最近在做页面分析的时候发现页面F5刷新时,浏览

美高梅平台下载 ,浏览器缓存机制 2015/12/01 · HTML5 ·HTTP 原文出处:吴秦    浏览器缓存机制 浏览器缓存机制,其实...

详细>>

HTML5受到欢迎的原因也在于它给用户带来的极大便

11 个 HTML5 动画工具 2015/11/16 · HTML5 ·动画 原文出处:devzum   译文出处:[PHP100 Zeroing]()    如今,在Web开发中最为流...

详细>>

而且在2010-05-18写了博文,当使用Keep-Alive模式(又

5、HTTP头字段总结 最后我总结下HTTP协议的头部字段。 1、 Accept:告诉WEB服务器自己接受什么介质类型,*/*表示任何类...

详细>>

减少页面请求 是一个优化页面加载速度很好的方

减少HTTP请求(大型网站优化技术) 2015/11/26 · HTML5 ·HTTP 原文出处: Kelly    在网站开发过程中,对于页面的加载效率一...

详细>>