类和原型的设计模式分别是怎样的,如何应用

Admin 2022-09-06 群英技术资讯 266 次浏览

这篇文章主要讲解了“类和原型的设计模式分别是怎样的,如何应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“类和原型的设计模式分别是怎样的,如何应用”吧!

JavaScript 技能持有者一定有问过这个问题:

JavaScript 是面向对象语言吗?

你期望得到的答案应该为:“是” 或 “不是”。

但是可惜,你得不到这样简单的答案!

你大概了解一通之后,你会被告知:

JavaScript 不是纯粹的面向对象语言!

wtf!为什么是不纯粹?能不能纯粹一点?!我们喜欢纯粹,不喜欢混沌!

......

实际上,死扣定义真的没太必要。定义背后的故事才是最重要的!

看完本篇,你就会明白这种“混沌”是什么、来自何处,以及去往何方!!

撰文不易,多多鼓励。点赞再看,养成习惯。

“类”设计模式

妇孺皆知,面向对象三大特性:【封装】、【继承】、【多态】。

  • 所谓封装,即把客观事物封装成抽象的类。
  • 所谓继承,即子类继承父类的能力。
  • 所谓多态,即子类可以用更特殊的行为重写所继承父类的通用行为。

其中,“类”的概念最最关键!【类】描述了一种代码的组织结构形式,它是软件中对真实世界中问题领域的建模方法。

举个例子:

就好比我们现实中修房子,你要修一个“写字楼”、或者一个“居民楼”、或者一个“商场”,你就得分别找到修“写字楼”、“居民楼”、“商场”的【设计蓝图】。

但是设计蓝图只是一个建筑计划,并不是真正的建筑。要想在真实世界实现这个建筑,就得由建筑工人将设计蓝图的各类特性(比如长宽高、功能)【复制】到现实世界来。

这里的【设计蓝图】就是【类】,【复制】的过程就是【实例化】,【实例】就是【对象】。

类的内部通常有一个同名的构造方法,我们设想下,它的伪代码就可能是这样的:

class Mall { // “商场”类
    Mall( num ){ // 同名构造方法
        garage = num // 地下车库数量
    }
    shop( goods ) { // 买东西
       output( "We can buy: ", goods )
    }
}
// 构造函数大多需要用 new 来调,这样语言引擎才知道你想要构造一个新的类实例。
vanke = new Mall(1) // vanke 有 1 个地下车库
vanke.shop("KFC") // "We can buy: KFC"

java 是典型的面向对象语言。基于“类”,我们再通过以下一段 java 代码来看看对继承和多态的理解。

public abstract class Animal{ // 抽象类
     abstract void sound();
}
public class Chicken extends Animal{ // 继承
    public void sound(){
      sound("咯咯咯");
    }
}
public class Duck extends Animal{
    public void sound(){
      sound("嘎嘎嘎");
    }
}
public static void main(String args[]){
  Aninal chicken = new Chicken();
  Animal duck = new Duck();
  chicken.sound(); //咯咯咯
  duck.sound();  //嘎嘎嘎
}

鸡和鸭都属于动物分类,都可以发出叫声(继承),但是它们却可以发出不同的叫声(多态),很容易理解。

继承可以使子类获得父类的全部功能; 多态可以使程序有良好的扩展;

回想下:在 JS 中,我们可能会怎样写:

var Duck = function () {};
var Chicken = function () {};
var makeSound = function ( animal ) {
    if( animal instanceof Duck){
        console.log("嘎嘎嘎");    
    }else if( animal instanceof Chicken){
        console.log("咯咯咯");    
    }
};
makeSound(new Duck());
makeSound(new Chicken());

这里既没用到继承,也没用到多态。这样【写判断】是代码“不清爽”的罪魁祸首!

此处留一个疑问,如果不用判断,还可以怎么写?

在 vue2 中,我们可能会这么写:

