突然看到继承,感觉对这个概念有点模糊,掌握的知识点不太全面牢固,所以才有了这篇博客。
在我的印象里,ES5的继承我只知道三种:通过构造函数继承、通过原型链继承。通过构造函数和原型链组合继承
对ES6的继承,暂时就知道一个class类继承,而且用的很少。下面就来详细的记录一下,ES5和ES6的继承方式,和其各有的优劣。
ES5继承
1、通过构造函数继承。
中心思想就是 在子类型的构造函数中调用超类型构造函数。
在这里解释一下子类型和超类型:子类型就是继承了其他对象的属性或方法的构造函数,超类型就是等待被继承的构造函数(个人见解,如果有更好的解释,欢迎交流)
// 超类
function SuperType(name) {
this.name = name;
this.colors = ['pink', 'blue', 'green'];
}
// 超类原型上存在six属性
SuperType.prototype.six = "man";
// 子类
function SubType(name) {
// 在子类中调用 超类构造函数 同时调用 call 使得 超类的 this指向该子类构造函数内部的this
SuperType.call(this, name);
}
// 子类实例 创建实例的同时,传递参数
let instance1 = new SubType('Yvette');
// 在实例本身内部找color属性,结果没有找到,然后在内部调用了 超类构造函数,同时还能传递参数
instance1.colors.push('yellow');
// 自己本身内部找 color 属性,没有找到,但是在超类中存在
console.log(instance1.colors);//['pink', 'blue', 'green', yellow]
// 重新实例化一个子类,传递一个不同的参数
let instance2 = new SubType('Jack');
// 可以看到,重新实例化的对象,没有继承上一次实例化操作中添加的 yellow
console.log(instance2.colors); //['pink', 'blue', 'green']
// 超类原型上的属性无法被继承
console.log(instance2.six); // undefined
优点:
- 可以向超类传递参数
- 解决了原型中包含引用类型值被所有实例共享的问题,也就是说不同的实例操作不会互相影响(也就是第一次实例化之后添加的 yellow 属性,没有被第二次实例化的时候继承过来)
缺点:
- 方法都在构造函数中定义,函数无法复用
- 超类型原型中定义的方法对于子类型而言都是不可见的(也就是超类中原型上定义的 six 属性,无法在子类实例对象上找到)
2、通过原型链继承。
原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
// 超类
function SuperType() {
this.name = 'Yvette';
this.colors = ['pink', 'blue', 'green'];
}
// 超类原型属性
SuperType.prototype.getName = function () {
return this.name;
}
// 子类
function SubType() {
this.age = 22;
}
// 子类原型 将子类原型设置为 父类实例
SubType.prototype = new SuperType();
// 同时在子类原型上增加新的属性
SubType.prototype.getAge = function() {
return this.age;
}
// 还原 constructor 指针
SubType.prototype.constructor = SubType;
// 实例子类对象1
let instance1 = new SubType();
// 在实例对象上找 colors 属性,没有,就去实例对象的原型上找,该实例对象的原型是超类的实例对象,也就是能拿到超类构造函数,以及超类构造函数的原型上的所有属性
instance1.colors.push('yellow');
// 原型上找方法,找到之后发现原型上没有 name 属性,所以取构造函数内部name属性
console.log(instance1.getName()); //'Yvette'
// 因为之前向超类构造函数内部的 colors 属性内部添加过 yellow 值
console.log(instance1.colors);//[ 'pink', 'blue', 'green', 'yellow' ]
// 新的子类实例
let instance2 = new SubType();
// 新的子类实例中 找 colors 属性,找到原型链上之后,发现存在该属性 但是,该超类的构造函数内部的colors 属性值已经在之前被改变了,且是永久改变
console.log(instance2.colors);//[ 'pink', 'blue', 'green', 'yellow' ]
优点:
- 超类型原型中定义的方法对于子类型而言都是可继承的(也就是超类中原型上定义的 getName 属性,可以在子类原型链上找到)
- 除了某些特定的方法和属性,其他的公用的方法和属性都能够写在原型链上,都可以通过复用继承到这些方法和属性
缺点:
- 通过原型来实现继承时,原型会变成另一个类型的实例(SubType.prototype = new SuperType()),原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。
- 在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数(第一次创建子类实例,给超类构造函数中 colors 属性 添加了 yellow 值,在后续创建的子类实例中,colors 的值一直都会是改变之后的)。
3、组合继承
通过 原型链和构造函数 实现组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性。
// 超类
function SuperType(name) {
this.name = name;
this.colors = ['pink', 'blue', 'green'];
}
// 超类原型
SuperType.prototype.sayName = function () {
console.log(this.name);
}
// 子类
function SuberType(name, age) {
调用超类,且传值
SuperType.call(this, name);
this.age = age;
}
// 子类原型 赋值为 超类实例
SuberType.prototype = new SuperType();
// 还原子类原型 counstructor 指针
SuberType.prototype.constructor = SuberType;
// 子类原型 增加属性
SuberType.prototype.sayAge = function () {
console.log(this.age);
}
// 子类实例化,且穿入参数
let instance1 = new SuberType('Yvette', 20);
// 子类实例 找 colors 属性 ,添加 yellow值
instance1.colors.push('yellow');
console.log(instance1.colors); //[ 'pink', 'blue', 'green', 'yellow' ]
// 子类实例 能继承超类 原型上的方法,原型上找不到name属性,去构造函数内部找
instance1.sayName(); //Yvette
// 新建子类实例,且传值
let instance2 = new SuberType('Jack', 22);
// 发现上一次改变 的 colors 属性在新的 子类实例中并没有被继承,colors 还是原始的值
console.log(instance2.colors); //[ 'pink', 'blue', 'green' ]
instance2.sayName();//Jack
优点:
- 可以向超类传递参数
- 每个实例都有自己的属性
- 实现了函数复用
缺点:
- 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候 SuberType.prototype = new SuperType(),另一次是在子类型构造函数内部 SuperType.call(this, name)。
ES6继承
首先ES6 在继承上给了一个关键字 extend ,同时还给出了 另外一个关键字 class
通过class 构造出一个类,通过 extend 可以使得 一个类 继承另外一个类的属性和方法。
使用ES6 继承的最大好处就是方便,不用像ES5 写上一大串,浏览器会自动
es5 的原型 继承
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
es6 的class extend 继承
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
比较一下就可以发现,class
的定义包含了构造函数constructor
和定义在原型对象上的函数hello()
(注意没有function
关键字),这样就避免了Student.prototype.hello = function () {...}
这样分散的代码。
最后,创建一个Student
对象代码和前面章节完全一样:
var xiaoming = new Student('小明');
xiaoming.hello();
class xxx extend yyy 继承方式 存在以下几个关键点
1、extends关键字:下面代码定义了一个B类(class),该类通过extends关键字,继承了A类的所有属性和方法。A类中的所有方法默认是添加到B的原型上,所以extends继承的实质仍然是原型链。
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
}
class B extends A {
constructor(name, age) {
super(name, age);
this.job = "IT";
}
getJob() {
return this.job;
}
getNameAndJob() {
return super.getName() + this.job;
}
}
var b = new B("Tom", 20);
console.log(b.name);
console.log(b.age);
console.log(b.getName());
console.log(b.getJob());
console.log(b.getNameAndJob());
//输出:Tom,20,Tom,IT,TomIT
2、super关键字:super这个关键字,既可以当作函数使用,也可以当作对象使用。当作函数使用时,super代表父类的构造函数,并在子类中隐式执行Parent.apply(this),从而将父类实例对象的属性和方法,添加到子类的this上面
子类必须在constructor方法中调用super方法,如果子类没有定义constructor方法,constructor方法以及其内部的super方法会被默认添加
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
}
class B extends A {}
var b = new B("Tom", 20);
console.log(b.name);
console.log(b.age);
console.log(b.getName());
console.log(b.hasOwnProperty("name"));
//输出:Tom,20,Tom,true
在子类的constructor方法中,只有调用super之后,才可以使用this关键字,否则会报错。
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class B extends A {
constructor(name, age) {
this.job = "IT";
super(name, age);
}
}
var b = new B("Tom", 20)
//输出:报错
super()只能用在子类的constructor方法之中,用在其他地方就会报错。
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class B extends A {
toStr(name, age) {
super(name, age)
}
}
var b = new B("Tom", 20)
//输出:报错
super作为对象时,在子类中指向父类的原型对象。
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
console.log(this.name);
}
}
A.prototype.n = 2;
class B extends A {
constructor(name, age) {
super(name, age);
}
toStr() {
return super.n;
}
activeGetName() {
super.getName(); // 此处作为一个对象,指向的是 父类的原型对象
}
}
var b = new B("Tom", 20);
console.log(b.toStr());
console.log(b.activeGetName());
//输出:2,Tom
静态方法的继承
在一个方法前加上关键字static,就表示该方法不会被实例继承。不会被实例继承的意思就是,当我的 super 作为对象的时候,指向的是父类的原型对象,加了 static 关键字之后,父类的原型对象上就默认不会将这个属性加入到原型链中
// 存在关键字 static
class A {
static say() {
console.log("hello");
}
}
class B extends A {
constructor() {
super();
}
getSay() {
super.say()
}
}
let bbb = new B()
console.log(bbb.getSay()); //输出:Uncaught TypeError: (intermediate value).say is not a function
// 不存在关键字 static
class A {
say() {
console.log("hello");
}
}
class C extends A {
constructor() {
super();
}
getSay() {
super.say()
}
};
let ccc = new C()
console.log(ccc.getSay()); //输出:hello
但是父类的静态方法,会被子类继承。 这种情况就是 super 不是作为对象而是作为函数使用,用来继承父类的属性和原型
class A {
static say() {
console.log("hello");
}
}
class C extends A {
constructor() {
super();
}
};
console.log(C.say()); //输出:hello
但是,反转来了。我们也可以使用super在子类的静态方法中调用父类的静态方法。super在静态方法中指向父类本身,而不是父类的原型对象。
class A {
static say() {
console.log("hello");
}
}
class B extends A {
// 子类的 static 内部调用 父类的 static
static toStr() {
super.say();
}
}
var b = new B();
console.log(B.toStr());//输出:hello
以上是本人觉得比较常用常见的几种继承方式,个人理解可能存在疏漏或错误,如果有大佬看出了问题,可以多多指教