# 实现继承
# 继承
- JS中的继承一般分为三部分:原型属性继承、静态属性继承、实例属性继承。
- 继承可以让原型链丰富,根据需求定制不同的原型链,不会存在内存浪费的情况,原型只会保留一份,用到的时候调用就行,还能节省空间。
# 原型继承
可以看出原型一般是一些共有的特性,实例是特有的特性,继承的越多越具体。
原型链的最顶端是最抽象的,越底端越具体,这样我们就可以根据需求在恰当位置继承来实现个性化的定制属性,统一而又有多样化。
# 原型之间的继承
function Parent() {} // 定义父类构造器
function Children() {} // 定义子类构造器
let ChildPrototype = Children.prototype; // 构造器原型
let ChildPrototypeProto = Children.prototype.__proto__; // 构造器原型的对象原型
// 方法一
ChildPrototypeProto = Parent.prototype; // 父类构造器原型作为子类构造器原型(ChildPrototype)的对象原型(ChildPrototypeProto)
// 方法二
ChildPrototype = Object.create(Parent.prototype); // Object.create返回一个对象,其__proto__指向传入的参数,也就实现返回的对象继承参数对象
// 方法三
Object.setPrototypeOf(ChildPrototype, Parent.prototype); // 直接设置参数1的原型(__proto__)为参数2
以上仅实现了原型之间的继承。
# 静态属性继承
- 静态属性的继承,意味着父构造器中定义的静态属性,在子构造器中可以直接调用。不仅实例可以通过对象原型实现继承,构造器也可以通过对象原型继承。之前提到过函数有
prototype
与__proto__
,其中prototype
是给实例用的,而__proto__
是给自己用的。 - 默认的构造函数的对象原型都指向原型函数构造器原型(即Function.prototype),可以理解所有函数都是由原始函数构造器生成
- 通过构造函数自身的对象原型(
__proto__
),来实现静态属性继承
function Parent() {} // 定义父类构造函数
function Children() {} // 定义子构造函数
// 定义父构造函数的静态方法
Parent.foo = function () {
console.log(this.name);
}
// 方法一
Children.__proto__ = Parent; // 子构造函数的对象原型指向父构造函数,也就实现继承
// 方法二
Object.setPrototypeOf(Children, Parent); // 同原型继承
console.log(Children.foo); // function() { console.log(this.name) } 实现继承
以上即为构造函数之间通过对象原型继承静态属性,注:函数也是对象
# 实例属性继承
- 实例自带的属性是由构造函数实例化时默认生成的,那么要实现实例属性的继承,势必要实现子构造函数中调用父构造函数,这样才能实现子构造函数实例化出来的对象也具备父构造函数给予的默认属性
- 在class语法糖的constructor中的super()函数就是实现这个继承
// 定义父构造函数
function Parent(name) {
this.name = name;
}
// 定义子构造函数
function Children(name, age) {
Parent.call(this, name);
this.age = age;
}
const obj = new Children('tom', 5);
console.log(obj); // {name: 'tom', age: 5} 实现实例属性继承
通过实例属性继承,可以把父构造器中默认生成的实例属性追加到子构造器实例化出来的对象上。
综合以上继承,现在实现真正的继承。
# 继承的实现
- 通过ES6的extends关键字来继承原型
- 手动实现原型继承
// 定义父构造函数,功能:初始化实例name属性
function Parent(name) {
this.name = name;
}
// 定义父构造函数的静态方法,功能:设置调用对象的name属性
Parent.setName = function setName(obj, name) {
obj.name = name;
}
// 定义父构造器原型(prototype)的方法,功能:获取调用对象的name属性
Parent.prototype.getName = function getName() {
return this.name;
}
/*--------以上已定义父类的原型方法(获取name),父类静态方法(设置name),以及构造器默认初始化的属性name-----*/
// 定义子构造函数,功能:初始化实例age属性,以及通过父构造器初始化实例name属性
function Children(name, age) {
Parent.call(this, name); // 调用父构造器,初始化name属性
this.age = age;
}
// 定义子构造函数的静态方法,功能:设置调用对象的age属性
Children.setAge = function setAge(obj, age) {
obj.age = age;
}
// 原型继承
// 设置Children.prototype['[[Prototype]]'] = Parent.prototype
// 此处的'[[Prototype]]'与设置__proto__相同
Children.prototype = Object.create(Parent.prototype);
// 注意此处原型继承之后,不带有constructor属性,应该手动指明为Children
Object.defineProperty(Children.prototype, 'constructor', {
value: Children,
writable: true, // 可写
enumerable: false, // 不可枚举
configurable: true // 可配置
});
// 上面两句的效果等于 Children.prototype.__proto__ = Parent.prototype;
// 由于子构造器原型方法必须在继承之后再定义,否则会被继承覆盖
// 定义子构造器原型(prototype)的方法,功能:获取调用对象的age属性
Children.prototype.getAge = function getAge() {
return this.age;
}
// 构造函数(继承静态属性)继承
// 设置Children.__proto__ = Parent
// 注意此处不能使用Children = Object.crete(Parent),因为Object.create返回的是一个对象,不能替换构造函数。
Object.setPrototypeOf(Children, Parent);
// 测试父级
const obj = new Parent('tom'); // 实例化父级实例
console.log(obj.getName()); // tom
Parent.setName(obj, 'jerry'); // 通过父级静态方法设置name
console.log(obj.getName()); // jerry
console.log(obj instanceof Parent); // true
// 测试子级
const obj1 = new Children(null, 5); // 实例化子级实例
console.log(obj1.getAge()); // 5
Children.setAge(obj1, 8); // 通过子级静态方法设置age
console.log(obj1.getAge()); // 8
console.log(obj1 instanceof Parent); // true
console.log(obj1 instanceof Children); // true
// 完整测试继承
const test = new Children('tom', 5); // 实例化子级实例,name='tom',age=5
console.log(test.getName()); // tom
Parent.setName(test, 'jerry'); // 通过父级静态方法设置name=jerry
console.log(test.getName()); // jerry
console.log(test.getAge()); // 5
Children.setAge(test, 8); // 通过子级静态方法设置age=8
console.log(test.getAge()); // 8
通过class方式继承
class Parent {
constructor(name) {
this.name = name;
}
static setName(obj, name) {
obj.name = name;
}
getName() {
return this.name;
}
}
class Children extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
static setAge(obj, age) {
obj.age = age;
}
getAge() {
return this.age;
}
}
所以说Class就是语法糖。实现继承,需要对原型、构造器、实例属性都加以实现继承。
← 实现class与extends this →