本文继续讲解JavaScript的面向对象程序设计。继承是面向对象语言中的一个基本概念,面向对象语言支持两种继承实现方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。但是在JavaScript中函数时没有签名的,所以无法实现接口继承。JavaScript支持实现继承,而且其实现继承主要是通过原型链继承的。

  • 原型链

  JavaScript中有原型链的概念,并将原型链作为实现继承的主要方法。基本实现思想是让一个函数的原型继承另外一个函数的原型的属性和方法。每一个函数都有一个原型对象,原型对象包含一个指向构造函数的指针,实例包含一个指向原型对象的指针。原型链的概念就是,一个原型对象指向另一个函数的原型,同样另一个函数的原型又指向其他函数的原型,层层递进,就构成了原型链。

  

 function SuperType(){

                 }
function SubType(){ }
SubType.prototype=new SuperType();
function ExtendType(){ }
ExtendType.prototype = new SubType();

  上面的代码展示了原型链的概念,ExtendType的原型指向了SubType实例,SubType的原型指向了SuperType实例,SuperType的原型指向了Object的原型。这样就形成了一个原型链。原型链本质上是扩展了前面介绍的原型搜素机制。当访问实例的属性或者方法时,首先搜索实例的属性或者方法,再搜索实例的原型。通过原型链,可以一直向上搜索,直至搜索到Object的原型。

  前面也简单地介绍了确认原型的方法。

 var extendtype = new ExtendType();
console.log(extendtype instanceof Object);
console.log(extendtype instanceof SubType);
console.log(extendtype instanceof SuperType);

  上面的代码中2、3、4行都输出true,这说明extendtype中能找到函数的原型。也可以通过另外一种方法实现原型的判断,就是通过原型的isPrototypeOf方法。

 console.log(Object.prototype.isPrototypeOf(extendtype));
