什么是继承?
A对象通过继承B对象,就可以拥有B对象的所有属性和方法。
原型链继承:
子类的原型是父类的实例,子类继承父类的所有私有属性、私有方法和其原型上的属性和方法。
// 定义父类Person function Person(name,age){ this.name=name; this.age=age; this.eat=function(){ console.log("Person的私有eat方法"); }; } Person.prototype.play=function(){ console.log("Person的公有play方法"); }; Person.prototype.sex="男"; // 定义子类Student function Student(score){ this.score=score; this.eat1=function(){ console.log("Student的私有eat1方法"); }; } // 子类继承父类 Student.prototype=new Person("wql",23); // 子类的原型是父类的实例 ----无法向父类构造函数传参 Student.prototype.sex1="女"; Student.prototype.play1=function(){ console.log("Student的公有play1方法"); }; // 实例化子类 var s1=new Student(100); console.log(s1.score) // 100 console.log(s1.sex1) // 女 s1.eat1() // Student的私有eat1方法 s1.play1() // Student的公有play1方法 console.log(s1.name) // wql console.log(s1.age) // 23 console.log(s1.sex) // 男 s1.eat() // Person的私有eat方法 s1.play() // Person的公有play方法
缺点:
1、无法实现多继承
2、来自原型对象的所有属性被所有实例共享
3、创建子类时,无法向父类构造函数传参
4、要想为子类新增属性和方法,必须要在Student.prototype=new Person()之后执行,不能放到构造器中
借用构造函数继承:
在子类构造函数中通过call()调用父类构造函数。
function Person(name,age){ this.name=name; this.age=age; this.play=function(){ console.log("play") }; } Person.prototype.eat=function(){ console.log("eat") }; function Student(name,age,score){ Person.call(this,name,age); // 调用父类构造,可以向父类传参 this.score=score; } var s1=new Student("wql",23,100); console.log(s1) // Student {name: "wql", age: 23, score: 100, play: ƒ} console.log(s1.score) // 100 console.log(s1.name) // wql console.log(s1.age) // 23 s1.play() // play s1.eat() // 语法错误 ----仅调用构造函数的方法不能继承父类的原型上的属性和方法
缺点:
1、实例不是父类的实例,只是子类的实例
2、只能继承父类的实例属性和方法,不能继承父类的原型上属性和方法
3、无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承(原型链+借用构造函数):
通过调用父类构造,继承父类的属性并保留传参的优点,然后把父类的实例作为子类的原型,实现函数复用。
function Person(name,age){ this.name=name; this.age=age; this.play=function(){ console.log("play") } } Person.prototype.eat=function(){ console.log("eat") } function Student(name,age,price){ Person.call(this,name,age); // 调用父类构造 ----好处是可以向父类传参 this.price=price; this.setScore=function(){ console.log("setScore") } } Student.prototype=new Person(); // 父类的实例作为子类的原型 Student.prototype.constructor=Student; // 修复构造函数的指向 Student.prototype.sayHello=function(){ console.log("sayHello") } var s1=new Student("wql",23,100); var s2=new Student("syz",30,150); console.log(s1) // Student {name: "wql", age: 23, price: 100, play: ƒ, setScore: ƒ} console.log(s2) // Student {name: "syz", age: 30, price: 150, play: ƒ, setScore: ƒ} console.log(s1.name) // wql console.log(s1.age) // 23 s1.play() // play s1.eat() // eat ----父类的实例作为子类的原型,可以继承父类的原型属性和方法 s1.setScore() // setScore s1.sayHello() // sayHello console.log(s1.constructor) // 打印Student函数
缺点:调用了两次父类构造函数,生成了两份实例。
通过父类原型和子类原型指向同一个对象,子类可以继承父类的公有方法作为自己的公有方法,而且不会初始化两次实例方法/属性,避免了组合继承的缺点。
function Person(name,age){ this.name=name; this.age=age; this.play=function(){ console.log("play") } } Person.prototype.eat=function(){ console.log("eat") } function Student(name,age,price){ Person.call(this,name,age); this.price=price; this.setScore=function(){ console.log("setScore") } } Student.prototype=Person.prototype; // 父类原型和子类原型指向同一个对象 Student.prototype.sayHello=function(){ console.log("sayHello") } var s1=new Student("wql",23,100); console.log(s1) // Student {name: "wql", age: 23, price: 100, play: ƒ, setScore: ƒ} console.log(s1.name) // wql console.log(s1.age) // 23 console.log(s1.price) // 100 s1.play() // play s1.eat() // eat s1.setScore() // setScore s1.sayHello() // sayHello
缺点:无法确定实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。
原型可以基于已有的对象来创建对象,var B=Object.create(A);----以A对象为原型,生成B对象,B继承了A的所有属性和方法。
function Person(name,age){ this.name=name; this.age=age; } Person.prototype.eat=function(){ console.log("eat") } function Student(name,age,price){ Person.call(this,name,age); this.price=price; this.setScore=function(){ console.log("setScore") } } Student.prototype=Object.create(Person.prototype); // 将Person的原型复制给Student的原型 Student.prototype.constructor=Student; // 修复构造函数的指向 var s1=new Student("wql",23,100); console.log(s1) // Student {name: "wql", age: 23, price: 100, setScore: ƒ} console.log(s1.name) // wql console.log(s1.age) // 23 console.log(s1.price) // 100 s1.eat() // eat s1.setScore() // setScore console.log(s1 instanceof Student,s1 instanceof Person) // true true console.log(s1.constructor) // 打印Student函数
几乎完美!
ES6的class继承:
通过extends关键字实现继承。
// 定义父类 class Person{ constructor(name,age){ this.name=name; this.age=age; this.play=function(){ console.log("play") } } showName(){ console.log("调用父类的方法") console.log(this.name,this.age) } } let p1=new Person("wql",23); console.log(p1) // Person {name: "wql", age: 23, play: ƒ} // 定义一个子类 class Student extends Person{ constructor(name,age,salary){ super(name,age); // 通过super调用父类的构造方法,继承来自父类的name、age属性和play方法 this.salary=salary; } showName(){ console.log("调用子类的方法") console.log(this.name,this.age,this.salary) } } let s1=new Student("syz",30,10000000); console.log(s1) // Student {name: "syz", age: 30, salary: 10000000, play: ƒ} s1.showName() // 调用子类的方法 syz 30 10000000 s1.play() // play
Student通过extends关键字继承Person,在constructor中通过super()调用父类的构造方法。