JS面向对象——多态

举例,我们在买票时会有成人票和儿童票,成人票的价格和儿童票的价格不一样,我们需要去区分成人票和儿童票,当然也有可能有其他的票型,这就很麻烦,我们就需要使用多态来处理。

概念:当不同对象完成同一个操作时,会产生不同的结果,这就是多态,简单点来说就是,基类指针/引用指向子类对象。

一、JS的重载

这个是多态的基础,JS函数不支持多态,但是事实上JS函数是无态的,支持任意长度,类型的参数列表。如果同时定义了多个同名函数,则以最后一个函数为准。

什么是函数重载

重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。这就是重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。不能只有函数返回值类型不同。

与之相似的函数重写:函数重写,也被称为覆盖,是指子类重新定义父类中有相同名称和参数的虚函数,主要在继承关系中出现。

函数重载基本条件

  • 函数名必须相同;
  • 函数参数必须不相同,可以是参数类型或者参数个数不同;
  • 函数返回值可以相同,也可以不相同。(如果函数的名称和参数完全相同,仅仅是返回值类型不同,是无法进行函数重载的。)

函数重载的应用场景

同一场景下,对于函数功能相同,仅仅参数不同的情况下进行重载,可减少开发的重复命名等情况

JS中的函数重载

JS中没有真正意义上的函数重载,因为 JS 中同一作用域下的同名函数,前者会被后者覆盖,但是可通过其他方法间接实现重载同样的效果。

JS中的函数没有签名,它的参数是由包含零到多个数组来表示的。无函数签名的话重载是不可能做到的,但是我们可以间接实现重载效果,使用 arguments 对象,是函数内部的一个类数组对象,它里面保存着调用函数时,传递给函数的所有参数。 简单的讲就是使用逻辑判断,根据参数所在数组的长度来执行不同的代码。

重载的本质就是将多个功能相近的函数合并为同一个函数

案例1:JS不支持重载:会覆盖

javascript 复制代码
function Person(){ 
    this.test1=function (a,b){ 
        window.alert('function (a,b)');  
    } 
    this.test1=function (a){ 
        window.alert('function (a)'); 
    } 
} 
var p1=new Person(); 
//js中不支持重载. 
//但是这不会报错,js会默认是最后同名一个函数,可以看做是后面的把前面的覆盖了。 
p1.test1("a","b"); 
p1.test1("a");

案例2:JS如何实现重载:通过判断参数个数

javascript 复制代码
//js怎么实现重载:通过判断参数的个数来实现重载 
function Person(){ 
    this.test1=function (){ 
        if(arguments.length==1){ 
            this.show1(arguments[0]); 
        }else if(arguments.length==2){ 
            this.show2(arguments[0],arguments[1]); 
        }else if(arguments.length==3){ 
            this.show3(arguments[0],arguments[1],arguments[2]); 
        } 
    } 
    this.show1=function(a){ 
        window.alert("show1()被调用"+a); 
    } 
    this.show2=function(a,b){ 
        window.alert("show2()被调用"+"--"+a+"--"+b); 
    } 
    function show3(a,b,c){ 
        window.alert("show3()被调用"); 
    } 
} 
var p1=new Person(); 
p1.test1("a","b"); 
p1.test1("a");

二、多态的定义

多态是同一个行为具有多个不同表现形式或形态的能力。在JAVA中,多态通过在子类中重写父类方法去实现。但是在JS中,由于JS本身是动态的,天生就支持多态。大家可以通过几个例子来理解一下。

多态实际上是不同对象作用于同一操作产生不同的效果。

多态的思想实际上是把"想做什么"和"谁去做"分开,多态的好处是什么呢?为什么要多态?我们来看看Martin Fowler 在《重构:改善既有代码的设计》里写到:多态的最根本好处在于,你不必再向对象询问"你是什么类型"而后根据得到的答案调用对象的某个行为,你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。 换句话说,多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 。

三、多态的实现

举个例子吧,国王听腻了只有鸭子为他唱歌,他决定搞一个动物合唱团。所以,大臣们搜罗了鸭,鸡,狗等动物,而且还设置了专门的选拔官员测试,选拔官员一声令下:'唱',面前的动物就发出了特有的叫声,鸭子嘎嘎嘎,小鸡咯咯咯,小狗汪汪汪......要实现这个功能,我们可以使用如下代码

