详解js继承
js原型图
JS继承的几种方式
1、原型链继承
function Animal(name, weight) {
this.name = name;
this.weight = weight;
}
Animal.prototype.eat = function () {
console.log("吃东西");
};
function Dog(color) {
this.color = color;
}
Dog.prototype = new Animal('小黑', '50斤');
Dog.prototype.bite = function () {
console.log('咬人');
};
function Huskie(sex) {
this.sex=sex;
}
Huskie.prototype = new Dog('灰色');
Huskie.prototype.play = function () {
console.log('玩');
};
var hashiqi = new Huskie('公');
console.log(hashiqi.name); // 小黑
console.log(hashiqi.weight); // 50斤
console.log(hashiqi.color); // 灰色
console.log(hashiqi.sex); // 公
hashiqi.play(); // 玩
hashiqi.eat(); // 吃东西
hashiqi.bite(); // 咬人
这种是纯粹的继承关系,实例是子类的实例,也是父类的实例
特点
- 简单
- 父类新增原型方法/原型属性,子类都能访问到
console.log(hashiqi.arr); // undefined Animal.prototype.arr = [1, 2, 3]; console.log(hashiqi.arr); // undefined
缺点
- 新实例无法向父类构造函数传参。(即 new Dog() 无法给 Animal传参)
- 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
let d1 = new Dog(); let d2 = new Dog(); Animal.prototype.obj = { a: 1, b: 2 }; console.log(d1.obj); // { a: 1, b: 2 } console.log(d2.obj); // { a: 1, b: 2 } d1.obj.a = 3; console.log(d1.obj); // { a: 3, b: 2 } console.log(d2.obj); // { a: 3, b: 2 }
2、借用构造函数继承
function Person(name, age, sex, weight) {
// 创建构造函数
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.eat = function() {
console.log("吃饭");
};
// 学生的构造函数
function Student(name, age, sex, weight, height) {
this.height = height;
Person.call(this, name, age, sex, weight); // window.Person.call(this)
}
// 实例化对象
const stu1 = new Student("小明", 18, "男", "100kg", "170cm");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.height); // 小明 18 男 100kg 170cm
console.log(stu1 instanceof Person); // false
console.log(stu1 instanceof Student); // true
stu1.eat(); // stu1.eat is not a function
所谓借用构造函数,就是在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的。
特点
- 在子实例中可向父实例传参
- 可以继承多个构造函数属性(call多个构造函数即可)
缺点
- 只继承了父类的属性,没有继承父类的原型的属性
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
- 实例并不是父类的实例,只是子类的实例
3、组合继承
function Person(name, age, sex, weight) {
// 创建构造函数
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.eat = function() {
console.log("吃饭");
};
// 学生的构造函数
function Student(name, age, sex, weight, height) {
this.height = height;
Person.call(this, name, age, sex, weight); // window.Person.call(this)
}
Student.prototype = new Person();
// 修复构造函数指向, 不然Student.prototype.constructor会指向Person
Student.prototype.constructor = Student;
// 实例化对象
const stu1 = new Student("小明", 18, "男", "100kg", "170cm");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.height); // 小明 18 男 100kg 170cm
console.log(stu1 instanceof Person); // true
console.log(stu1 instanceof Student); // true
stu1.eat(); // 吃饭
console.log(Student.prototype.constructor); // [Function: Student]
结合了以上两种继承方式的特点,可传参,可复用
特点
- 解决以上两种继承方式的缺点
缺点
- 调用了两次父类构造函数(耗内存),生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4、寄生组合式继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log("说话");
};
Person.go = function() {
console.log("走动");
};
// 1.创建F构造函数,并且返回实例 new F();
// 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
function object(parentPro) {
var F = function() {};
F.prototype = parentPro;
return new F();
}
// 2.将返回的实例作为子类的原型对象,同时修改子类原型对象的constructor指向子类。
function inhert(par, child) {
var mid = object(par.prototype);
mid.constructor = child;
child.prototype = mid;
}
function Chinese(skin, language, name, age) {
this.skin = skin;
this.language = language;
Person.call(this, name, age);
}
inhert(Person, Chinese);
var p1 = new Chinese("黄皮肤", "中文", "小白", 18);
console.log(p1); // Chinese { skin: '黄皮肤', language: '中文', name: '小白', age: 18 }
console.log(p1.constructor); // [Function: Chinese]
p1.say(); // 说话
这种继承方式基本是完美的解决方案,通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
图解如下
5、ES6 extends继承
class People {
constructor(name) {
this.name = name;
}
sayMyName() {
return this.name;
}
}
class Student extends People {
constructor(name, age) {
super(name); //调用父类的constructor(x,y)
this.age = age;
}
sayMyAge() {
//调用父类的方法
return this.age;
}
}
let stu1 = new Student("小李", 20);
console.log(stu1); // Student { name: '小李', age: 20 }
console.log(stu1.sayMyName()); // 小李
console.log(stu1.sayMyAge()); // 20
console.log(stu1 instanceof People); // true
console.log(stu1 instanceof Student); // true