# JS面向对象基础--继承
虽然JS中没有类的概念,是通过构造函数模式和原型模式组合实现的类,然后可以用new关键字来创建实例,又以什么样的方式实现了类继承呢?
同样我们还是可以用**面对对象编程(oop)**的思想来理解js中的类。类的继承、封装、多态:
封装:低耦合高内聚
多态:重载和重写、向上转型。
重载:方法名和返回值类型相同,但形参个数或者形参类型不同
- JS中的重载是指:同一个方法,根据参数个数不同,实现出不同的效果。(场景:ES6中extends继承的时候需要省略写constructor根据constructor的参数个数来定)
//Java中(写多个同名同返回值类型的方法,实现重载) public void sum(int n){} public void sum(int n,int m){} public void sum(int n,String m){} public void sum(int n,String m,float o){} //JS中(在JS中不存在真正意义上的重载) function sum(n,m){console.log('1')} function sum(n,m,o){console.log('2')}//函数提升 sum(1,2)//2 sum(1,2,3)//2 //但是js中也有类似的功能函数的arguments function sum(){ console.log(arguments) //if(arguments[2] === undefined){ ... return } //else{...} } sum(1,2)//[Arguments] { '0': 1, '1': 2 } sum(1,2,3)//[Arguments] { '0': 1, '1': 2, '2': 3 } //同名的函数传入不同个数的形参,产生不同的效果。
重写:在类的继承当中,子类可以重写父类中的方法。
- Java是拷贝继承,子类把父类的属性和方法拷贝过来,然后再进行重写父类的方法。(是不会改变父类)
继承:子类继承父类上的属性和方法
继承的目的:子类实例能调用要用父类中的方法和属性
function A(x){ this.x=x } A.prototype.getX=function(){ console.log(this.x) } function B(y){ this.y=y } B.prototype.getY=function(){ console.log(this.y) } let b1=new B(100) b1.y//100 b1.getY()//打印100 //此时我们想要使用b1实例点出getX方法 b1.getX()//TypeError: b1.getX is not a function
JS中实现类的继承常用的四种方法:
(原理思路:实例调用一个属性,先从本身对象中找是否有这个属性,如果没有该属性,则沿原型链找,直到找到为止,找不到就返回undefined)
# 原型继承
让父类中的属性和方法在子类实例的原型链上!!
CHILD.prototype = new PARENT(); CHILD.prototype.constructor = CHILD;
特点:
- 1.不像其他语言中的继承一样(其它语言如Java的继承一般是拷贝继承,也就是子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供子类的实例调取使用),而JS原型继承是把父类的原型放在子类实例的原型链上,实例想调取这些方法和属性,是基于
__proto__
原型链查找机制完成的。 - 2.子类可以重写父类上的方法(这样会导致父类其它的实例也受到影响)
CHILD.prototype.__proto__.xxx
- 3.父类中私有和公有的属性方法,最后都会变为子类中公有的属性方法。
//依然上面的例子 function A(x){//私有 this.x=x } A.prototype.getX=function(){//公有 console.log(this.x) } function B(y){ this.y=y } //此时我们想要B的原型对象和Object原型对象之间加一个A的原型对象 B.prototype=new A(200)//此时B的原型对象中有:1.A的实例本身对象2.A的原型对象(包含原型上的方法和constructor属性) B.prototype.constructor = B;//B的原型对象的constructor属性指针需要修改成B的构造函数。 B.prototype.getY=function(){//我们再来自定义添加B的原型方法 console.log(this.y) } //B.prototype是指向A的实例,此时在B.prototype添加属性,相当于在A的实例上面添加方法。如果B.prototype.__proto__.xxx就可以修改A原型上的方法了。这样显然不科学。所以我们很少用__proto__
- 1.不像其他语言中的继承一样(其它语言如Java的继承一般是拷贝继承,也就是子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供子类的实例调取使用),而JS原型继承是把父类的原型放在子类实例的原型链上,实例想调取这些方法和属性,是基于
# call继承
子类继承父类中的私有属性(继承到了子类的私有属性上)
CHILD方法中把PARENT当做普通函数来执行, 让PARENT中的this指向CHILD的实例, 相当于给CHILD的实例设置了很多PARENT的私有属性和方法。
特点:
- 1.只能继承父类私有的属性或方法(因为把PARENT当普通函数来执行,和其原型上的公有属性或方法没有关系)
- 2.父类私有的属性或方法 变为 子类私有的属性或方法
function A(x) { this.x = x//私有属性和方法 } A.prototype.getX = function () {//公有属性和方法 console.log(this.x) } function B(y) { A.call(this,200)//直接调用A函数(并非去new执行构造函数生产实例),所以此时与prototype属性没什么关系了。 this.y = y } B.prototype.getY = function () { console.log(this.y) } let b2 = new B(100) console.log(b2.y);//100 console.log(b2.x);//200 //所以此时就实现了,子类继承父类中的私有属性(继承到了子类的私有属性上)
# 寄生组合继承
CALL继承+类似于原型继承:
特点:父类私有和公有的属性和方法分别是子类实例私有和公有的属性和方法。(推荐)
Object.create(obj):创建一个新对象,让这个新对象的
__proto__
指向obj
let obj = {name:'xiaoming',age:20} let OBJ = Object.create(obj) console.log(OBJ);//{} console.log(OBJ.__proto__);//{ name: 'xiaoming', age: 20 } //我们自己实现一个Object.create()方法 Object.create = function (obj) { let newObj = {} newObj.__proto__ = obj//IE不支持__proto__ return newObj } Object.create = function (obj) { function newObj() { }//只有new构造函数创建对象才支持prototype newObj.prototype = obj return new newObj() }
function A(x) { this.x = x } A.prototype.getX = function () { console.log(this.x) } function B(y) { A.call(this, 200)//保留call:继承构造函数中的私有属性或方法 this.y = y } //用于继承公用的属性或方法 B.prototype = Object.create(A.prototype) //又使用原型链继承,此时是新创建一个Object实例,但是这个实例原型指向A的原型 B.prototype.constructor =B //再把重定向的到的constructor指回B B.prototype.getY = function () { console.log(this.y) } let b2 = new B(100) console.log(b2.y); console.log(b2.x);
# ES6中的extend继承(extend和super)
原理就是寄生组合继承的语法糖。
//实现 父类公有的属性和方法 成为 子类公有的属性和方法 class CHILD extends PARENT { //=>B.prototype.__proto__=A.prototype constructor(){ super()//实现 父类私有的属性和方法 成为 子类私有的属性和方法 //=>A.call(this) 把父类当做普通方法执行,给方法传递参数,让方法中的this是子类的实例 } //如果不写constructor(),浏览器会默认创建 //constructor(...args){ super(...args) } }
//ES6中基于class创在出来的类不能直接当做普通函数来执行。 //所以call继承是行不通的。 //也不允许重定向原型指向 //所以原型继承也行不通。 class A { constructor(x) { this.x = x } getX() { console.log(this.x); } } class B extends A { constructor(x, y) { super(x)//子类只要继承父类constructor()中第一行必须写super() this.y = y } getY() { console.log(this.y); } } let b1 = new B(200, 100); b1.getX();//200 b1.getY();//100