目录
1、什么是面向对象(OOP)?
面向对象(OOP,Object Oriented Programming)是一种编程思想,它有三大特性:封装、继承、多态。JavaScript 以及目前几个主流的前端框架 Vue/React/JQuery 也是基于面向对象构建出来的,它们都是类,在开发的时候都是创建它们的实例来进行操作的。
JavaScript 中的面向对象,和其它编程语言略有不同,JavaScript 中类和实例基于原型和原型链机制,而且 JavaScript 中关于类的重载、重写、继承也和其它语言不太一样。JavaScript 中不存在真正意义上的重载,JavaScript 中重载指的是同一个方法,根据传参不同,实现出不同的效果。重写:在类的继承中,子类可以重写父类中的方法,但是 JavaScript 和后端语言的继承不太一样,下面来说一下 JavaScript 的继承。
2、JavaScript 原型链继承
继承其实就是子类继承父类中的属性和方法,目的是让子类的实例能够调取父类中的属性和方法。下面我们来看一下 JavaScript 中原生的继承方式:原型链继承 — 将父类中的属性和方法放到子类实例的原型链上。关键步骤如下:
CHILD.prototype = new PARENT(); //修改子类的原型指向,将子类的原型指向父类的实例
CHILD.prototype.constructor = CHILD; //修改子类的原型指向后,保证原型重定向的完整性
原型链继承的特点:
(1)JavaScript 原型链继承不像其他语言中的继承一样(其它语言的继承一般是拷贝继承,也就是子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供子类的实例调用),它是把父类的实例放到子类实例的原型链上,实例想调取这些方法,是基于 __proto__原型链查找机制 完成的。
(2)子类可以重写父类上的方法(这样会导致父类其它的实例也受到影响)。
(3)父类中私有或者公有的属性方法,最后都会变为子类中公有的属性和方法。
下面看一个例子:将B类通过原型链继承A类,这样子类B的实例可以通过原型链调用父类A中的属性和父类A原型上的方法。
function A(x){
this.x = x; //A类中的私有属性x
}
A.prototype.getX = function(){ //往A类的原型上添加getX()公有方法 每一个函数都天生自带一个prototype属性指向自身原型
console.log(this.x); //每个对象都天生自带一个__proto__属性
};
function B(y){
this.y = y; //B类中的私有属性y
}
B.prototype = new A(200); //修改B类的原型指向,将B类的原型指向A类的实例
B.prototype.constructor = B; //修改B类的原型指向后,注意要改写构造器,保证原型重定向的完整性
B.prototype.getY = function(){ //往B类的原型上添加getY()公有方法 每一个函数都天生自带一个prototype属性指向自身原型
console.log(this.y); //每个对象都天生自带一个__proto__属性
};
let b1 = new B(100);
b1.y; //B的实例b1,可以调用B类中的私有属性y
b1.getY(); //B的实例b1,可以调用B类原型上的公共方法getY()
b1.getX(); //B的实例b1通过原型链,可以调用A原型上的方法getX()
3、JavaScript call() 继承
在JavaScript 的第一种继承方式(原型链继承)中:,父类中私有或者公有的属性和方法,最后都会变为子类中公有的属性和方法。这样或可能不太好,有没有一种继承方法,可以使父类中的私有属性变成子类的私有属性,父类中的共有方法变成子类的共有方法?下面我们先来介绍一种新的继承方式,call() 继承,可以使父类中的私有属性变成子类的私有属性。
JavaScript 中第二种继承方式:call() 继承 — 在子类方法中把父类当做普通函数执行,让父类中的 this 指向子类的实例,相当于给子类的实例设置了很多私有的属性或者方法。
call() 继承的特点:
1、只能继承父类私有的属性或者方法(因为是把父类当做普通函数执行,和其原型上的属性和方法没有关系)。
2、父类的私有属性或方法会变为子类的私有属性或方法。
function A(x){
this.x = x;
}
function B(y){
A.call(this,200); //改变this指向,A类中的this此时指向B类的实例b1:b1.x = 200
this.y = y;
}
let b1 = new B(100);
b1.y;
b1.x; //200
4、寄生组合继承:call() 继承+类似于原型继承
call() 继承是将父类的私有属性或方法会变为子类的私有属性或方法,还缺少将父类共有的属性和方法变为子类的共有的属性和方法,针对这一问题,介绍一种继承方式:寄生组合继承。
寄生组合继承是首先通过 call() 继承将父类的私有属性或方法会变为子类的私有属性或方法,然后通过一种类似于原型继承的方式将父类共有的属性和方法变为子类的共有的属性和方法。
特点:父类私有和公有的分别是子类实例的私有和公有属性方法(推荐)
function A(x){
this.x = x; //A类中的私有属性x
}
A.prototype.getX = function(){
console.log(this.x);
};
function B(y){
A.call(this,200); //改变this指向,A类中的this此时指向B类的实例b1:b1.x = 200
this.y = y; //B类中的私有属性y
}
B.prototype = Object.create(A.prototype);//创建一个空对象,让空对象的__proto__指向第一个参数,然后让B的原型指向该对象
B.prototype.constructor = B; //修改子类的原型指向后,保证原型重定向的完整性
B.prototype.getY = function(){
console.log(this.y);
};
let b1 = new B(100);
b1.x; //200
b1.y; //100
b1.getY(); //100
b1.getX(); //200
//IE不兼容,__proto__再IE中禁用,可以重写create() 方法
Object.create = function (obj){
function Fn(){}
Fn.prototype = obj;
return new Fn();
};
5、基于 ES6 的继承
上述的几种继承方式都是通过 ES5 的 function 来创建一个函数,然后通过 new 执行方法来完成的。在真实开发中,应该是大量应用 ES6 的语法来开发,ES6 中通过 class 来创建类,然后它是怎么实现继承的?关键步骤如下:
class CHILD extends PARENT{} //相当于B.prototype._proto_ = A.prototype
ES6 中基于 class 创造出来的类不能当做普通函数执行,因此基于这一点的 call() 继承在这里就不适用了。而且 ES6 不允许重定向原型的指向,所以 B.prototype = Object.create(A. prototype); 这种方式也不能用了。但是它的原理是是寄生组合继承。通过这种方式可以将父类的私有属性或方法会变为子类的私有属性或方法,将父类共有的属性和方法变为子类的共有的属性和方法。
class A{ //ES6中基于CLASS创造出来的类不能当做普通函数执行
constructor(x){
this.x = x;
}
getX(){
console.log(this.x);
}
}
class B extends A{
constructor(y){ //子类只要继承父类,可以不写constructor,如果写第一句话必须是super()
super(200);
this.y = y;
}
getY(){
console.log(this.y);
}
}
let b1 = new B(100);
console.log(b1);