这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
一、JS中的继承
在面向对象中我们主要研究以下几个问题:(类和实例的关系)
- 继承 子类继承父类
- 封装 为实例添加方法、为原型增加方法
- 多态(重写、重载)
继承:两个类A B ,想让B类的实例使用A类上的属性和方法,就需要让B类继承A类,我们称B类为子类,A类为父类(超类)
function Super() {
}
function A() {}
A.prototype.say = function () {
console.log('hello World');
};
let b = new B();
b是B类的实例 想使用A类的属性和方法
console.log(b.say); undefined
为啥是undefined?对象.属性名 先看自己是否有着私有属性,如果私有属性没有,就去实例所属类的原型查找公有的属性和方法,如果原型上也没有,就根据原型对象的__proto__ 一直找到Object基类的原型上为止,如果还没有就返回undefined;
现在需要b可以使用A类的say方法,现在就需要继承;
二、原型链继承
JavaScript 是通过原型对象实现的面向对象,所以继承的第一种方式就是原型链继承。其核心就是通过扩展子类的原型链,通过这一方式可以使得子类实例在原型链查找的过程中可以访问到父类的属性和方法
- 原型继承: 将子 类B的原型对象 重写成父类A的一个实例。
B.prototype = new A(); 把父类共有属性和私有属性变成子类的公有属性
function A() {
}
A.prototype.age = 19;
A.prototype.say = function () {
console.log('来自A类原型的say方法')
};
function B() {}
B.prototype = new A(); 把B类的原型改写成一个A类的实例;
let b = new B();
console.log(b);
console.log(b.__proto__); {text: 'xxx', __proto__....}
console.log(b.age);
console.log(b.say);
把B的原型改写成A类的一个实例,此时通过 b.age 访问b的age属性,首先在私有属性中查找,私有属性中没有age属性,接着去b所属类的原型(B.prototype)上查找,此时原型对象是A的实例对象,在原型对象也没有age属性,然后通过原型对象的__proto__就找到了A.prototype上,A的prototype上有age属性
- 原型继承是把子类公有的属性和私有的属性都变成了子类私有的属性;
- 缺点:改写子类的原型对象,会导致子类原型对象上的constructor属性被改写,需要重新指定继承后的constructor;
三、借用构造函数继承
所谓继承就是通过某种方式让子类的实例可以访问到父类的属性,所以直接把父类的属性添加到子类实例上自然而然的就成为了一种可行的继承方式。
借用构造函数:把父类当做普通函数,在子类的函数体中call执行父类的函数;
function A() {
this.a = 'aa';
this.say = function () {
console.log('A say');
}
}
A.prototype.public = 'public';
function B() {
A.call(this); this是B类的一个实例,A.call(this) 的意思是把A中的this就该成B的实例(而在B的构造函数中this就是B的实例)这样在A中通过this.xxx = xxx 的方式添加的属性都会添加到B的实例身上。
}
let b = new B();
console.log(b); {a: 'aa', say: fun.....}
let b2 = new B();
console.log(b2.say === b.say); false;
借用构造函数继承:
- 把父类当做普通函数,在子类的函数体里面,通过call方法执行 A.call(this)
- call方法是用来修改this指向的,这样一来就把A中的this修改成了b的实例;在函数A中通过this.xxx = xxx 添加的属性都添加到了B的实例身上;
- 特点:只能把父类的私有属性和方法继承为子类的私有属性和方法;
四、组合继承
经历了前两种继承方式,我们发现他们各有优缺点,如果能够恰到好处的利用两者的优点就可以避免掉他们各自的缺点,这就诞生了组合继承的方式;
组合继承:原型链继承 + 借用构造函数继承
- 原型链继承:把父类私有的和共有的继承为子类公有的;
- 借用构造函数继承:把父类私有的继承为子类私有的
function A() {
this.a = '私有的'
}
A.prototype.text = '公有';
A.prototype.say = function () {
console.log('A公有的say方法')
};
function B() {
A.call(this); 借用构造函数继承,继承父类私有的
}
B.prototype = new A(); 原型链继承,继承父类私有和公有的属性;
B.prototype.constructor = B;
let b = new B();
console.log(b.text); 公有的
console.log(b.a); 私有的
- 组合继承也并非没有缺点,组合继承会父类的私有继承两次,一份在借用构造函数继承时成为私有的,而另一份是在原型继承时成为公有的;
五、原型式继承
原型式继承可以认为是一种“桥接”,这种方式可以在原型链查找的过程中插入一个环节。
原型式继承:把父类的公有属性继承为子类的公有属性;
创建一个新的对象,并且新对象的__proto__指向A.prototype,最后把这个新对象作为B类的原型;
- 创建一个对象,并且对象的__proto__ 指向obj
Object.create(obj)
- 原型式继承示例
function A() {
this.private = 'private私有';
}
A.prototype.public = 'public公有';
function B() {}
B.prototype = Object.create(A.prototype); 创建一个指定原型的对象 创建一个对象,并且这个对象的__proto__ 指向A.prototype
B.prototype.constructor = B;
let b = new B();
console.log(b.public);
值得注意的是,原型式继承同样是修改B类原型的指向,所以需要重新指定构造函数!
六、寄生组合式继承
寄生组合式继承:原型式继承 + 借用构造函数继承
- 原型式继承:把父类公有的 继承为子类实例公有的
- 借用构造函数继承:继承父类私有的属性
function A() {
this.private = '私有属性';
}
A.prototype.public = '公有属性';
function B() {
A.call(this); 借用构造函数继承
this.name = 'b私有的';
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
let b = new B(); {name: '私有的', private: '私有属性'}
console.log(b.private); 继承过来的私有属性
console.log(b.public); 继承过来到的公有属性
七、冒充对象继承
冒充对象继承:在子类的构造函数中生成一个父类的实例,把父类的这个实例进行遍历,把属性都添加子类的实例上;
function A() {
this.private = '私有属性';
}
A.prototype.public = '公有属性';
function B() {
this.name = 'B私有的属性';
let tmp = new A();
for (let key in tmp) {
this[key] = tmp[key];
}
}
let b = new B();
console.log(b);
八、 ES6 中的类及继承
8.1 ES6 和 ES5 中的类
-
ES5 一个函数就是一个类
-
ES6 借鉴后端语言,增加了class关键字,创建一个类
-
ES5
function Teacher(name, age, subject) {
this.name = name;
this.age = age;
this.subject = subject;
}
Teacher.motor = '传道 授业 解惑';
Teacher.prototype.teach = function () {
console.log(this.name + this.age);
};
let t = new Teacher('qq', 19, 'js');
- ES6
class Teacher {
constructor (name, age, subject) {
这里面通过this.xxx = xxx 是给实例添加私有属性
this.name = name;
this.age = age;
this.subject = subject;
}
添加公有属性
teach () {
console.log(this.name + this.age + this.subject);
}
添加静态方法
static motor = '传道授业解惑';
static getMotor () {
console.log('We are U');
}
}
let t = new Teacher('qq', 18, 'JS');
t.teach();
console.log(Teacher.motor);
Teacher.getMotor();
8.2 ES6继承
ES6继承:extends关键字
class A {
constructor (name, age) {
this.name = name;
this.age = age;
}
公有方法(添加到原型上)
say () {
console.log(`${this.name} say`);
}
}
ES6继承时使用 extends 关键字实现继承
class B extends A { B继承A类
constructor (x, y, forName, forAge) {
注意:在使用ES6的extends关键字之前,必须使用super(); super表示父类的构造函数
super(forName, forAge); 注意:
this.x = x;
this.y = y;
}
}
let b = new B('x', 'y', 'qq', 18);
console.log(b);
b.say();
- ES6的继承原理是:寄生组合式继承