写在前面:冬天是一个非常冷的季节,注意保暖~
最近在看一本新书籍《javascript设计模式》,文中以对话的方式去描述知识点和实现方式,比较喜欢的内容和描述方式,今天分享的是“面向对象编程之继承”
继承和封装是面向对象中两个主要的特性
继承即是对怨偶对象的封装,从额创建私有属性、私有方法、特权方法、共有属性、共有方法等,对于美中属性和方法特点是不一样的
1.开发过程中功能实现方式?
面向过程:就是初期最长用的一种方式,按照自己的思维方式去实现,将功能实现按照步骤来写
面向对象:将需求抽象成一个对象,然后针对这个对象分析其特征(属性)和方法,也就是类,将共有的属性和方法封装起来
2.封装?
封装主要体现在类上,通过类来对this(函数内部自带的一个变量,用于指向当前的这个对象)的变量添加属性或者方法来实现对类添加属性或者方法。将具有相同的属性和行为的对象抽象出一个公共类~
类的组成
- 第一部是构造函数内的,供实例化对象用的
- 第二个是构造函数外的,通过点语法添加的,这是供类使用的,实例化对象是访问不到的
- 第三部分是类的原型中,实例化对象可以通过其原型链可以间接的访问到,也是供所有类实例化对象所用的
3.继承
- 类式继承
通过最常见的new关键字来直接进行继承
function SuperClass(){ this.superValue =[1,2,3,4]; } SuperClass.prototype.getSuperVlaue=function(){ return this.value; } //声明子类 function subClass(){ this.subValue =[3,45]; } //继承父类 subClass.prototype = new SuperClass(); //新创建的对象复制了父类的构造函数内的属性与方法,并将原型_proto_指向类父类的原型对象 //添加子类的共有方法 subClass.prototype.getSubValue=function(){ return this.subValue; } //使用子类 var sub1= new subClass(); var sub2 =new subClass(); console.log(sub1.superValue);//[1, 2, 3, 4] sub1.superValue.push('3333'); //sub1对superVlaue值进行更改 console.log(sub2.superValue) //[1, 2, 3, 4, "3333"]
类式继承缺点:
(1)由于子类是通过其原型prototype对父类进行的实例化,继承来父类,所以父类的共有属性如果是引用类型,就会被所有子类所共用,一个子类改变其值时,其他的子类也将会改变
(2)由于子类实现的继承是靠prototype对父类实例化的,无法向父类传递参数,因为实例化父类的时候也无法对父类的构造函数的属性进行初始化
- 构造函数继承
console.log('%c构造函数继承','color:red;font-size:16px') function SuperAnimal(type){ this.food = ['apples','banana']; this.type =type; function getType(){ console.log(this,type) } } SuperAnimal.prototype.getFood=function(){ console.log(this.food); } function SubAnimal(type){ //继承父类 SuperAnimal.call(this,type); } //实例化 var animal1 =new SubAnimal('dog'); var animal2 =new SubAnimal('cat'); animal1.food.push('reak'); console.log(animal1.food); //["apples", "banana", "reak"] console.log(animal1.type); //dog console.log(animal2.food); //["apples", "banana"] console.log(animal2.type); //cat
// animal2.getType() //Uncaught TypeError: animal2.getType is not a function // animal2.getFood(); // Uncaught TypeError: animal2.getFood is not a function
通过构造函数继承的方式,引用类型的父构造函数数据将不会是共有的,父类的原型方法不能被子类所继承,如果想要继承就得放在在构造函数中,因此创建出来的每一个实例化对象都会有一个单独的共有属性,代码复用小
通过浏览器打印animal1实例发现也没有继承父类的prototype扩展的方法
- 组合继承=类继承+构造函数继承
类继承通过子类的原型prototype对父类实例化来实现的,构造函数式继承式通过子类的构造函数作用环境中执行一次父类的构造函数来实现,
console.log('%c 组合继承','color:red;font-size:16px') function SuperStudent(id){ //值类型 this.id =id; //引用类型 this.score = [22,33,11,33]; } SuperStudent.prototype.getId=function(){ console.log(this.id) } //子类 function SubStudent(id,name){ SuperStudent.call(this,id); this.name = name; } //类式继承+子类原型继承父类 SubStudent.prototype =new SuperStudent(); SubStudent.prototype.getName = function(){ console.log(this.name) } //实例化 var student1 = new SubStudent(1,'mafeng'); console.log(student1); var student2 =new SubStudent(2,'mingy'); console.log(student1.id);//1 student1.getId();//1 student1.getName(); //mafeng console.log(student2.id); //2 student2.getId();//2 student2.getName();//mingy
通过对student1的实例化进行查看可以知道,子类继承了父类的实例化对象
融合了构造函数继承和类继承的优点 但是不免发现在编译的过程中 父类的构造函数共调用类两遍
- 原型式继承
function inhertObject(obj){ function F(){}; //过度对象的原型继承父类型 F.prototype =obj; return new F(); } var book = { name:"javascript", type:['technology','finall'], } var newBook = inhertObject(book); newBook.name = 'java'; newBook.type.push("thernal"); //改变引用类型的值 var otherBook = inhertObject(book); otherBook.name = 'lihai'; otherBook.type.push("yyyy"); //改变引用类型的值 console.log(newBook.name); //java console.log(newBook.type); //["technology", "finall", "thernal", "yyyy"] console.log(otherBook.name); //lihai console.log(otherBook.type); //["technology", "finall", "thernal", "yyyy"] console.log(book.name); //javascript console.log(book.type); //["technology", "finall", "thernal", "yyyy"]
打印输出后发现:值类型被赋值,但是引用类型被共用,
- 寄生式继承
其实就是对原型类型的第二次封装
//对原型继承的第二次封装 var student={ name:'uuu', score:[3,4,5,6], } function createdStudent(obj){ var o = new inhertObject(obj); o.getName = function(name){ console.log(name); } return o } var newStudent = createdStudent(student); newStudent.name ='ma'; newStudent.score=[4,5,6,4,3]; // console.log(newStudent.name); //ma console.log(newStudent.score);// [4,5,6,4,3] newStudent.getName('ooo'); //ooo console.log(student.name); // uuu console.log(student.score);//[3,4,5,6]
从二者构造出的对象中查看,原型继承的对象,其继承的属性值只有值类型的,而引用类型的都是共有的,而寄生式继承的继承了父类的值类型和引用类型的数据
- 寄生组合式继承
这种继承方式依托于原型继承,原型继承又和类式继承相向
console.log('%c 原型类继承','color:red;font-size:16px'); function inheritPrototype(subClass,superClass){ var p = inhertObject(SuperClass);//复制一份父本保存在变量中 p.constructor = subClass;//修改因为重写子类原型而导致的子类constructor属性修改 使刚复制的父类的contructor指向子类 subClass.prototype =p //设置子类的原型 } //定义父类 function SuperMonther(){ this.name = 'Mom'; this.type=['beautifuly','honest']; } SuperMonther.prototype.getName=function(){ console.log(this.name) } function SubMonther(name,age){ SuperMonther.call(this,name); this.age =age; } inheritPrototype(SubMonther,SuperMonther); SubMonther.prototype.getAge=function(){ console.log(this.age); } var mom1 = new SubMonther('aa',33); var mom2 =new SubMonther('yy',44); mom1.type.push('uuuu'); console.log(this.mom1.type); //["beautifuly", "honest", "uuuu"] console.log(this.mom2.type); //["beautifuly", "honest"]
通过寄生的方式重新继承父类的原型,
- 总结:
- 类式继承:通过原型链继承的方式
- 构造函数继承:通过构造函数继承的方式
- 组合继承=类式继承+构造函数继承 但是由于类式继承的过程中会实例化父类,如果父类的构造函数较为复杂,这样就不是一种好的方式
- 寄生式继承 通过一个函数内的过度对象来实现继承并返回新的对象的方式
- 继承组合式 在寄生式的基础上,融合构造函数中的优点并去除缺点