继承有助于实现代码的复用。
一个对象通过继承另一个对象,就能拥有另一个对象的所有属性和方法。
原型链继承
子类型的原型为父类型的一个实例对象
//父类型
function Person(name, age) {
this.name = name,
this.age = age,
this.play = [1, 2, 3]
this.setName = function () { }
}
Person.prototype.setAge = function () { } //原型对象添加方法
//子类型
function Student(price) {
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象,子类型继承父类型的属性和方法
var s1 = new Student(15000) //创建子类型实例时不能向父类型传递参数
var s2 = new Student(14000) //创建两个子类型对象实例
s1.name='zhangsan'; //子类型实例创建成功后,可以对父类型构造函数进行传参
s1.age=10;
console.log(s1,s2)
将子类的原型指向父类的实例,所有子类的实例可以通过__proto__访问到Student.prototype也就是Person的实例,子类型可以访问父类型私有的属性和方法,通过Person实例的__proto__指向父类型的prototype原型对象,子类型可以访问父类型原型方法。
子类型通过原型链继承将父类型的私有属性和原型方法(公有方法)作为子类型的公有属性和方法。
操作基本数据类型是操作数值,操作引用数据类型操作的是地址,当父类型的私有属性中存在引用数据类型,通过继承称为子元素的公有属性,则在一个子类型的实例对象中操作该引用数据类型属性时,其他实例对象中的该属性也会发生改变。
s1.play.push(4)
console.log(s1.play, s2.play) //[1,2,3,4] [1,2,3,4] 来自原型对象的所有属性都被实例所共享
console.log(s1.__proto__ === s2.__proto__)//true
console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__)//true
在子类型中添加方法或重写父类方法时,一定要放置在原型指向语句中,不然无效。
function Person(name, age) {
this.name = name,
this.age = age
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(price) {
this.price = price
this.setScore = function () { }
}
// Student.prototype.sayHello = function () { }//在这里写子类的原型方法和属性是无效的,
//因为会改变原型的指向,所以应该放到重新指定之后
Student.prototype = new Person()
Student.prototype.sayHello = function () { }
var s1 = new Student(15000)
console.log(s1)
特点
- 父类型的所有私有和原型属性方法,在子类型中都能访问到,为子类型的公有属性。
- 简单易于实现。
缺点
- 无法实现多继承
- 来自原型对象的所有属性都会被实例共享
- 创建子类型实例时,无法向父类型的构造参数传参
- 为子类型添加属性和方法必须在原型链继承之后执行,不能放在构造器中。
构造函数继承
在子类型构造函数中通过call()调用父类型的构造函数。
function Person(name, age, a) {
this.name = name,
this.age = age,
this.a=a,
this.setName = function () {}
}
Person.prototype.setAge = function () {}
function Student(name,age,setName,price){
Person.call(this,name,age,setName); //继承父类型的属性和方法
this.price=price;
}
var s1 = new Student('Tom', 20, 15000)
console.log(s1);
console.log(s1.setAge())//Uncaught TypeError: s1.setAge is not a function 不能继承父类型原型属性和方法
该方法只能实现部分继承,继承父类型的私有属性和方法,不能继承父类型原型属性和方法。
特点
- 解决了原型链继承中子类型实例共享父类型引用数据类型属性的问题
- 创建子类型实例时,可以向父类型传递参数
- 可以实现多继承,call方法可以对多个父类型对象
缺点
- 最后创建的实例只是子类型的实例,不是父类型的实例
- 只能继承父类型的属性和方法,不能继承父类型的原型属性和方法
- 无法实现函数复用,每个子类型都有父类型实例函数的副本,影响性能
原型链+借用构造函数组合继承
function Person(name, age) {
this.name = name,
this.age = age,
this.arr=[1,2,3],
this.setAge = function () { }
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age,arr, price) {
Person.call(this,name,age,arr) //构造函数继承
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person() //原型链继承
var p1=new Student('zahngsan',10,2000);
console.log(p1.constructor) //Person
Student.prototype.constructor = Student //组合继承也是需要修复构造函数指向的
Student.prototype.sayHello = function () { }
var s1 = new Student('Tom', 20, 15000)
var s2 = new Student('Jack', 22, 14000)
console.log(s1)
s1.arr.push(4);
console.log(s1.arr,s2.arr); //[ 1, 2, 3, 4 ] [ 1, 2, 3 ] 一个子类型实例改变引用数据类型属性不会改变其他实例的该属性
console.log(s1.constructor) //Student
融合原型链继承和构造函数的优点,是JavaScript中常用的继承方式。
但是无论在什么情况下都会调用两次构造函数。
特点
- 可以继承父类型实例属性和方法,也可以继承父类型原型属性和方法
- 不存在不同子类型对象实例引用数据类型属性共享问题
- 创建子类型对象实例时可以传递父类型参数
- 函数可以复用
缺点
调用两次父类构造函数,生成了两份实例
ES6中class继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
class Person {
//调用类的构造方法
constructor(name, age) {
this.name = name
this.age = age
}
//定义一般的方法
showName() {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
}
let p1 = new Person('kobe', 39)
console.log(p1)
//定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age)//通过super调用父类的构造方法
this.salary = salary
}
showName() {//在子类自身定义方法
console.log("调用子类的方法")
console.log(this.name, this.age, this.salary);
}
}
let s1 = new Student('wade', 38, 1000000000)
console.log(s1)
s1.showName()
特点
语法简单易懂,操作更方便