JS中的继承
继承:子类继承父类中的属性和方法
- JS中的继承主要利用的是原型链。可以对应原型链的文章。
- 原型链的原理:让一个引用类型继承另一个引用类型的属性和方法。
- JS中的继承机制和其它后台语言是不一样的,有自己的独特处理方式。
JAVA中类的多态:重载和重写
- JAVA中重载:函数名相同,但是传参类型、数量不同或者返回值不一样,这相当与把一个函数重载了。
- JS中没有类似于后台语言中的重载机制:JS中的重载只的是同一个方法,根据传参不同,实现不同的业务逻辑。
- 重写:子类重写父类上的方法。
方案一:原型链继承
子类的原型指向父类的实例(B子类 => A父类)
function A() {
this.x = 100;
this.colors=['red','black','blue','green']
}
A.prototype.getX = function getX() {
console.log(this.x);
};
function B() {
this.y = 200;
}
B.prototype.sum=function(){
console.log('sum')
}
//原型链继承
B.prototype = new A(); // 子类的原型指向父类的实例 。B继承A
B.prototype.constructor = B
B.prototype.getY = function getY() {
console.log(this.y);
};
let b1 = new B;
let b2 = new B;
b2.colors.push('yellow');
console.log(b2.colors); // ["red", "black", "blue", "green", "yellow"]
console.log(b1.colors) // ["red", "black", "blue", "green", "yellow"]
// 在A构造函数定义了一个colors属性,当B通过原型链继承后,这个属性就会出现B.prototype中,就跟专门创建了B.prototype.colors一样.
//所以会导致B的所有实例都会共享这个属性,所以b2修改colors这个引用类型值,也会反映到b1中
- 并不会把父类中的方法克隆一份给子类,而是建立了子类和父类之前的原型链查找机制。
- 重定向子类的原型后,默认丢失了原本的constructor属性(或者原本在原型上设置的属性和方法)
- 子类或者子类的实例,可以基于原型链“肆意”修改父类上的属性,对父类造成一些破坏。
- 会把父类中的私有属性方法作为子类共有属性方法继承过来(父类中不管是共有还是私有属性,最后都变为子类的共有属性)
- 优缺点:
- 优点:
- 继承了⽗类的模板,⼜继承了⽗类的原型对象
- 缺点:
- 可以在⼦类构造函数中,为⼦类实例增加实例属性。如果要新增原型属性和⽅法,则必须放在
SubType.prototype = new SuperType('SubType');这样的语句之后执⾏。 - 无法实现多继承
- 来⾃原型对象的所有属性被所有实例共享
// ⽗类 function SuperType () { this.colors = ["red", "blue", "green"]; this.name = "SuperType"; } // ⼦类 function SubType () {} // 原型链继承 SubType.prototype = new SuperType(); // 实例1 var instance1 = new SubType(); instance1.colors.push("blcak"); instance1.name = "change-super-type-name"; console.log(instance1.colors); // ["red", "blue", "green", "blcak"] console.log(instance1.name); // change-super-type-name // 实例2 var instance2 = new SubType(); console.log(instance2.colors); // ["red", "blue", "green", "blcak"] console.log(instance2.name); // SuperType- 更改 父 引⽤类型属性时,会使 子 所有实例共享这⼀更新。基础类型属性更新则不会。
- 创建⼦类实例时,⽆法向⽗类构造函数传参,或者说是,没办法在不影响所有对象实例的情况下,向超类的构造函数传递参数
- 可以在⼦类构造函数中,为⼦类实例增加实例属性。如果要新增原型属性和⽅法,则必须放在
方案二: call继承(构造函数继承)
- 基本思想:在⼦类型的构造函数内部调⽤⽗类型构造函数。
- 注意:函数只不过是在特定环境中执⾏代码的对象,所以这⾥使⽤ apply/call 来实现。
- 使⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类(没⽤到原型) 把父类当做普通函数执行,让其执行的时候,方法中的this变为子类的实例即可
- 特点
- 只能继承父类中的私有属性(继承的私有属性赋值给子类实例的私有属性),而且是类似拷贝过来一份,而不是链式查找。
- 因为只是把父类当做普通的方法执行,所以父类原型上的公有属性方法无法被继承过来。
function A() {
this.x = 100;
this.colors = ['red', 'blue', 'green']
}
A.prototype.getX = function getX() {
console.log(this.x);
};
function B() {
//CALL继承
A.call(this); //=>this.x = 100; b.x=100;
this.y = 200;
}
B.prototype.getY = function getY() {
console.log(this.y);
};
let b1 = new B; // 运⾏⼦类构造函数,并在⼦类构造函数中运⾏⽗类构造函数,this绑定到⼦类
let b2 = new B;
console.log(b1); // B {x:100, y:200}
b1.getX() // Uncaught TypeError: b.getX is not a function
b1.getY() // 200
b1.colors.push('black')
console.log(b1.colors) //["red", "blue", "green", "black"]
console.log(b2.colors) //["red", "blue", "green"]
//在新建B实例是调用了A构造函数,这样以来,就会在新B对象上执行A函数中定义的所有对象初始化代码 结果,B的每个实例就会具有自己的colors属性的副本了
- 借助call可以传递参数
function A(name) {
this.name = name
}
function B() {
// 继承SuperType
A.call(this, '小明')
this.job = 'student'
}
let b = new B()
console.log(b.name) // 小明
console.log(b.job) // student
- 有点缺点
- 优点:
- 解决了1中⼦类实例共享⽗类引⽤对象的问题,实现多继承,创建⼦类实例时,可以向⽗类传递参数
- 缺点:
- 实例并不是⽗类的实例,只是⼦类的实例
- 只能继承⽗类的实例属性和⽅法,不能继承原型属性/⽅法
- ⽆法实现函数复⽤,每个⼦类都有⽗类实例函数的副本,影响性能
方案三:组合继承
CALL继承+变异版的原型继承共同完成的
- CALL继承实现:私有到私有(实现对实例属性的继承)
- 原型继承实现:公有到公有 (原型属性和方法的继承)
function A(name) {
this.x = 100;
this.name = name ;
this.colors = ['red', 'blue', 'green']
}
// 父类添加方法
A.prototype.getX = function getX(x) {
this.x = x
console.log(this.x);
};
A.prototype.sayName = function sayName() {
console.log(this.name);
};
function B(name, job) {
// call 继承!!!!!!!!!!!!!!!!!
A.call(this,name);
this.y = 200;
this.job = job
}
//原型链继承
B.prototype = Object.create(A.prototype);
//=>Object.create(OBJ) 创建一个空对象,让其__proto__指向OBJ(把OBJ作为空对象的原型)
B.prototype.constructor = B;
//为子类添加一些方法
B.prototype.getY = function getY() {
console.log(this.y);
};
B.prototype.sayJob = function sayJob() {
console.log(this.job);
};
let b = new B('小明','student');
let b1 = new B('小红','teacher');
console.log(b) //B {x: 100, name: "小明", colors: Array(3), y: 200, job: "student"}
b.colors.push('black')
console.log(b.colors) //["red", "blue", "green", "black"]
b.sayName() // 小明
b.sayJob() // 'student'
b.getX(3); //3
b.getY(); //200
console.log(b1)// B {x: 100, name: "小红", colors: Array(3), y: 200, job: "teacher"}
console.log(b1.colors) //["red", "blue", "green"]
b1.sayName() // 小红
b1.sayJob() // 'teacher'
b1.getX(50); // 50
b1.getY(); //200
console.log(b instanceof A) //true
console.log(b instanceof B)//true
原理图:
使用ES6创建class
class A {
constructor(name) {
this.x = 100;
this.name = name
}
getX() {
console.log(this.x);
}
}
//=>extends继承和寄生组合继承基本类似
class B extends A {
constructor() {
super(name); //=>一但使用extends实现继承,只要自己写了constructor,就必须写super <=> A.call(this,100)
this.y = 200;
}
getY() {
console.log(this.y);
}
}
let b = new B;
console.log(b); // B {x: 100, y: 200}
ES6引入的class和原有的JavaScript原型继承有什么区别呢
实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。
真实项目中用到继承的情况
- REACT创建类组件
class Vote extends React.Component{
}
- 自己写插件或者类库的时候
class Utils{
//=>项目中公共的属性和方法
}
class Dialog extends Utils{
}