假如有一天,你的男朋友在学习JS的时候折磨你,如何以她所知道的JS基础知识,来讲解JS的继承?
JS的继承有什么用,为什么有继承这种概念?
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' eat')
}
function Cat(name) {
this.name = name;
this.subname = 'cat ' + name;
}
Cat.prototype.eat = function() {
console.log(this.name + ' eat')
}
假如需要定义一个Animal类,同时要定义一个和Animal类有相同的属性和方法的Cat类。
就可以让Cat类继承Animal类的属性和方法,如果更改了Animal类的方法,Cat也会继承更改后的方法和属性。
可以让你减少很多代码工作量。
new一个对象的时候做了什么?
先回顾一下,new一个对象时做了什么,以下是一个简单的例子
function Person (name,age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log('my name is ' + this.name);
}
let person = new Person("xiaohei",18);
console.log(person);
person.getName();//my name is xiaohei
使用关键字new创建新实例对象经过了以下几步:
- 创建一个新对象
- 将新对象的_proto_指向构造函数的prototype对象(让实例对象可以访问原型对象上的方法)
- 将构造函数的作用域赋值给新对象 (将this指向新对象)
- 执行构造函数中的代码(将属性添加到实例对象上面)
- 返回新的对象
function Person (name,age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log('my name is ' + this.name);
}
function createPerson(name,age){
let obj = {};// 创建一个新对象
obj.__proto__ = Person.prototype;//将新对象的_proto_指向构造函数的prototype对象
Person.call(obj,name,age,sex);//改变this指向,并执行函数内代码
return obj;//返回对象
}
let person = new Person("xiaohei", 18);
let person2 = createPerson("xiaobai", 19);
console.log(person,person2);
// person = {
// name: 'xiaohei',
// age: 18,
// __proto__: Person.prototype,
// }
// person2 = {
// name: 'xiaobai',
// age: 19,
// __proto__: Person.prototype,
// }
new一个对象,就等于调用了createPerson方法来创建对象
JS的几种继承
类式继承
function Animal(name) {
this.name = name;
this.age = 18;
}
Animal.prototype.eat = function(){
console.log(this.name +' eat')
}
function Cat(name) {
this.subname = 'cat '+ name;
}
Cat.prototype = new Animal();
let catA = new Cat('xiaohei');
console.log(catA.name); //undefined
console.log(catA.age); //18
console.log(catA.subname); //'cat xiaohei'
catA.eat(); //undefined eat
以上就是一个类式继承的例子,把Cat.prototype指向一个Animal的实例对象
Cat类继承了Animal类的eat方法,Cat类的实例对象catA有age属性,但是name属性为undfined
下面用伪代码来分析一下,以上代码做了什么
`Cat.prototype = new Animal();`
//模拟new Animal()的过程
let obj = {};
obj.__proto__ = Animal.prototype;
Animal.call(obj); // 由于没有传入参数,等于执行了obj.name = undefined
Cat.prototype = obj;
//可得到Cat.prototype为以下结构对象
Cat.prototype = {
name: undefined, //要从构造函数Animal()传参
age: 18,
__proto__: {
eat() {
console.log(this.name +' eat')
}
}
}
`let catA = new Cat('xiaohei');`
//模拟new Cat('xiaohei')的过程
let obj = {};
obj.__proto__ = Cat.prototype;
Cat.call(obj, 'xiaohei'); //obj.subname = 'cat '+ 'xiaohei'
catA = obj;
//可得到catA为以下结构对象
catA = {
subname: 'cat xiaohei',
__proto__: {
name: undefined, //要从构造函数Animal()传参
age: 18,
__proto__: {
eat() {
console.log(this.name +' eat')
}
}
}
}
通过 Cat.prototype = new Animal();
let catA = new Cat('xiaohei');
得到的catA对象没有name属性
name属性需要从构造函数Animal()传入参数,而类式继承的过程中调用Animal()构造函数是没有传入参数的
catA对象的name和age属性都是在__proto__上的,也就是共有的属性,如果是引用类型的属性,修改了会影响到其他实例
以下是一个引用类型属性共享的例子
function Animal() {
arr: [1,2,3]
}
function Cat() {}
Cat.prototype = new Animal();
let catA = new Cat();
let catB = new Cat();
`可得到catA catB为以下结构对象
{
__proto__: {
arr: [1,2,3],
__proto__: {}
}
}`
catA.arr.push(666); //修改了__proto__上的引用类型的属性
console.log(catB.arr); //[1,2,3,666]
以上就是类式继承的特点,会有两个问题:
- 父构造函数不能传参
- 引用类型属性共享
构造器继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function () {
console.log(this.name + ' eat');
}
function Cat(name) {
Animal.call(this, name);
this.subname = 'cat '+ name;
}
let cat = new Cat('xiaohei');
console.log(cat.name, cat.subname); //xiaohei cat xiaohei
cat.eat(); //cat.eat is not a function
以上就是构造器继承,在构造函数Cat()中执行父构造函数Animal(),实例化时将父构造函数的属性复制到自身上,但是会有问题:
- 不会继承父构造函数原型上的方法
组合继承
组合继承将类式继承和构造器继承组合在一起,实现属性和方法的继承
function Animal(name) {
this.name = name;
this.age = 22;
}
Animal.prototype.eat = function () {
console.log(this.name + ' eat');
}
function Cat(name) {
Animal.call(this, name);
this.subname = 'cat ' + name;
}
Cat.prototype = new Animal();
let cat = new Cat('xiaohei');
console.log(cat.name); //xiaohei
console.log(cat.age); //22
console.log(cat.subname); //cat xiaohei
cat.eat(); //xiaohei eat
用伪代码来分析一下
`Cat.prototype = new Animal();`
//用伪代码模拟new Animal()
let obj = {};
obj.__proto__ = Animal.prototype;
Animal.call(obj); // 没有传入参数,等于执行了 this.name = undefined; thia.age = 22
Cat.prototype = obj;
//可得到Cat.prototype为以下结构的对线
Cat.prototype = {
name: undefined, //没有传入参数
age: 22,
__proto__: {
eat() {
console.log(this.name + ' eat');
}
}
}
`let cat = new Cat('xiaohei');`
//用伪代码模拟new Cat('xiaohei')
let obj = {};
obj.__proto__ = Cat.prototype;
Cat.call(obj,'xiaohei');
let cat = obj;
//可得到Cat.prototype为以下结构的对象
let cat = {
name: 'xiaohei',
age: 22,
subname: 'cat xiaohei',
__proto__: {
name: undefined, // 没有传入参数
age: 22,
__proto__: {
eat() {
console.log(this.name + ' eat');
}
}
}
}
以上就是得到的cat实例对象的结构,Cat类继承了Animal类的属性和方法
但组合继承有两个问题:
- 实例化cat时,执行了两次父构造函数的方法
- cat对象的__proto__里面的属性没有意义
寄生组合继承
function Animal(name) {
this.name = name;
this.age = 20;
}
Animal.prototype.eat = function() {
console.log(this.name + ' eat');
}
function Cat(name) {
Animal.call(this,name);
this.subname = 'cat ' + name;
}
function inherit(subClass,superClass){
function F(){};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}
inherit(Cat,Animal);
let cat = new Cat('xiaohei');
console.log(cat); //{name: 'xiaohei', age: 20, subname: 'cat xiaohei'}
cat.eat(); //xiaohei eat
经过inherit()方法,使用空的构造函数F()去实例化对象,这样就不会像组合继承那样复制父类的属性,并能复制父类原型上的方法
惯例,上伪代码~
`inherit(Cat,Animal);`
//用伪代码模拟inherit(Cat,Animal)
function F(){}
F.prototype = Animal.prototype;
let obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
Cat.prototype = obj;
Cat.prototype.constructor = Cat;
//可得到Cat.prototype为以下结构的对象
Cat.prototype = {
__proto__: {
eat(){
console.log(this.name + ' eat');
}
}
}
`let cat = new Cat('xiaohei');`
//用伪代码模拟 new Cat('xiaohei');
let obj = {};
obj.__proto__ = Cat.prototype;
//Cat.call(obj,'xiaohei');
Animal.call(obj,'xiaohei');
obj.subname = 'cat ' + 'xiaohei';
let cat = obj;
//可得到cat为以下结构的对象
let cat = {
name: 'xiaohei',
age: 20,
subname: 'cat xiaohei',
__proto__: {
__proto__: {
eat(){
console.log(this.name + ' eat');
}
}
}
}
以上的继承方式没有上面那些方式的缺点,能够把父类的属性和方法全部继承,一般认为是最好的继承方式
extends关键字
extends就是寄生组合继承的一种语法,在react中经常会用到
class Animal {
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log(this.name + ' eat')
}
}
class Cat extends Animal {
constructor(name,age){
super(name,age); // 相当于Animal.call(this,name,age)
this.subname = 'cat ' + name;
}
}
var cat = new Cat('xiaohei',18);
// cat为以下结构的对象,和寄生组合继承得到的对象是一样的结构
`let cat = {
name: 'xiaohei',
age: 18,
subname: 'cat xiaohei',
__proto__: {
__proto__: {
eat(){
console.log(this.name + ' eat');
}
}
}
}`
Cat类可以继承Animal类的所有属性和方法,实例化后和寄生组合继承得到的对象是一样的结构