分享一下我一年前端自学路上的学习笔记。若有侵权,请联系我,我立即删除或标明出处。
//通过构造函数实现继承
function Parent() {
this.name = "Parent1";
}
Parent1.prototype.sex = "男";
function Child() {
Parent1.call(this); //apply也可以
this.type = "Child";
}
console.log(new Child().name); //Parent1
console.log(new Child().sex); //undefined
小结:上述的通过构造函数实现继承,只能实现部分继承,无法继承父类原型对象,并没有真正的实现继承。
//通过原型链实现继承
function Parent() {
this.name = "Parent";
this.play = [1, 2, 3];
}
Parent.prototype.sex = "男";
function Child() {
this.type = "Child";
}
Child.prototype = new Parent;
var s1 = new Child();
var s2 = new Child();
console.log(s1.sex, s2.sex); //男,男
console.log(s1.play, s2.play); //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play) //[1, 2, 3, 4],[1, 2, 3, 4]
小结:上述通过原型链来实现继承,解决了通过构造函数实现继承的缺点,使之能继承父类的原型对象,但是实例s1和s2共用原型链上的原型对象,两个并不隔离,修改会相互干预。
//通过构造函数和原型链组合实现继承
function Parent() {
this.name = "Parent";
this.play = [1, 2, 3];
}
function Child() {
Parent.call(this);
this.type = "Child";
}
Child.prototype = new Parent();
var s1 = new Child();
var s2 = new Child();
console.log(s1.play, s2.play); //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play); //[1, 2, 3, 4],[1, 2, 3]
小结:上述的通过组合继承方式,解决了上面两种方式继承的缺点,既实现对父类原型对象的继承,而且实例相互隔离,修改互不干预。但是父类的构造被执行了两次,第一次是Child.prototype = new Parent()
,第二次是实例化时Parent.call(this)
,这是没有必要的。我们下面进行优化。
//组合第一次优化
function Parent() {
this.name = "Parent";
this.play = [1, 2, 3];
}
function Child() {
Parent.call(this);
this.type = "Child";
}
Child.prototype = Parent.prototype; //这是没优化之前的代码Child.prototype = new Parent();
var s1 = new Child();
var s2 = new Child();
console.log(s1.play, s2.play); //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play); //[1, 2, 3, 4],[1, 2, 3]
小结:上述是组合继承的第一次优化,我们将父类的原型对象直接赋给子类的原型对象。这样就减少了一次父类构造函数运行次数。但是!为什么叫第一次优化,因为这次优化并不是完美的。其实有个问题我没有提出,接着第一次优化的代码,请看下面代码的执行结果。
//组合继承的隐藏问题,接组合继承方式第一次优化代码继续看
console.log(s1 instanceof Child); //true
console.log(s2 instanceof Parent) //true
console.log(s1.constructor.name); //Parent
小结:上述代码可以看出我们无法判断实例s1是由Child
直接实例化还有由Parent
间接实例化,所以我们使用s1.constructor.name
来确认。但结果表明s1是由父类构造实例化出来,而并非子类。所以我们来进行最后一次优化。
//通过构造函数和原型链组合实现继承的最后一次优化
function Parent() {
this.name = "Parent";
this.play = [1, 2, 3];
}
function Child() {
Parent.call(this);
this.type = "Child";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var s1 = new Child();
var s2 = new Child();
console.log(s1.constructor.name); //Child
小结:上述代码为最后一次组合优化。放弃了Child.prototype = Parent.prototype
而使用一个中间对象将父类和子类相关联Child.prototype = Object.create(Parent.prototype)
,之后再修改Child.prototype.constructor = Child
,从而解决实例是由父类构造函数实例化出来的问题。或许你会问为什么不在第一次优化上直接修改Child.prototype.constructor
?那你就仔细思考下我们为什么要使用中间对象Object.create()
。好了,除了上面这么多实现继承方式外,还有吗?看就继续看喽。
//ES6实现继承
class Parent {
constructor(name, id) {
this.name = name;
this.id = id;
}
say() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(name, id, age) {
super(name, id);
this.age = age;
}
}
let s1 = new Child('hi', 18, 18)
console.log(s1, s1.say());
小结:上面是ES6的继承。ES6中Class
通过extends
关键字,使用super()
方法调用父类的constrctor()
,从而实现继承,是不是比ES5方便很多。
上面就是实现继承的N种方式,希望对大家有所帮助。特别注意一点,ES5
继承和ES6
继承是不同的。这里不细说了,网上资料很多。
简单的说,ES5
实质是先创造子类的实例对象this
,然后再将父类的的方法添加到上面。ES6
则是先创造父类的实例对象this
,然后再用子类的构造函数修改this
.