实现继承前必须先储备原型/原型链/类和构造函数知识
- 原型和原型链:具体学习这篇文章:juejin.cn/post/739848…
- 构造函数和类:具体学习这篇文章:juejin.cn/post/739917…
引入继承
继承是面向对象当中的一个概念。如果一个类别B继承自另一个类别A,就把这个B称为A的子(派生)类,而把A称为B的父(超)类。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
在下面代码中可以看到Person和Student有很多的相似之处,Person有的在Student中都有,这时候我们就可以让Student继承自Person,减少Student中和Person相同的代码,让其通过Studen创建的实例对象也有通过Person创建的实例对象里的方法和属性
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log(this.name + "在running");
};
function Student(name, age, school, score) {
this.name = name;
this.age = age;
this.school = school;
this.score = score;
}
Student.prototype.running = function () {
console.log(this.name + "在running");
};
Student.prototype.jumping = function () {
console.log(this.name + "在jumping");
};
var s1 = new Student("bob", 18, "清华", 750);
s1.jumping()
继承应该达到的状态:
- 子类可以使用父类中的属性和方法
- 子类不同的实例之间不会互相影响
- 子类实例能够向父类传参
- 能实现多继承(一个子类可继承多个父类)
- 父类的方法能被复用(不会过多的消耗内存),而不是每创建一个子类实例都生成一份父类方法
原型链实现继承
-
方式一:
Student.prototype = Person.prototype,缺点如下:- 因为子类和父类是共享一个原型的,它们两个会互相影响,子类修改原型方法时,父类也会修改
-
方式二:
Student.prototype.__proto__ = Person.prototype,缺点如下:- __proto__不是标准,可能有兼容问题
- 直接修改
__proto__可能会导致性能问题,V8引擎团队曾经表示,频繁修改__proto__可能会导致引擎优化失效,从而影响性能
-
方式三:
var prototype = Object.getPrototypeOf(Student.prototype);Object.setPrototypeOf(prototype, Person.prototype),缺点如下:- 代码可读性差,间接修改原型链,可能引入意想不到的副作用,也影响 JavaScript 引擎的优化
-
方式四:
Student.prototype = new Person(),本质其实是方式二但不是直接修改,而是创建新的实例并赋值- 不能继承父类的属性,子类通过
new创建的实例s1对象里面没有name属性和age属性
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.running = function () { console.log(this.name + "在running"); }; function Student(school, score) { this.school = school; this.score = score; } // Student.prototype = Person.prototype; // 方式一 // Student.prototype.__proto__ = Person.prototype // 方式二 // 方式三 // var prototype = Object.getPrototypeOf(Student.prototype); // Object.setPrototypeOf(prototype, Person.prototype) Student.prototype = new Person(); // 方式四 Student.prototype.jumping = function () { console.log(this.name + "在jumping"); }; var s1 = new Student("清华", 750); s1.running(); console.log(s1) // {school: '清华', score: 750}方式1代码原型图示如下:
方式2、3和4代码原型图示如下:
- 不能继承父类的属性,子类通过
缺点:
- 不能继承父类的属性,子类通过
new创建的实例s1对象里面没有name属性和age属性
借用构造函数继承属性
也称经典继承,此方法只实现了属性的继承,在子类构造函数的内部通过apply()和call()方法调用父类构造函数
- 解决了
s1对象里面没有name属性和age属性的问题function Person(name, age) { this.name = name; this.age = age; } Person.prototype.running = function () { console.log(this.name + "在running"); }; function Student(school, score) { Person.call(this, name, age); // 借用构造函数 this.school = school; this.score = score; } Student.prototype.jumping = function () { console.log(this.name + "在jumping"); }; var s1 = new Student("bob", 18, "清华", 750); console.log(s1) // {name: 'bob', age: 18, school: '清华', score: 750} s1.running() // 报错:s1.running is not a function
缺点:
- 只能继承父类的实例属性和方法,不能继承原型链上的属性和方法
组合继承
组合继承结合了原型链继承和借用构造函数的优点,既能继承父类的实例属性,又能继承原型链上的方法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log(this.name + "在running");
};
function Student(school, score) {
Person.call(this, name, age); // 借用构造函数
this.school = school;
this.score = score;
}
Student.prototype = new Person(); // 原型链
Student.prototype.jumping = function () {
console.log(this.name + "在jumping");
};
var s1 = new Student("bob", 18, "清华", 750);
s1.running();
console.log(s1) // {name: 'bob', age: 18, school: '清华', score: 750}
缺点: 父类构造函数会被调用两次 ,所有的子类实例事实上会拥有两份父类的属性,Student.prototype 上会有 name 和 age 属性,这些属性属于 Person 的实例,不应该出现在 Student.prototype 上
原型式继承方法
这种模式要从道格拉斯·克罗克福德(著名的前端大师,JSON的创立者)在2006年写的一篇文章说起: 在JavaScript中使用原型式继承
- 之所以称为原型式继承,因为这种继承机制是通过对象的原型来实现的
- 直接创建新对象,而不是通过
Student.prototype = new Person() - 我们先要理解他要创建的对象需要具备什么条件:
-
必须创建出来一个对象(可以是new的实例可以是普通对象)
-
这个对象的
__proto__必须指向父类的prototype -
需要将这个对象赋值给子类的
prototype
-
- 这种方式解决了组合继承的缺点
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log(this.name + "在running");
};
function Student(name, age, school, score) {
Person.call(this, name, age); // 借用构造函数
this.school = school;
this.score = score;
}
/*
函数object做的操作:
创建一个新对象并返回
让这个对象的__proto__指向父类的prototype
*/
function object(superPrototype) {
// 方式一:道格拉斯·克罗克福德的方法,无任何兼容性问题
// function F() {}
// F.prototype = superPrototype;
// return new F();
// 方式二
// var obj = {};
// // obj.__proto__ = superPrototype; // 可能有兼容性问题
// Object.setPrototypeOf(obj, superPrototype);
// return obj;
// 方式三
return Object.create(superPrototype);
}
Student.prototype = object(Person.prototype);
Student.prototype.constructor = Student
Student.prototype.jumping = function () {
console.log(this.name + "在jumping");
};
var s1 = new Student("bob", 18, "清华", 750);
s1.running();
console.log(s1) // {name: 'bob', age: 18, school: '清华', score: 750}
代码原型图示如下:
缺点:
- 注意这种方法
constructor属性丢失,需要手动设置过来。 - 但当不止
Student继承自Person时,还有Teach、police等等也继承自Person,这时就需要重复写很多遍34和35行的代码,那和工厂模式结合就有了下面的继承方式
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思想,并且同样由道格拉斯·克罗克福德提出和推广
- 寄生式继承的思路是结合原型式继承和工厂模式的一种方式
- 创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回
// 子类和父类寄生于这个函数实现继承
function inheritPrototype(subType, superType) {
var obj = Object.create(superType.prototype);
obj.constructor = subType; // 增强对象的constructor属性
subType.prototype = obj; // 赋值对象
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log(this.name + "在running");
};
function Student(name, age, school, score) {
Person.call(this, name, age); // 借用构造函数
this.school = school;
this.score = score;
}
inheritPrototype(Student, Person);
Student.prototype.jumping = function () {
console.log(this.name + "在jumping");
};
var s1 = new Student("bob", 18, "清华", 750);
s1.running();
console.log(s1) // {name: 'bob', age: 18, school: '清华', score: 750}
- 这种方式已经很好地实现了继承,下面这种方式是结合原型式和寄生式进行的封装优化
寄生组合式继承(ES5最终方案)
这种方式是结合原型式和寄生式进行的封装优化,是ES5实现继承的最终方案
/*
函数object做的操作:
创建一个新对象并返回
让这个对象的__proto__指向父类的prototype
*/
function object(superPrototype) {
// 兼容性判断
if (typeof Object.create !== 'function') {
Object.create = function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
}
return Object.create(superPrototype)
}
// 子类和父类寄生于这个函数实现继承
function inheritPrototype(subType, superType) {
var obj = object(superType.prototype);
obj.constructor = subType; // 增强对象的constructor属性
subType.prototype = obj; // 赋值对象
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log(this.name + "在running");
};
function Student(name, age, school, score) {
Person.call(this, name, age); // 借用构造函数
this.school = school;
this.score = score;
}
inheritPrototype(Student, Person);
Student.prototype.jumping = function () {
console.log(this.name + "在jumping");
};
var s1 = new Student("bob", 18, "清华", 750);
s1.running();
console.log(s1) // {name: 'bob', age: 18, school: '清华', score: 750}
ES6类的继承
使用extends关键字,是ES5继承的语法糖
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + "在running");
}
static jumping() {
console.log(this.name + "在jumping");
}
}
class Student extends Person {
constructor(name, age, school, score) {
super(name, age);
this.school = school;
this.score = score;
}
study() {
console.log(`${this.name}study`);
}
static talk() {
console.log(`${this.name}talk`);
}
}
var s1 = new Student("bob", 20, "清华", 750);
s1.running();
s1.study();
转换后成ES5代码后:
function _callSuper(t, o, e) {
return (
(o = _getPrototypeOf(o)),
_possibleConstructorReturn(
t,
_isNativeReflectConstruct()
? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor)
: o.apply(t, e)
)
);
}
function _possibleConstructorReturn(t, e) {
if (e && ("object" == _typeof(e) || "function" == typeof e)) return e;
if (void 0 !== e)
throw new TypeError(
"Derived constructors may only return object or undefined"
);
return _assertThisInitialized(t);
}
function _assertThisInitialized(e) {
if (void 0 === e)
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
return e;
}
function _isNativeReflectConstruct() {
try {
var t = !Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
} catch (t) {}
return (_isNativeReflectConstruct = function _isNativeReflectConstruct() {
return !!t;
})();
}
function _getPrototypeOf(t) {
return (
(_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf.bind()
: function (t) {
return t.__proto__ || Object.getPrototypeOf(t);
}),
_getPrototypeOf(t)
);
}
function _inherits(t, e) {
if ("function" != typeof e && null !== e)
throw new TypeError("Super expression must either be null or a function");
(t.prototype = Object.create(e && e.prototype, {
constructor: { value: t, writable: !0, configurable: !0 }
})),
Object.defineProperty(t, "prototype", { writable: !1 }),
e && _setPrototypeOf(t, e);
}
function _setPrototypeOf(t, e) {
return (
(_setPrototypeOf = Object.setPrototypeOf
? Object.setPrototypeOf.bind()
: function (t, e) {
return (t.__proto__ = e), t;
}),
_setPrototypeOf(t, e)
);
}
function _typeof(o) {
"@babel/helpers - typeof";
return (
(_typeof =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (o) {
return typeof o;
}
: function (o) {
return o &&
"function" == typeof Symbol &&
o.constructor === Symbol &&
o !== Symbol.prototype
? "symbol"
: typeof o;
}),
_typeof(o)
);
}
function _classCallCheck(a, n) {
if (!(a instanceof n))
throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
(o.enumerable = o.enumerable || !1),
(o.configurable = !0),
"value" in o && (o.writable = !0),
Object.defineProperty(e, _toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return (
r && _defineProperties(e.prototype, r),
t && _defineProperties(e, t),
Object.defineProperty(e, "prototype", { writable: !1 }),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var Person = /*#__PURE__*/ (function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
return _createClass(
Person,
[
{
key: "running",
value: function running() {
console.log(this.name + "在running");
}
}
],
[
{
key: "jumping",
value: function jumping() {
console.log(this.name + "在jumping");
}
}
]
);
})();
var Student = /*#__PURE__*/ (function (_Person) {
function Student(name, age, school, score) {
var _this;
_classCallCheck(this, Student);
_this = _callSuper(this, Student, [name, age]);
_this.school = school;
_this.score = score;
return _this;
}
_inherits(Student, _Person);
return _createClass(
Student,
[
{
key: "study",
value: function study() {
console.log("".concat(this.name, "study"));
}
}
],
[
{
key: "talk",
value: function talk() {
console.log("".concat(this.name, "talk"));
}
}
]
);
})(Person);
var s1 = new Student("bob", 20, "清华", 750);
s1.running();
s1.study();