一、为什么要有JS继承
假如每一个实例对象,都要有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。(这就和我们现实中会继承父母的资产一样,你是可以选择不继承的,但是浪费钱啊)
二、ES5继承的三种方式
1.原型继承
function Person() {
this.ages = [12,13] //引用类型的属性
}
//父类原型对象上添加一个函数sayHi
Person.prototype.sayHi = function () {
console.log('hello world')
}
// 构建一个子类
function Student() {
}
// 让 子类 继承 父类
Student.prototype = new Person()
var s1 = new Student()
var s2 = new Student();
s1.ages.push(24); //修改一个实例对象的引用类型属性,所有实例对象的属性发生变化
console.log(s1.ages); //[12,13,24]
console.log(s2.ages); //[12,13,24]
原型链继承总结:子类.prototype = 父类的实例;
//原型链继承是改变了子类的原型指向(这就像我和爸爸之间的关系,我的原型链(proto)指向了我的爸爸,你要问我有什么,我把我所有的属性和方法展示给你看,然后我的身上还有一个 proto ,从那里可以查询到我爸爸有啥)
缺点:
- 继承下来的属性并没有在该子类身上,而是在其 proto 上
- 要用的属性和方法虽然被继承,但是在多个位置,不便于寻找
- (不便于寻找)对书写,维护和阅读代码不太友好
- 引用类型的属性被所有实例共享
2.构造函数继承
//前提:需要使用call函数改变函数内部 this 指向的方法
function Person(name) {
this.name = "hello";
this.ages = [12,13] //引用类型的属性
}
//父类原型对象上添加一个函数sayHi
Person.prototype.sayHi = function () {
console.log('hello world')
}
// 构建一个子类
function Student(age,name) {
this.age = 18;
Person.call(this,name);
}
var s1 = new Student()
var s2 = new Student();
s1.ages.push(24);
console.log(s1.ages); //[12,13,24]
console.log(s2.ages); //[12,13]
console.log(s1.name); //"hello"
构造函数继承总结:父类.call(this,属性,方法)
//构造函数继承方式是利用父类构造函数体, 向子类身上添加成员(我爸会钓鱼,我不会,所以我要继承他的钓鱼方法就要让他教我【爸爸.call(我,钓鱼)】)
优点:
- 不用一个个在 proto 上找属性和方法了
- 要用几个属性,在call函数里传参就行(你想学几个技能,就让你爸教给你几个)
缺点:
- 只能继承父类的属性和方法,不能继承父类原型的属性和方法(因为你只找到你爸爸教你技能,所以你爷爷会啥你不清楚)
- 父类的方法被继承,每new对象时都会有同样的函数空间出现,所以继承父类方法会造成资源浪费
//因为构造函数继承支持多重继承,所以你要继承哪个父类,就 哪个父类.call(this,属性)(这意思就是你要向谁学习技能,就让他.call(你,技能))
3.组合继承
//组合继承:把原型链继承和构造函数继承融合在一起。
// 1. 父类构造函数
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log('hello world')
}
// 2. 子类构造函数
function Student(gender, name, age) {
this.gender = gender
// 构造函数继承
// call 方法的参数
// 第一个是要改变的this 指向
// 从第二个参数开始依次给函数传递参数
Person.call(this, name, age) // 这里的 this 就是 Student 的实例
// 调用一个 Person 函数, 并且把 Person 里面的 this 改变成 Student 的实例
}
// 原型继承
// 能继承属性和方法的
// s1 的 sayHi 方法是依靠这个 原型继承 继承下来的
Student.prototype = new Person()
var s1 = new Student('男', 'Jack', 18)
console.log(s1)
组合继承总结:父类.call(this,属性,属性),再子类.prototype = 父类的实例
优点:
- 将属性继承下来了,不用再一个个到_proto_找了(构造函数继承的优点)
- 方法也继承下来了(原型链继承的优点)
注意:组合继承方式的子类里面调用继承方式顺序不能调换,因为构建函数是根据参数决定继承哪些属性或方法,如果不写,等同于不继承
三、ES6继承的方式
es6的继承主要是class和extends来实现继承。
class Person{
constructor(name) {
this.name =name;
}
say(){
console.log(`我的名字叫${this.name}`)
}
}
class Student extends Person{
constructor(name,age) {
super(name);
this.age = age;
}
get(){
console.log(`我叫${this.name}今年${this.age}岁`);
}
}
let p = new Student("123",26);
p.say() //我的名字叫123
p.get(); //我叫123今年26岁
注:子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。因此,只有调用super之后,才可以使用this关键字。