继承的概念
继承,封装,多态是面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
继承的方式
- 原型链继承:
//父类
function Parent(name) {
this.name = name; //实例属性
this.list = [1, 2, 3];
this.sex = 'male';
}
Parent.prototype.color = 'yellow'; //原型属性
//子类
function Son(age) {
this.age = age;
}
//将子类原型指向父类实例(将父类的实例属性赋值给子类原型)
Son.prototype=new Parent();
let son1 = new Son(10);
let son2 = new Son(11);
console.log(son1.list); //[1, 2, 3]
console.log(son2.list); //[1, 2, 3]
console.log(son1.sex); //male
console.log(son2.sex); //male
//该继承方法会共享父类属性的值(其他文章说是共享引用类型的值,我个人不这么认为),且无法往父类构造函数传值
son1.list.push(4);
console.log(son1.list); //[1, 2, 3, 4]
console.log(son2.list); //[1, 2, 3, 4]
//有小伙伴可能会说,这时候没有共享
son1.sex='Female'
console.log(son1.sex); //Female
console.log(son2.sex); //male
//son1.sex是给son1这个实例新增了sex属性,按照查找顺序,如果在实例上找到了属性,就不会继续往上查找,所以这里的sex的值不是原型上的sex属性的值
//son1.list.push其实是操作原型上的数组
console.log(son1) // {age: 10, sex: "Female"} //sex属性是son1.sex新增的
console.log(son1.__proto__) //Parent {name: undefined, list: Array(4), sex: "male"}
console.log(son2) // {age: 11} //sex属性是子类实例所没有的,而是来自原型对象
console.log(son2.__proto__) //Parent {name: undefined, list: Array(4), sex: "male"}
缺点:1.子类不能往父类的构造函数传值;
2.所有子类共享父类属性的值;
- 构造函数继承
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
this.sex = 'male'
}
Parent.prototype.color = 'yellow';
function Son(age, name) {
Parent.call(this, name) //这一句实现继承,创建子类实例时调用父类构造函数
this.age = age;
}
let son1 = new Son(10,'一个名字')
let son2 = new Son(10,'又是一个名字')
console.log(son1.list); //[1, 2, 3]
console.log(son2.list); //[1, 2, 3]
//该继承方法解决了原型链继承共享父类属性值的缺点
son1.list.push(4);
console.log(son1.list); //[1, 2, 3, 4]
console.log(son2.list); //[1, 2, 3]
//但是无法继承父类原型上的属性和方法
console.log(son1.color) //undefined
缺点:1.无法继承父类原型上的属性和方法;
2.父类不在子类的原型链上;
3.父类的实例方法不能复用;
- 组合继承
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
this.sex = 'male'
}
Parent.prototype.color = 'yellow';
function Son(age, name) {
Parent.call(this, name)
this.age = age;
}
Son.prototype=new Parent();
Son.prototype.construct = Son;
let son1 = new Son(10,'一个名字')
//该继承方法兼备原型链继承和构造函数继承的优点,但是会造成属性重复多余
// Son.prototype=new Parent(); 将父类属性加到子类原型上
// Parent.call(this, name); 将父类实例属性传递给子类
//子类的实例和原型上会有重复的属性,实例的属性将原型上的属性覆盖。
console.log(son1) //{name: "一个名字", list: Array(3), sex: "male", age: 10}
console.log(son1.__proto__) //{name: undefined, list: Array(3), sex: "male"}
缺点:1.造成属性重复多余;
- 原型式继承
//将已有的对象作为原型对象
function CreateObj(obj) {
function T() { };
T.prototype = obj;
return new T();
}
const obj = {
name: 'name',
list: [1, 2, 3]
}
let obj1 = CreateObj(obj)
let obj2 = CreateObj(obj)
//该方法和原型链继承类似,父类的属性和方法都在原型上,不同的实例共享。
console.log(obj1.list); //[1, 2, 3]
console.log(obj2.list); //[1, 2, 3]
obj1.list.push(4);
console.log(obj1.list); //[1, 2, 3, 4]
console.log(obj2.list); //[1, 2, 3, 4]
缺点:1.不同的实例共享属性和方法;
- 寄生式继承
//与原型继承的区别在于增强对象
function CreateObj(obj) {
function T() { };
T.prototype = obj;
T.Say = function(){
console.log('Say')
}
return new T();
}
const obj = {
name: 'name',
list: [1, 2, 3]
}
缺点:1.实例方法不能复用;
- 寄生组合继承
function Parent(name) {
this.name = name;
this.list = [1, 2, 3];
this.sex = 'male'
}
Parent.prototype.color = 'yellow';
function Son(age, name) {
Parent.call(this, name) //继承父类实例属性和方法
this.age = age;
}
//只继承父类原型属性和方法
function ChangePrototype(Son, Parent) {
function T() { };
T.prototype = Parent.prototype;
T.prototype.construct = Son;
Son.prototype = new T();
}
ChangePrototype(Son, Parent)
let son1 = new Son();
console.log(son1) // {name: undefined, list: Array(3), sex: "male", age: undefined}
console.log(son1.__proto__) // {}
console.log(son1.color) //yellow
- extends继承
class Parent {
constructor(name) {
this.name = name;
this.list = [1, 2, 3];
this.sex = 'male'
}
sayName() {
console.log(this.name)
}
}
class Son extends Parent {
constructor(age, name) {
super(name);
this.age = age;
}
}
let son1=new Son(11,'name')
son1.SayName(); //name