继承

什么是继承?

我认为利用原型链让一个引用类型,可以使用另一个引用类型的属性和方法,就叫做继承。

继承方式

1.原型链

使用原型链,让一个引用类型继承另外一个引用类型的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Parent(){
this.name = 'qinhanwen';
}
Parent.prototype.getParentName = function(){
console.log(this.name);
}

function Child(){
this.ChildName = 'lalala';
}

//这里继承了Parent
Child.prototype = new Parent();

Child.prototype.getChildName = function(){
console.log(this.ChildName);
}

var child = new Child();
child.getParentName();//qinhanwen
child.getChildName();//lalala
console.log(child);

打印的child如图:

WX20190223-224226@2x

这里的继承其实是重写了Child的原型对象,使它指向了Parent构造函数的实例。

缺点:引用类型会被所有实例共享,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent(){
this.arr = [1,2];
}

function Child(){
}

//这里继承了Parent
Child.prototype = new Parent();

var child = new Child();
child.arr.push(3);

var child1 = new Child();
console.log(child1.arr);// [1, 2, 3]

2.借用构造函数

子构造函数通过new关键字调用的时候,调用到内部调用父构造函数,会创建新的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent(){
this.arr = [1,2];
}
function Child(){
Parent.call(this);
}
var child = new Child();
child.arr.push(3);
console.log(child.arr);//[1, 2, 3]

var child1 = new Child();
console.log(child1);//[1, 2]
console.log(child1);

打印的child1,如图

WX20190224-154036@2x

其实两个new关键字创建出了2个实例里的arr是不同的

3.组合继承

整合原型链和借用构造函数的方式,其实就是在实例上创建新的属性,不像前面是直接共享使用原型链上的属性。

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
function Parent(name){
this.name = name;
this.arr = [1,2];
}

Parent.prototype.sayName = function(){
console.log(this.name);
}

function Child(name,age){
Parent.call(this,name);//调用Parent
this.age = age
}

Child.prototype = new Parent();//调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function(){
console.log(this.age);
}

var child1 = new Child('qinhanwen','25');
child1.arr.push(3);
console.log(child1);

var child2 = new Child('zenghua','26');
console.log(child2);

先看child1打印结果:

WX20190224-160624@2x

可以看到实例上有个arr数组,push操作是往这个arr数组里压入

再看child2打印结果:

WX20190224-160735@2x

跟原型链方式比较:区别是原型链方式没有为实例新建属性,而是在[[Prototype]]上,相同的引用地址指向同一个对象。如果修改__proto__上的arr属性,就会被修改。而且有两次调用Parent函数

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
function Parent(name){
this.name = name;
this.arr = [1,2];
}

Parent.prototype.sayName = function(){
console.log(this.name);
}

function Child(name,age){
Parent.call(this,name);
this.age = age
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.sayAge = function(){
console.log(this.age);
}

var child1 = new Child('qinhanwen','25');
child1.__proto__.arr.push(3);
console.log(child1.__proto__.arr);//[1,2,3]

var child2 = new Child('zenghua','26');
console.log(child2.__proto__.arr);//[1,2,3]

4.原型式继承

这种继承方式要求,使一个对象作为另一个对象的基础,也就是创建一个新的对象,让它出现在另一个对象的[[Prototype]]上。

1
2
3
4
5
6
7
8
9
var person = {
name:'qinhanwen',
arr:[1,2]
}

var anotherPerson = Object.create(person);
anotherPerson.name = 'lalala';
anotherPerson.arr.push(3);
console.log(anotherPerson);

打印的anotherPerson如图:

WX20190224-211331@2x

5.寄生式继承

通过函数,创建一个对象,并在函数内为这个对象新增多的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var createAnotherPerson = function(obj){
var clone = Object(obj);
clone.getName = function(){
console.log(this.name);
}
return clone;
}

var person = {
name:'qinhanwen',
age:25
}

var person1 = createAnotherPerson(person);
person1.getName();//qinhanwen
console.log(person1);

打印的person1如图:

WX20190224-213846@2x

6.寄生组合式继承

其实就是Child.prototype 继承了Parent.prototype,然后在Parent的构造函数内又可以添加自定义的属性。

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
function inherit(subType, superType) {//这个方法做的事情其实就是让subType.prototype === superType.prototype
var prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}

function Parent(name) {
this.name = name;
this.arr = [1, 2];
}

Parent.prototype.getName = function () {
console.log(this.name);
}

function Child(name, age) {
Parent.call(this,name);
this.age = age;
}
inherit(Child, Parent);
Child.prototype.getAge = function () {
console.log(this.age);
}

var child = new Child('qinhanwen', 25);
console.log(child);

打印的child如图:

WX20190224-221848@2x

es6编译成es5 super与extends实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent{
getParentName(){

}
}
class Child extends Parent{
constructor(){
super()
}
getChildName(){
super.getParentName();
}
}
var child = new Child();
child.getChildName();

编译结果:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"use strict";

function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}

function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}

function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}

function _superPropBase(object, property) {
while (!Object.prototype.hasOwnProperty.call(object, property)) {
object = _getPrototypeOf(object);
if (object === null) break;
}
return object;
}

function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}

function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}

function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}

function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}

var Parent =
/*#__PURE__*/
function () {
function Parent() {
_classCallCheck(this, Parent);
}

_createClass(Parent, [{
key: "getParentName",
value: function getParentName() {}
}]);

return Parent;
}();

var Child =
/*#__PURE__*/
function (_Parent) {
_inherits(Child, _Parent);

function Child() {
_classCallCheck(this, Child);

return _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this));
}

_createClass(Child, [{
key: "getChildName",
value: function getChildName() {
_get(_getPrototypeOf(Child.prototype), "getParentName", this).call(this);
}
}]);

return Child;
}(Parent);

var child = new Child();
child.getChildName();
  • 调用_createClass方法为Parent构造函数添加原型与静态属性
  • 调用 _inherits(Child, _Parent)
1
2
3
4
5
6
7
8
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
writable: true,
configurable: true
}
});
if (Parent) _setPrototypeOf(Child, Parent);

也就是Child.prototype.__proto__ = Parent.prototypeChild.__proto__ = Parent

  • 调用_createClass为Child构造函数添加原型与静态属性
  • 创建Child实例
    • 调用 _classCallCheck(this, Child),如果不是Child的实例就扔出错误
    • 接着调用_possibleConstructorReturn(this, _getPrototypeOf(Child).call(this))
      • 先进入_getPrototypeOf调用,得到Child的__proto__,也就是Parent
      • 接着也就是调用Parent.call(this),也就是调用_classCallCheck(this, Parent)
    • 调用_possibleConstructorReturn
      • 进入_assertThisInitialized(self)调用,如果self等于undefined,扔出错误。
  • 调用child.getChildName方法,也就是调用到下面这个
1
_get(_getPrototypeOf(Child.prototype), "getParentName", this).call(this);

最后进入

1
return _get(target, property, receiver || target)

得到Parent上的getParentName方法

总结

extends实现总的来说就是

Child.prototype.__proto__ = Parent.prototype

Child.__proto__ = Parent

super的实现是先获取了原型上的某个方法,再调用