关于继承那点事
实现方式1: 基于原型链继承
function Parent1() {
this.name = 'parent1';
}
function Child1() {
this.type = 'child1';
}
Child1.prototype = new Parent1();
console.log(new Child1())
对于该种方式的实现来说, 父类的方法和实例都可以访问到
但是会有一个问题: 原型的属性共享的问题
实现方式2: 借助于构造函数继承
function Parent2() {
this.name = 'parent2'
}
Parent2.prototype.getName = function() {
return this.name;
}
function Child2() {
Parent2.call(this)
this.type = 'child1';
}
let child2 = new Child2();
console.log(child2)
console.log(child2.getName()) // child2.getName is not a function
那么我们对于借助于构造函数的方式,父类的引用属性不会被共享
但是还是有缺点:只能继承父类的实例属性和方法,不能继承原型属性和方法
实现方式3: 会综合1,2方式 组合继承
function Parent3() {
this.name = 'parent3';
this.play = [1,2,3]
}
Parent3.prototype.getName = function() {
return this.name;
}
function Child3() {
// 第二次调用 parent3
Parent3.call(this)
this.type = 'child3';
}
// 第一次调用
Parent3Child3.prototype = new Parent3();
Child3.prototype.constructor = Child3;
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4);
console.log(s3.play, s4.play) // (4) [1, 2, 3, 4] (3) [1, 2, 3]
组合继承可以借助构造函数和原型去实现继承,不过还存在一个问题在上述代码中也提到了就是会调用两次 parent3,那么多构造一次就会多进行一次性能开销,这是我们不愿意看到的,那么还有没解决的方案的呢,后续揭晓。
上面都是基于构造函数去实现继承, 如果是针对于js 的普通对象呢,怎么实现继承呢?有没有思路呢?
接下来我们来探索一下
实现的方式4: 原型式继承
这里我们就会提到 es5 里面的 Object.create 方法:
这个方法会接受两个参数: 一是用作新对象原型的对象
二是为新对象定义额外属性的对象(可选参数)
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push('jiuwo');
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true
console.log(person5.name); // parent4
console.log(person4.friends); // ['p1', 'p2', 'p3', 'jiuwo', 'lucy']
console.log(person5.friends); // ['p1', 'p2', 'p3', 'jiuwo', 'lucy']
第一个结果 “tom”,比较容易理解, person4 继承了 person4的name 属性,但是在这个基础又进行了自定义
第二个是继承而来的 getName 方法检查自己的name 是否和属性里面的值一样 , true
第三个结果"parent4"也比较容易理解, person5继承了parent4的name 属性, 没有进行覆盖, 因此输出父对象的属性
最后的输出结果是一致的, 其实就是 Object.create() 方法是可以为一些对象实现浅拷贝
但是这种继承方式的缺点也很明显, 多个实例的引用类型属性指向相同的内存,存在篡改的可能
实现的方式5: 寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝, 然后利用这个浅拷贝的能力再进行增强,添加一些方法, 这样的继承方式叫做寄生式继承
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ['p1', 'p2', 'p3']
虽然优缺点和原型继承一样, 但是对于普通对象的继承方式而言, 寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法
实现的方式6: 寄生组合继承
function Parent() {
this.name = "parent";
this.play = ['1', '2', '3']
}
Parent.prototype.getName = function() {
return this.name;
}
function Child() {
Parent.call(this);
this.type = 'child';
}
function clone(child, parent) {
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
clone(Child, Parent);
Child.prototype.getFriends = function() {
return this.type;
}
let parent6 = new Child();
console.log(parent6) // name: "parent" play: (3) ['1', '2', '3'] type: "child"
console.log(parent6.getName()) // parent
console.log(parent6.getFriends()); // child
通过这段代码可以看出来 这种寄生组合式继承的方式, 基本上可以解决 前几种继承方式的缺点
较好的实现了继承, 同时减少了构造次数, 减少了性能的开销。
当然 es6 提供了 继承的关键字 extends 我们可以来看一下 它的底层实现的逻辑
es6 的 extends 关键字实现逻辑
先看一下 extends 如何实现实现继承
// es6 --- class extends superclass Parent {
constructor(name) {
this.name = name;
}
// 原型方法
// Parent.prototype.getName = function() {}
getName = function() {
console.log('parent:', this.name)
}}
class Child extends Parent {
constructor(name, age) {
super(name)
// 子类的 构造函数中 则需要 在使用 this 之前 首先调用 super();
this.age = age;
}}
const a = new Child('123', 15)
a.getName();
其实我们可以通过babel这个编译工具看看 具体做了什么事情
function _possibleConstructorReturn (self, call) {
// ...
return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
}
function _inherits (subClass, 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;
}
var Parent = function Parent () {
// 验证是否是 Parent 构造出来的 this
_classCallCheck(this, Parent);
};
var Child = (function (_Parent) {
_inherits(Child, _Parent);
function Child () {
_classCallCheck(this, Child);
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
return Child;
}(Parent));
然后 我们是不是 可以尝试类似的思路去做一些尝试呢?
// extends
class Super {};
Super.prototype.getName = function() {
console.log('123:');
}
function Sub() {};
// constructor: {
// value: Sub,
// enumerable: false,
// writeable: true,
// configurable: true
// }
Sub.prototype = Object.create(Super.prototype, {
constructor: {
value: Sub,
enumerable: false,
configurable: true,
writeable: true,
}})
let s = new Sub()s.getName()
总结: