面向对象编程就是将你的需求抽象成一个对象,然后对这个对象进行分析,为其添加对应的属性与方法,我们将这个对象称之为类。面向对象有三大特性,封装、继承和多态。
封装
通过构造函数添加
- 通过this定义的属性和方法,我们实例化对象的时候都会重新复制一份,会造成内存的浪费。
function Person(name){
this.name = name;
this.sayName = function () {
console.log(this.name);
}
}
var person = new Person('tom');
通过构造函数+原型prototype
- 对于那些不变的属性和方法,我们可以直接将其添加在类的prototype 对象上,提高了运行效率。
function Person(name){
this.name=name;
}
//写法一:
Person.prototype.sayName=function(){
console.log(this.name);
}
//写法二:
Person.prototype={
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
var person = new Person('tom');
继承
ES5继承
类式继承
就是使用原型的方式,将方法添加在父类的原型上,然后子类的原型是父类的一个实例化对象。
//声明父类
function Parent(){
this.parent='parent';
}
//为父类添加原型方法
Parent.prototype.getParent=function(){
return this.parent;
}
//声明子类
function Son () {
this.son = 'son';
}
//继承父类
Son.prototype = new Parent();
//为子类添加原型方法
Son.prototype.getSon = function () {
return this.son;
}
不足之处:
- 子类改变会父类,从而影响其他子类。(由于子类通过原型prototype对父类实例化,继承父类,只要父类的公有属性中有引用类型,就会在子类中被所有实例共用,如果其中一个子类更改了父类构造函数中的引用类型的属性值,会直接影响到其他子类。)
- 无法传递参数,进行初始化操作。(由于子类是通过原型prototype对父类实例化的,所以在创建父类的时间,无法给父类传递参数,也就无法在实例化父类的时候对父类构造函数内部的属性进行初始化操作。)
构造函数继承
利用call方法直接改变this的指向,但是会造成内存浪费的问题。
//声明父类
function Parent(parent) {
this.parent=parent
}
//声明父类原型方法
Parent.prototype.show = function () {
console.log(this.parent)
}
//声明子类
function Son(son) {
Parent.call(this,son)
}
不足之处:
- 父类的原型方法不会得到继承。(这种继承方式没有涉及到原型prototype,所以父类的原型方法不会得到继承,而如果要想被子类继承,就必须要放到构造函数中,这样创建出来的每个实例都会单独拥有一份,不能共用。)
组合式继承
即避免了内存浪费,又使得每个实例化的子类互不影响。
//声明父类
function Parent (value) {
this.value = value;
}
//为父类添加原型方法
Parent.prototype.show = function () {
console.log(this.value);
}
//声明子类
function Son (value) {
// 构造函数式继承父类 value 属性
Parent.call(this,value)
}
//类式继承
Son.prototype = new Parent();
不足之处:
- 父类的构造函数执行了两遍。(我们在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一父类的构造函数,那么父类的构造函数执行了两遍。)
寄生组合式继承
//原型式继承
//原型式继承其实就是类式继承的封装,实现的功能是返回一个实例,改实例的原型继承了传入的o对象
function inheritObject(o) {
//声明一个过渡函数对象
function F() {}
//过渡对象的原型继承父对象
F.prototype = o;
//返回一个过渡对象的实例,该实例的原型继承了父对象
return new F();
}
//寄生式继承
//寄生式继承就是对原型继承的第二次封装,使得子类的原型等于父类的原型。并且在第二次封装的过程中对继承的对象进行了扩展
function inheritPrototype(son, parent){
//复制一份父类的原型保存在变量中,使得p的原型等于父类的原型
var p = inheritObject(parent.prototype);
//修正因为重写子类原型导致子类constructor属性被修改
p.constructor = son;
//设置子类的原型
son.prototype = p;
}
//定义父类
var Parent = function (name) {
this.name = name;
};
//定义父类原型方法
Parent.prototype.getName = function () {
console.log(this.name)
};
//定义子类
var Son = function (name) {
Parent.call(this,name)
}
inheritPrototype(Son,Parent);
ES6继承
class Animal {
//构造函数,里面写上对象的属性
constructor(props) {
this.name = props.name || 'Unknown';
}
//方法写在后面
eat() {//父类共有的方法
console.log(this.name + " will eat pests.");
}
}
//class继承
class Bird extends Animal {
//构造函数
constructor(props,myAttribute){
//props是继承过来的属性,myAttribute是自己的属性
//调用实现父类的构造函数
super(props)//相当于获得父类的this指向
this.type = props.type || "Unknown";//父类的属性,也可写在父类中
this.attr = myAttribute;//自己的私有属性
}
}