console.log(SubType.prototype.isPrototypeOf(extendtype));
console.log(SuperType.prototype.isPrototypeOf(extendtype));

  上面的代码通过isPrototypeOf方法来判断实例的类型。同样,输出都是true。

  通过原型链在JavaScript中实现的继承依然存在一定的问题。原型链会将实例中的所有属性都共享,但是我们在构造函数中定义属性,二不在原型中定义属性就是为了不共享属性

  

 function Super(){
this.colors=["green"];
}
function Sub(){ }
Sub.prototype= new Super();
var sub = new Sub();
sub.colors.push("red");
console.log(sub.colors.toString());//green,red
var sub2 = new Sub();
console.log(sub2.colors.toString());//green.red

  上面的代码定义两个对象super和sub,sub的原型继承了super的实例。我们创建了sub的两个实例,对其中的一个实例colors的属性添加了一个元素,但是我们发现两个实例的属性都改变了。因为该两个实例的colors属性都指向super中的属性。

  • 借用构造函数

  借用构造函数的思想就是在子类型的构造函数中调用父类的构造函数,可以通过apply或者call调用父类构造函数。

 function Super(){
this.colors=["green"];
}
function Sub(){
Super.call(this);
}
var sub = new Sub();
sub.colors.push("red");
console.log(sub.colors.toString());//green,red
var sub2 = new Sub();
console.log(sub2.colors.toString());//green

  上面的代码在子类的函数中调用了父类的构造函数,通过call。同时,我们实例化了两个子类对象,发现sub的操作并没有影响sub2的结果。每次实例化都会调用父类的构造函数,这样每个sub都有自己的colors属性。

  同时,通过借用构造函数,我们还能传递参数。

 function Super(name){
this.colors=["green"];
this.name=name;
}
function Sub(name){
Super.call(this,name);
}
var sub = new Sub("hehe");
sub.colors.push("red");
console.log(sub.name);//hehe
var sub2 = new Sub("haha");
console.log(sub2.name);//haha

  上面的代码,我们通过构造函数传递了参数,并通过call方法传递参数给父类的构造函数。借用构造函数和构造函数模式创建对象拥有同样的问题,方法和属性都在构造函数中定义,因为函数无法复用。也无法判断函数的类型。

  • 组合式继承

  组合继承是指将原型链和构造函数的技术组合在一起。它的思路是通过原型链实现属性和方法的继续,通过借用构造函数模式实现实例属性的继承。这样在原型链上实现方法,保证函数的复用,同时又保证每个实例有自己的属性。

  

 function Super(name){
this.name=name;
}
Super.prototype.getName=function(){
return this.name;
}
function Sub(name,age){
Super.call(this,name);
this.age=age;
}
Sub.prototype= new Super();
var sub = new Sub("haha",18);
console.log(sub.getName());//haha
console.log(sub.age);//
var sub2 = new Sub("hehe",19);
console.log(sub2.getName());//hehe
console.log(sub2.age);//

  上面的代码中Super定义了一个属性name和一个原型方法getName,sub定义了一个实例属性age。sub继承了super的实例。也就是sub用super的原型方法,同时能够调用super的实例属性。在后面定义了两个sub实例,他们是不同的实例,拥有不同的实例属性,但是他们共享了原型方法。

  组合继承避免了原型链和构造函数的缺陷,是一种常用的继承实现方法。

  • 原型式继承

  克罗克福德提出了原型式继承的方法。他的方法是借助原型基于已有的对象创建新的对象,同时还不必因此创建自定义类型。

  

 function create(o){
function F(){};
F.prototype=o;
return new F();
}

  在create函数内部,先创建了零时行的函数F,并将F的原型指向参数o,参数o是另一个对象的原型,最后返回F的实例,并且该实例继承了传递进行来的对象的原型。

  

 function create(o){
function F(){};
F.prototype=o;
return new F();
}
var Person={
"name":"haha",
getName:function(){
return this.name;
}
}
var oneperson=create(Person.prototype);
oneperson.name="hehe";
console.log(oneperson.name);//hehe
var twoperson = create(Person.prototype);
twoperson.name="jack";
console.log(twoperson.name);//jack

  上面的代码,基于create函数创建了两个对象,这两个对象继承了Person。这就意味着Person中的属性和方法,oneperson中同样拥有。ECMAScript5中定义了新的方法Object.create()方法,该方法有两个参数,一个参数是一个对象原型,另一个参数是需要生成的新属性。与上面的方法类似。

  • 寄生式继承  

  寄生式继承与寄生式函数的工厂模式类似,也是创建一个用于封装继承过程的函数。在函数内部以一定的方式增强对象,最后返回对象。

  

 function create(o){
function F(){};
F.prototype=o;
return new F();
}
function createPerson(o){
var f=create(o);
o.sayHi=function(){
console.log("hi");
}
return o;
}
var Person={
name:"haa",
age:"8"
}
var one=createPerson(Person);

  上面的one不仅继承了person的属性,同时还拥有增强属性sayHi。

  • 寄生组合式继承

  前面说过最常用的继承方式是组合式继承,但是组合继承,不论什么情况,都要父类构造函数两次。第一次是在子类继承父类的实例时候,第二次是子类实例化过程中。

  寄生组合式继承,通过借用构造函数来继承属性,通过原型链来继承方法。使用寄生式继承来继承父类的原型,不必通过实例化来继承父类,这样减少了调用父类构造函数的次数,只用调用一次。

  

     function create(o){
function F(){};
F.prototype=o;
return new F();
}
function SuperType(name){
this.name=name;
}
function Sub(name,age){
SuperType.call(this,name);
this.age=age;
}
Sub.prototype=create(SuperType.prototype);
Sub.prototype.getName=function(){
return this.name;
}
var sub = new Sub("haha",19);
console.log(sub.getName());//
var sub2 = new Sub("hehe",18);
console.log(sub2.getName());//hehe

  

