用 prototype 实现继承的一点笔记。首先要明白,继承之后会发生什么事情。例如类 B 继承了类 A 之后,
instanceof
判断来自类 A有了方向之后,我们就能写出下面的代码
function inherit(Parent, childConstructor) {
function Child(...args) {
// 实例方法继承
Parent.apply(this, args);
childConstructor.apply(this, args);
}
// 创建一个 Temp 的中间变量,是为了让子类不会影响到父类的原型
function Temp() {}
Temp.prototype = Parent.prototype;
// 原型链继承
Child.prototype = new Temp();
Child.prototype.constructor = childConstructor;
// 类方法(静态方法)继承
Object.keys(Parent).forEach(k => {
Child[k] = Parent[k];
});
return Child;
}
写个例子测试一下上面的代码
function Animal(name) {
const name = name;
this.species = "animal"
this.getName = function() {
return name;
}
}
Animal.prototype.say = function() {
return "";
}
function Cat() {
this.species = "cat"
}
Cat = inherit(Animal, Cat);
Cat.prototype.say = function() {
return "Miao~";
}
const c = new Cat("LALA");
console.log(c.species) // cat
console.log(c.getName()) // LALA
console.log(c.say()) // Miao~
console.log(c instanceof Animal) // true
上面的方法不太好的一点是必须在执行了 inherit
之后,才能开始写子类的原型方法和静态方法。在 ES6 中,引入了类的概念,继承也可以通过 extend
去实现。但是一般我们都会用 Babel 或者 TypeScript 去编译成 ES5。所以我们来看下这两个工具是怎样实现继承的。以下面这段代码为例
class Animal {}
class Cat extends Animal {}
先看看 Babel 是怎么做的
"use strict";
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return call && (typeof call === "object" || typeof call === "function")
? call
: self;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof superClass
);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Animal = function Animal() {
_classCallCheck(this, Animal);
};
var Cat = (function(_Animal) {
_inherits(Cat, _Animal);
function Cat() {
_classCallCheck(this, Cat);
return _possibleConstructorReturn(
this,
(Cat.__proto__ || Object.getPrototypeOf(Cat)).apply(this, arguments)
);
}
return Cat;
})(Animal);
这里的原型继承,Object.create
去实现,根据 MDN 文档的 polyfill,其实就是类似我们自己实现的中间变量 Temp
。通过 setPrototypeOf 这段去掉,结果也是正常的。
看下 TypeScript 的实现
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Animal = /** @class */ (function () {
function Animal() {
}
return Animal;
}());
var Cat = /** @class */ (function (_super) {
__extends(Cat, _super);
function Cat() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Cat;
}(Animal));
这里一个比较有趣的是,通过 ({ __proto__: [] } instanceof Array
去判断浏览器是不是支持 __proto__
。