javascript 复制代码
var singStart(animal){
    if (animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
function Duck(){}
function Chicken(){}
singStart(new Duck());  // 嘎嘎嘎
singStart(new Chicken());  // 咯咯咯

这种方法当然也可以实现多态,但是却违反了封装性,我们将可变的动物类型与不可变的唱歌指令耦合到了一起。如果动物类型增加,我们必须在开始唱歌方法中新增判断分支。这就好比是选拔官员发出的指令是这样的:"你是鸭子的话,唱嘎嘎嘎,是鸡的话,唱咯咯咯,是狗的话,唱汪汪汪......"这明显是不合理的。真实的情况应该是,选拔官员发出简短清晰的指令"唱"时,每种动物会场出自己独有的声音。 让我们用面向对象的思想去考虑,将不变的指令隔离开来,将可变的具体实现封装起来。

JAVA会使用类继承和重写的方式来实现,如下:

javascript 复制代码
abstract class Animal {  
    abstract void sing();  
}  
  
class Duck extends Animal {  
    public void sing() {  
        System.out.println("嘎嘎嘎");  
    }  
}  
  
class Chicken extends Animal {  
    public void sing() {  
        System.out.println("咯咯咯");  
    }  
} 
public class Test {
    public static void main(String[] args) {
        Animal duck = new Duck();
        Animal chicken = new Chicken();
        singStart(duck);  // 嘎嘎嘎
        singStart(chicken);  // 咯咯咯
  }  
            
public static void singStart(Animal a)  {
      a.sing();
}

而对于JS来讲,我们为具体的动物类型的原型定义具体的sing方法即可,如下:

javascript 复制代码
function Duck(){}
Duck.prototype.sing = function(){
    console.log('嘎嘎嘎');
}
function Chicken(){}
Chicken.protorype.sing = function(){
    console.log('咯咯咯');
}
function singStart(animal){
    animal.sing();
}
singStart(new Duck());  // 嘎嘎嘎
singStart(new Chicken());  // 咯咯咯

而且,更棒的是,JS是动态的,这里并不限制传入的参数类型是animal。大家可以看到,我们在代码中,也没有实现Animal这个类型,事实上,我们可以传入任意类型的对象,只要它正确包含一个sing方法即可。如下:

javascript 复制代码
function Person(){}
Person.protorype.sing = function(){
    console.log('哈哈哈');
}
singStart(new Person());  // 哈哈哈

可以看出来,由于JS本身的动态性,在JS中实现多态更加方便,且更加强大。再看下一个例子:

假设我们要编写一个地图应用,现在有两家可选的地图 API 提供商供我们接入自己的应用。 就像我们接入一个地图APi的调用

javascript 复制代码
var googleMap = {
    show: function () {
        console.log('开始渲染谷歌地图');
    }
};
var renderMap = function () {
    googleMap.show();
};
renderMap(); // 输出:开始渲染谷歌地图

由于某些原因,我们要换一下其他的API接口,为了让 renderMap 函数保持一定的弹性, 我们用一些条件分支来让 renderMap 函数同时支持两种地图的接口:

javascript 复制代码
var googleMap = {
    show: function () {
        console.log('开始渲染谷歌地图');
    }
};
var bdMap = {
    show: function () {
        console.log('开始渲染百度地图');
    }
};
var renderMap = function (type) {
    if (type === 'google') {
        googleMap.show();
    } else if (type === 'bd') {
        dbMap.show();
    }
};
renderMap('google'); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图

可以看到,虽然 renderMap 函数目前保持了一定的弹性,但这种弹性是很脆弱的,一旦需要替换成其他的地图接口,那无疑必须得改动 renderMap 函数,继续往里面堆砌条件分支语句。我们还是先把程序中相同的部分抽象出来,那就是显示某个地图:

javascript 复制代码
var renderMap = function( map ){
    if ( map.show instanceof Function ){
        map.show();
  }
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图 
renderMap( bdMap ); // 输出:开始渲染百度地图

现在来找找这段代码中的多态性。当我们向两种地图对象分别发出"展示地 图"的消息时,会分别调用它们的 show 方法,就会产生各自不同的执行结果。对象的多态性提示我们,"做什么"和"怎么去做"是可以分开的,即使以后增加了其他地图,renderMap 函数仍 然不需要做任何改变,如下所示:

javascript 复制代码
var sosoMap = {
    show: function(){
        console.log( '开始渲染搜搜地图' );
    }
}
renderMap( sosoMap ); // 输出:开始渲染搜搜地图
相关推荐
Fan_web12 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常13 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记4 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java4 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele4 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范