浅谈JavaScript的面向对象程序设计(四)的更多相关文章

  1. 浅谈JavaScript的面向对象程序设计(三)

    前面已经对JavaScript的面向对象程序设计作了简单的介绍,包括了对象的属性.对象的工厂模式.构造函数和原型等.通过介绍,这些创建对象的方法依然有不少优化和改进的地方. 组合使用构造函数模式和原型 ...

  2. 浅谈JavaScript的面向对象程序设计(一)

    面向对象的语言有一个标志,他们都有类的概念,通过类可以创建多个具有相同属性和方法的对象.但是JavaScript中没有类的概念,因此JavaScript与其他的面向对象语言还是有一定区别的.JavaS ...

  3. 浅谈JavaScript的面向对象程序设计(二)

    前面介绍通过Object构造函数或者字面量创建单个对象,但是通过这个的方法创建对象有明显的缺点:调用同一个接口创建多个实例,会产生大量的重复代码.怎么样解决? 工厂模式 工厂模式是软件工程领域经常使用 ...

  4. 浅谈JavaScript的面向对象和它的封装、继承、多态

    写在前面 既然是浅谈,就不会从原理上深度分析,只是帮助我们更好地理解... 面向对象与面向过程 面向对象和面向过程是两种不同的编程思想,刚开始接触编程的时候,我们大都是从面向过程起步的,毕竟像我一样, ...

  5. 浅谈javascript的面向对象思想

    面向对象的三大基本特性 封装(把相关的信息(无论数据或方法)存储在对象中的能力) 继承(由另一个类(或多个类)得来类的属性和方法的能力) 多态(一个对象在不同情况下的多种形态) 定义类或对象 第一种: ...

  6. 浅谈JavaScript浮点数及其运算

    原文:浅谈JavaScript浮点数及其运算     JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...

  7. 浅谈JavaScript中的正则表达式(适用初学者观看)

    浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...

  8. [转载]浅谈JavaScript函数重载

     原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...

  9. 浅谈javascript函数节流

    浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...

随机推荐

  1. Android开发中有用工具之--Log工具类

    在开发的过程中.我们常常会使用Log来输出日志,帮助我们来调试程序 可是有时候并不能全然满足我们的须要 ,比方我想知道这个日志信息是来自于哪一个包 哪一个类 所以我们封装一个这个Log类.方便我们的使 ...

  2. node.js平台下,cropper.js实现图片裁剪预览并转换为base64发送至服务端。

    一 .准备工作 1.首先需要先下载cropper,常规使用npm,进入项目路径后执行以下命令: npm install cropper 2. cropper基于jquery,在此不要忘记引入jq,同时 ...

  3. mssql查询过去一段时间数据库中执行过的语句及执行效率

    SELECT TOP 1000 ST.text AS '执行的SQL语句', QS.execution_count AS '执行次数', QS.total_elapsed_time AS '耗时', ...

  4. itextpdf添加非自带字体(例如微软雅黑)

    找到需要的字体,例如 在windows系统中找到需要字体,本例使用微软雅黑,使用C:\\Windows\\Fonts\\msyh.ttf. 代码如下: /** * 创建pdf,使用微软雅黑字体 * * ...

  5. 《程序设计实践》【PDF】下载

    <程序设计实践>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196319 内容简介 本书从排错.测试.性能.可移植性.设计.界面. ...

  6. 将java项目打包成docker镜像

    简介:将jar打包成镜像好说,毕竟jar包长的都是一样的,但是我们只是写了一个普通的java项目,我也不方便封装成jar包什么的,但是我们也想打包docker image怎么办呢,我们可以用编译后的j ...

  7. 解决model 里 NSInteger类型

    #import "CJGWCListModel.h" @implementation CJGWCListModel - (NSInteger)goods_number{ if (_ ...

  8. 一次对象过大引起的gc性能问题的分析与定位

    现象:一个接口在4C的机器上跑最大只有7TPS,CPU使用率就已经90%多. 定位: 1.  使用top命令查看CPU使用情况,找到进程号 2.  使用top -H -pid命令,查看进程信息,看到有 ...

  9. 【十九】require和include的区别

    1.require是一个函数,include是一个关键字 2.require是无返回值,include有返回值 3.include()会产生一个警告,而require()则导致一个致命的错误(出现错误 ...

  10. Oracle数据库(一)概述、基础与简单操作

    数据库: 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库. 数据库分类: 关系型数据库 非关系型数据库 数据库 类型 特性 优点 缺点 关系型数据库 SQLite.Oracle. ...