JavaScript之继承
本文记录JavaScript的几种继承方式
原型链继承
function Parent() {
this.tpye = 'parent'
this.age = 88
this.like = ['🏀']
}
Parent.prototype.getAge = function () {
return this.age
}
function Child() {
this.type = 'child'
}
Child.prototype = new Parent()
const child = new Child()
console.log(child.getAge()) // 88
console.log(child.like) // [ '🏀' ]
console.log(child.type) // child
以上方法就实现了原型链继承,这种继承方法存在一些问题:
- 引用类型的属性被
所有实例共享 - 创建
子类型的实例时,不能向父类型传参数
如下: 我们只改变了child1的like属性的值,child2的like属性对应的值也会改变了。因为两个实例使用的是同一个原型对象,它们的内存空间是共享的。
const child1 = new Child()
const child2 = new Child()
console.log(child1.like, child2.like) // [ '🏀' ] [ '🏀' ]
child1.like.push('🏓️')
console.log(child1.like, child2.like) // [ '🏀', '🏓️' ] [ '🏀', '🏓️' ]
构造函数继承
function Parent(fnName) {
this.tpye = 'parent'
this.age = 88
this.like = ['🏀']
this.fnName = fnName
}
Parent.prototype.getAge = function () {
return this.age
}
function Child(fnName) {
Parent.call(this, fnName)
this.type = 'child'
}
const child1 = new Child('child1')
const child2 = new Child('child2')
console.log(child1.like, child2.like) // [ '🏀' ] [ '🏀' ]
child1.like.push('🏓️')
console.log(child1.like, child2.like) // [ '🏀', '🏓️' ] [ '🏀']
console.log(child1.fnName, child2.fnName) // child1 child2
/* 没有继承父实例原型中的属性和方法 */
console.log(child1.getAge, child2.getAge) // undefined undefined
我们借用call函数来实现构造函数继承
优点
- 解决了原型链继承中
实例共享问题 - 创建
子类型的实例时,可以向父类型传参数
缺点
- 每次创建实例,都会调用一次父级构造函数
- 无法继承
父类型原型链中的属性和方法
组合继承
组合继承综合原型链继承和构造函数继承,将两者优点集中起来。通过原型链继承原型上的属性和方法,通过构造函数继承实例中的属性
function Parent(fnName) {
this.age = 88
this.like = ['🏀']
this.fnName = fnName
}
Parent.prototype.getAge = function () {
return this.age
}
function Child(fnName) {
Parent.call(this, fnName)
}
Child.prototype = new Parent()
Child.prototype.getFnName = function () {
return this.fnName
}
const child1 = new Child('child1')
const child2 = new Child('child2')
console.log(child1.like, child2.like) // [ '🏀' ] [ '🏀' ]
child1.like.push('🏓️')
console.log(child1.like, child2.like) // [ '🏀', '🏓️' ] [ '🏀' ]
console.log(child1.getAge(), child2.getAge()) // 88 88
console.log(child1.getFnName(), child2.getFnName()) // child1 child2
组合继承弥补了原型链继承和构造函数继承的缺点,是JavaScript中使用最多的继承模式。
原型式继承
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
类似于ES5的Object.create实现,创建一个构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个构造函数的实例。其实就是对传入对象进行浅拷贝,并且返回浅拷贝得到的对象。
缺点
- 和
原型链继承一样,存在引用类型的属性被所有实例共享的问题
const parent = {
like: ['🏀'],
age: 88,
getAge() {
return this.age
}
}
const child1 = create(parent)
child1.like.push('🏸️')
console.log(child1.like) // [ '🏀', '🏸️' ]
const child2 = create(parent)
child2.like.push('⚽️')
console.log(child2.like) // [ '🏀', '🏸️', '⚽️' ] [ '🏀', '🏸️', '⚽️' ]
寄生式继承
使用原型式继承可以获取一份目标对象的浅拷贝,得到一个新的对象,再对这个对象进行扩展,这样的方式叫做寄生式继承。它的缺点和原型式继承一样,但是可以对目标对象进行扩展,添加对应的属性和方法
function createObj(o) {
const newObj = Object.create(o)
newObj.getAge = function () {
return 88
}
}
const child1 = createObj(parent),
child2 = createObj(parent)
console.log(child1.getAge()) // 88
console.log(child1.like, child2.like) // [ '🏀' ] [ '🏀' ]
child2.like.push('🏓️')
console.log(child1.like, child2.like) [ '🏀', '🏓️' ] [ '🏀', '🏓️' ]
寄生组合继承
结合以上几种继承方法,得出最优解的寄生组合继承。
寄生组合继承继承父类型实例上的属性时,使用构造函数继承,继承父类原型中的属性和方法时候,使用原型式继承
function Parent() {
this.age = 88
this.like = ['🏀']
}
Parent.prototype.getAge = function () {
return this.age
}
function Child () {
Parent.call(this)
this.name = 'child'
}
function inheritPrototype(Child, Parent) {
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
}
inheritPrototype(Child, Parent)
const child1 = new Child()
console.log(child1.like, child1.getAge(), child1.name) // [ '🏀' ] 88 child
const child2 = new Child()
console.log(child2.like) // [ '🏀' ]
child2.like.push('🏓️')
console.log(child1.like, child2.like) // [ '🏀' ] [ '🏀', '🏓️' ]
寄生组合继承只调用一次Parent构造函数,避免了子类原型上不必要的属性,效率更高,而且,原型链仍然保持不变,因此instanceof操作符和isPrototypeOf()方法可以正常使用
ES6中的extends继承
Class使用extends继承,上面的寄生组合继承使用ES6来写就是
class Parent {
age = 88
like = ['🏀']
getAge() {
return this.age
}
}
class Child extends Parent {
constructor() {
// 这里super相当于Parent.call(this),调用了父类的构造函数
super()
this.name = 'child'
}
}
const child1 = new Child()
console.log(child1.like, child1.getAge(), child1.name) // [ '🏀' ] 88 child
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。在子类的构造函数中,必须先调用super函数,才能使用this关键字,否则报错。
在Class中,父类的静态方法也可以被子类继承
class Parent {
static age = 88
}
class Child extends Parent {}
console.log(Child.age)
可以看出,Class中的继承存在两条继承链
-
Child.__proto__指向Parent -
Child.prototype.__proto__指向Parent.prototype
class Parent {}
class Child extends Parent {}
console.log(Child.__proto__ === Parent) // true
console.log(Child.prototype.__proto__ === Parent.prototype) // true
最上面的继承使用babel编译后如下:
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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 _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 _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof 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;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Parent = /*#__PURE__*/ (function () {
function Parent() {
_classCallCheck(this, Parent);
_defineProperty(this, "age", 88);
_defineProperty(this, "like", ["🏀"]);
}
_createClass(Parent, [
{
key: "getAge",
value: function getAge() {
return this.age;
}
}
]);
return Parent;
})();
var Child = /*#__PURE__*/ (function (_Parent) {
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
var _this;
_classCallCheck(this, Child);
// 这里super相当于Parent.call(this),调用了父类的构造函数
_this = _super.call(this);
_this.name = "child";
return _this;
}
return Child;
})(Parent);
var child1 = new Child();
console.log(child1.like, child1.getAge(), child1.name); // [ '🏀' ] 88 child
主要看一下这一段,这就是寄生组合继承中继承原型的方法
function _inherits(subClass, superClass) {
// 判断extends后面跟着的父类,只能是一个函数或者null
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// 将Child.prototype.__proto__指向Parent.prototype
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});
//将Child.__proto__指向Parent
if (superClass) _setPrototypeOf(subClass, superClass);
}
Object.create方法还可以传入第二个参数,为新创建的对象添加指定的属性值和对应的属性描述符。具体看MDN中的描述
_possibleConstructorReturn
看一下这个函数在何处调用
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
/* 这里的Derived就是Child,获取Child.__proto__,那就是Parent,
下面这一堆代码就是调用父类构造函数,得到返回值,result = Parent.call(this) */
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
// 传入子类实例,和父级构造函数执行后的返回值
return _possibleConstructorReturn(this, result);
};
}
// 在Child中有这个调用,传入了Child本身
var _super = _createSuper(Child);
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
简化写就是var _this = _possibleConstructorReturn(this, Parent.call(this));,得到了_this,然后根据子类构造函数逻辑对_this执行对应操作并返回
function Child() {
var _this;
_classCallCheck(this, Child);
// 这里super相当于Parent.call(this),调用了父类的构造函数,拿到返回值
_this = _super.call(this);
// 根据子类构造函数逻辑对`_this`执行对应操作并返回
_this.name = "child";
return _this;
}
我们可能在父类构造函数中返回一个值,如果父类型构造函数返回一个object/function类型的值,就返回该值,如果返回null/undefined/基本类型的值就返回子类的this
class Parent {
constructor() {
return { age: 1 }
}
}
class Child extends Parent {}
const child = new Child()
console.log(child) // { age: 1 }
extends继承总结如下
-
建立Child和Parent之间的原型链关系
-
Child.__proto__指向Parent
-
Child.prototype.__proto__指向Parent.prototype
-
调用
Parent.call(this),根据Parent构造函数的返回值类型确定子类构造函数this的初始值_this -
根据
子类构造函数对_this进行扩展或者其他操作等,并且最终返回_this