1.原型链继承
将父类的实例作为子类的原型
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent(); // 不同的 Child 实例的 proto 会引用同一 Parent 的实例
var child1 = new Child();
child1.names.push('yayu');
var child2 = new Child();
console.log(child1.names); // ["kevin", "daisy", "yayu"]
console.log(child2.names); // ["kevin", "daisy", "yayu"]
// 两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,
// 另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。
- 实例之间共享引用类型的值
- 创建子类实例时,没法像向父类的构造函数中传递参数
2.构造函数继承
function Parent () {
this.names = ['kevin', 'daisy'];
}
Parent.prototype.getName = function () {
return this.names;
}
function Child () {
Parent.call(this); // here
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
console.log(child1.getName()); // 会报错
- 它使父类的引用属性不会被共享,优化了第一种继承方式的弊端;
- 但是随之而来的缺点也比较明显——只能继承父类的实例属性和方法,不能继承原型属性或者方法。
可以在 Child 中向 Parent 传参
function Parent (name) {
this.name = name;
}
function Child (name) {
Parent.call(this, name);
}
var child1 = new Child('kevin');
console.log(child1.name); // kevin
var child2 = new Child('daisy');
console.log(child2.name); // daisy
3.组合继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name); // 第二次调用 Parent()
this.age = age;
}
Child.prototype = new Parent(); // // 第一次调用 Parent()
Child.prototype.constructor = Child; //// 手动挂上构造器,指向自己的构造函数
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.getName()); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.getName()); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
有个问题:构造函数多调用了一次
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("jerry");
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,jerry,lucy
console.log(person5.friends); //p1,p2,p3,jerry,lucy
-
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());
console.log(person5.getFriends());
6.寄生组合式继承
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:Child.prototype = new Parent();
一次在创建子类型实例的时候:
var child1 = new Child('kevin', '18');
// 回想下 new 的模拟实现,其实在这句中,我们会执行:
// Parent.call(this, name);
// 在这里,我们又会调用了一次 Parent 构造函数。
如果我们不使用 Child.prototype = new Parent()
,而是间接让Child.prototype
访问到Parent.prototype
呢?
o = new Constructor(); 相当于 o = Object.create(Constructor.prototype);
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent() {
this.name = 'parent';
this.play = [1, 2, 3];
}
Parent.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent.call(this);
this.friends = 'child';
}
clone(Parent6, Child6);
Child.prototype.getFriends = function () {
return this.friends;
}
let person = new Child();
console.log(person);
console.log(person.getName());
console.log(person.getFriends());
ES6的extend
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
利用 babel 这个编译工具,将 ES6 的代码编译成 ES5
https://github.com/logan70/Blog/issues/22
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));
JavaScript 核心原理精讲