export default {
  data() {
      return {
      },
      mounted(){
          this.Chicken()
          this.Duck()
      },
      methods:{
          funtion AnimalSound(sound){
              console.log("叫声:" + sound)
          },
          funtion Chicken(){
              this.AnimalSound("咯咯咯")
          },
          funtion Duck(){
              this.AnimalSound("嘎嘎嘎")
          }
      }
  }

像这种函数嵌套调用是很常见的。没有看到继承,也没有看到多态,甚至都没有看到最根本的“类”?!

(实际上,每个函数都是一个 Function 对象。按照最开始定义所述,对象是类的实例,所以也是能在函数中看到“类”的!)

在 JavaScript 中,函数成了第一等公民! 函数似乎什么都能做!它可以返回一个对象,可以赋值给一个变量,可以作为数组项,可以作为对象的一个属性......

但这明显不是“类的设计模式”吧!

“类的设计模式” 意味着对【设计蓝图】的【复制】,在 JS 各种函数调用的场景下基本看不到它的痕迹。

“原型”设计模式

其实,众所周知,JS 也是能做到【继承】和【多态】的!只不过它不是通过类复制的方式,而是通过原型链委托的方式!

一图看懂原型链?

看不懂?没关系,记住这两句话再来看:

  • 一个对象的显示原型的构造函数指向对象本身(很熟悉有没有?在本文哪里见过?)
  • 一个对象的隐式原型指向构造这个对象的函数的显示原型。

原来,JS 不是通过在类里面写同名构造函数的方式来进一步实现的实例化,它的构造函数在原型上!这种更加奇特的代码服用机制有异于经典类的代码复用体系。

这里再附一个经典问题?JS new 操作会发生什么?

会是像类那样进行复制吗?

答案是否定的!

JS 访问一个对象的属性或方法的时候,先在对象本身中查找,如果找不到,则到原型中查找,如果还是找不到,则进一步在原型的原型中查找,一直到原型链的最末端。复制不是它所做的,这种查找的方式才是!对象之间的关系更像是一种委托关系,就像找东西,你在我这找不到?就到有委托关系的其它人那里找找看,再找不到,就到委托委托关系的人那里找......直至尽头,最后还找不到,指向 null。

所以:JavaScript 和面向对象的语言不同,它并没有类来作为对象的抽象模式或者设计蓝图。JavaScript 中只有对象,对象直接定义自己的行为。对象之间的关系是委托关系,这是一种极其强大的设计模式。在你的脑海中对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的!

不过你也可以通过这种委托的关系来模拟经典的面向对象体系:类、继承、多态。但“类”设计模式只是一种可选的设计模式,你可以模拟,也可以不模拟!

现实是 ES6 class 给我们模拟了:

class Widget { 
    constructor(width,height) { 
        this.width = width || 50; 
        this.height = height || 50; 
        this.$elem = null; 
    } 
    render($where){ 
        if (this.$elem) { 
            this.$elem.css( { 
                width: this.width + "px", 
                height: this.height + "px" 
            }).appendTo( $where ); 
        } 
    } 
} 
class Button extends Widget { 
    constructor(width,height,label) { 
        super( width, height ); 
        this.label = label || "Default"; 
        this.$elem = $( "<button>" ).text( this.label ); 
    } 
    render($where) { 
        super.render( $where ); 
        this.$elem.click( this.onClick.bind( this ) ); 
    } 
    onClick(evt) { 
        console.log( "Button '" + this.label + "' clicked!" ); 
    } 
}

看起来,非常不错,很清晰!

没有 .prototype 显示原型复杂的写法,也无需设置 .proto 隐式原型。还似乎用 extends 、super 实现了继承和多态。

然而,这只是语法糖的陷阱!JS 没有类,没有复制,它的机制是“委托”。

class 并不会像传统面向类的语言一样在申明时作静态复制的行为,如果你有意或者无意修改了父类,那子类也会收到影响。

举例:

class C { 
 constructor() { 
    this.num = Math.random(); 
 } 
 rand() { 
    console.log( "Random: " + this.num ); 
 } 
} 
var c1 = new C(); 
c1.rand(); // "Random: 0.4324299..."
C.prototype.rand = function() { 
    console.log( "Random: " + Math.round( this.num * 1000 )); 
}; 
var c2 = new C(); 
c2.rand(); // "Random: 867"
c1.rand(); // "Random: 432" ——噢!

ES6 class 混淆了“类设计模式”和“原型设计模式&rdquo;。它最大的问题在于,它的语 法有时会让你认为,定义了一个 class 后,它就变成了一个(未来会被实例化的)东西的 静态定义。你会彻底忽略 Class 是一个对象,是一个具体的可以直接交互的东西。当然,它还有其它细节问题,比如属性覆盖方法、super 绑定的问题,有兴趣自行了解。

总地来说,ES6 的 class 想伪装成一种很好的语法问题的解决方案,但是实际上却让问题更难解决而且让 JavaScript 更加难以理解。 —— 《你不知道的 JavaScript》

小结

  • “类设计模式”的构造函数挂在同名的类里面,类的继承意味着复制,多态意味着复制 + 自定义。
  • “原型设计模式”的构造函数挂在原型上,原型的查找是一种自下而上的委托关系。
  • “类设计模式”的类定义之后就不支持修改。
  • “原型设计模式”讲究的是一种动态性,任何对象的定义都可以修改,这和 JavaScript 作为脚本语言所需的动态十分契合!

你可以用“原型设计模式”来模拟“类设计模式”,但是这大概率是得不偿失的。

最后,如果再被问道:JavaScript 是面向对象语言吗?

如果这篇文章看懂了,就可以围绕:“类设计模式”和“原型设计模式”来吹了。

如果本文没有看懂,就把下面的标答背下来吧......

参考

命名函数表达式探秘

函数式和面向对象编程有什么区别?

tutorials/js.mp

JavaScript 轻量级函数式编程

你不知道的JavaScript


“类和原型的设计模式分别是怎样的,如何应用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业技术相关的知识可以关注群英网络网站,小编每天都会为大家更新不同的知识。 群英智防CDN,智能加速解决方案
标签: JavaScript

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

猜你喜欢

成为群英会员,开启智能安全云计算之旅

立即注册
专业资深工程师驻守
7X24小时快速响应
一站式无忧技术支持
免费备案服务
免费拨打  400-678-4567
免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
在线客服
微信公众号
返回顶部
返回顶部 返回顶部
在线客服
在线客服