# 什么是继承
继承是什么,我的一点粗浅的理解就是汉语中的“同上”。目的在于代码的复用。继承这个概念,按照其字面意思直接理解即接收前人的东西。在计算机科学中,则是指支持面向对象编程语言中的专用术语。
如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。
在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。
另外,为子类追加新的属性和方法也是常见的做法。
---维基百科
但并不是所有的编程语言都有继承机制,前面有说到,继承是面向对象中的术语,C语言就没有。为什么JavaScript需要继承机制的存在,这里贴一小段阮一峰老师对历史的探究。原文点击这里 (opens new window)。
如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
俗话说就是,继承机制的存在使得代码中各个对象能够产生联系,而不是独立的,产生的联系的意义在于可以少写代码。说到底,还是为了“偷点懒”(提高效率)。
如何实现继承,其实,在JavaScript中,当一个对象的__proto__
属性指向一个有效的对象时,继承就发生了,但实现继承的方式远不止这一种。 // 突然觉得这句话不对,原型链的存在就一定意味着是继承的关系吗。
在 JavaScript 中,构造函数其实就是一个普通的函数。当使用 new 操作符来作用这个函数时,它就可以被称为构造方法(构造函数)。
如果用Object.create创建对象,就可以使用现有的对象来提供新创建的对象的__proto__。之前有个方法是Object.setPrototypeOf()
,手动设置某个对象的原型。
而现在的create
是直接将新创建对象的原型指向create方法的参数。
let a = {a: 1};
let b = Object.create(a);
console.log(b.a);
// 1 (继承而来)
2
3
4
5
6
其实就相当于如下代码,只不过使用create会让整个逻辑连贯一些。
let b = {}
Object.setPrototypeOf(b, a)
2
# 做个题
有如下题目
- 写出一个构造函数 Animal
- 输入为空
- 输出为一个新对象,该对象的共有属性为 {行动: function(){}},没有自有属性
- 再写出一个构造函数 Human
- Human 继承 Animal
- 输入为一个对象,如 {name: 'Frank', birthday: '2000-10-10'}
- 输出为一个新对象,该对象自有的属性有 name 和 birthday,共有的属性有物种(人类)、行动和使用工具
- 再写出一个构造函数 Asian
- Asian 继承 Human
- 输入为一个对象,如 {city: '北京', name: 'Frank', birthday: '2000-10-10' }
- 输出为一个新对象,该对象自有的属性有 name city 和 birthday,共有的属性有物种(人类)、行动和使用工具和肤色
# ES5里的继承
原型链实现
function Animal () {
}
Animal.prototype.action = function () {
console.log('行动的共有属性')
};
function Human (obj = {}) {
this.name = obj.name;
this.birthday = obj.birthday
}
Human.prototype = Object.create(Animal.prototype) // 这个写法只会拿到其原型上的属性
// Human.prototype = new Animal() // 这个写法会导致私有属性重复
// 2021.1.26 但是发现使用create会导致constructor属性的丢失
Human.prototype.species = '人类';
Human.prototype.toolUse = function() {
console.log('使用工具的共有属性')
};
function Asian (obj = {}) {
this.city = obj.city;
this.name = obj.name;
this.birthday = obj.birthday
}
Asian.prototype = Object.create(Human.prototype);
Asian.prototype.color = 'yellow';
let sam = new Asian({city: 'shenzhen', name: 'sam', birthday: '828'})
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
29
30
31
结果如下,如果使用上面注释一行的代码实现继承,就会导致name和birthday重复
sam = {
birthday: "828",
city: "shenzhen",
name: "sam",
__proto__: Asian,
color: "yellow",
__proto__: Human,
species: "人类",
toolUse: f(),
__proto__: Animal,
action: f()
}
2
3
4
5
6
7
8
9
10
11
12
# ES6里的继承
extends实现
类可以通过extends关键字实现继承。类是面向对象里面的一个概念,虽然JS也是面向对象,但在ES6之前并没有类这个概念,在这之前JavaScript的面向对象都是不完备的面向对象。
类的返回值是对象,构造函数的返回值也是对象,所以在ES6之前,类这个概念是通过构造函数来实现。
ES6中引入的JavaScript类实质上是JavaScript现有的基于原型的继承的语法糖。类语法并没有为JavaScript引入新的面向对象的继承模型。
定义一个类的方法是使用一个类声明。要声明一个类,需要使用带有class关键字的类名。
// 类声明
class rectangle{
constructor(height,width){
this.width = width
this.height = height
}
}
// 类表达式,类名非必须
let rectangle = class (className) {
constructor(height, width) {
this.height = height;
this.width = width;
}
2
3
4
5
6
7
8
9
10
11
12
13
函数声明和类的声明之间的一个重要区别是函数声明会提升,类声明不会,所以需要先声明再访问。
构造函数、静态方法、原型方法、getter、setter、父类、子类,这些名词是类这一概念中的术语。
据此来实现前面提到的继承题目如下:
class Animal {
action () {console.log('行动的共有属性')}
}
class Human extends Animal {
constructor (params) {
super()
// name和birth 称为实例属性
this.name = params.name
this.birth = params.birth
}
species () {return '人类'}
toolUse () {return '使用工具的共有属性'}
}
class Asian extends Human {
constructor (params) {
super(params)
this.city = params.city
}
color () {return '肤色'}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 构造函数
constructor 方法是一个特殊的方法,其用于创建和初始化使用class创建的一个对象。一个类只能拥有一个名为“constructor”的特殊方法。 如果多个,则报语法错误。一个构造函数可以使用super关键字来调用一个父类的构造函数。
- 每一个函数数据类型(普通函数,类)都有一个天生自带的属性 :prototype (原型),并且这个属性是一个对象数据类型的值。
- 并且在prototype上浏览器天生给他加了一个属性constructor(构造函数),属性值是当前函数(类)本身。
- 每一个对象数据类型(普通的对象、实例、prototype等)也天生自带一个属性:proto,属性值是当前实例所属类的原型。
四种用于拓展原型链的方法
- new 和 构造函数的配合
- Object.create
- Object.setPrototypeOf
- proto
实例化和继承是两个不同的概念 其中的称呼是 类和实例 & 父类和子类
function dialog(target) {
this.dialog = target
// 默认return this
// 如果手动return 值类型,会被忽略
// 如果手动return 引用类型,会覆盖默认的
}
2
3
4
5
6
什么是原型对象,和对象区别在于?
在JavaScript中,每个函数都是一个Function对象。
相关文章:
https://juejin.im/post/5d615b7f6fb9a06b0202ccb5
https://zhuanlan.zhihu.com/p/57336944
https://www.zcfy.cc/article/master-the-javascript-interview-what-s-the-difference-between-class-amp-prototypal-inheritance-2185.html
https://2ality.com/2015/09/proto-es6.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Public_class_fields#%E7%A7%81%E6%9C%89%E5%AD%97%E6%AE%B5
// ES6实现继承中的一些概念
class Person {
static a = '静态公有字段'
b = '公有实例字段'
// 静态私有字段可以在类声明内部的构造函数上被访问到
static #c
// 私有实例字段
#privateField
constructor() {
this.#privateField =83
}
static logName() {
console.log('这是一个静态公共方法')
}
logAge() {
console.log('这是一个公共实例方法')
}
static #privateStaticMethod() {
console.log('这是一个静态私有方法')
}
#privateMethod() {
console.log('私有实例方法')
}
}
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
29
30
31
32
33