(最全面的)JavaScript 设计原则指南


“如果您使用的解决方案不是唯一的解决方案来
解决本身就是一个独特挑战的挑战,那么
您就利用了 JavaScript设计模式的力量”


软件语言已经存在了几十年。至此,软件语言的生命周期已经被很好地理解了。在任何语言的生命周期中,许多此类可重用解决方案都是由给定语言社区中的大量开发人员制作和测试的。通过复合开发人员经验的力量,创建了称为设计模式的解决方案,并使之对大众有用。这些解决方案支持创建优化方法,以基于更少的设计模式来解决大量问题类型。


“设计模式是软件设计中常见问题的可重用解决方案”

我们从设计模式中获得什么好处?


  • 行之有效的解决方案:许多软件开发人员都使用设计模式。被许多开发人员成功使用,我们在实现给定的设计模式时获得了更大的成功确定性。当设计模式成为主流时,您可以放心地知道它们已经被多次修改和重构。经过验证的解决方案通常是最优的,考虑到边缘情况,并且可用于各种用例。


  • 易于重用:当解决方案可重用时,可以对其进行修改以解决多个特定问题。设计模式记录了一个可重用的解决方案,该解决方案与任何特定问题无关,而是与设计模式帮助克服的一系列挑战相关。


  • 富有表现力:很多时候,设计模式可以以简明扼要的方式解释大型解决方案。


  • 降低重构代码的需求:大多数设计模式都会考虑代码依赖性、现有和未来的依赖性。例如,开放封闭设计原则 - 无需重构已编写的代码。相反,您创建一个新类(用其他非 js 语言实现接口)并添加代码。您可以限制使用设计模式重构代码的需要。


  • 简化沟通:由熟悉设计模式的软件工程师组成的团队能够通过其代码库语言更轻松地进行内部沟通。他们还能够与外部沟通潜在的解决方案、值得注意的未来问题以及所有架构设计。设计模式简化了沟通。


  • 精简代码库站点:由于其优雅、高效且经过深思熟虑的方法,设计模式通常需要更少的代码,最终只需一个团队的代码库。

“在深入研究之前,让我们简要回顾一下 JavaScript 的历史,以更好地理解许多现代设计模式的构建背景。”


JavaScript 历史(简短)课程


在 Web 开发领域,JavaScript 是当今最流行的编程语言之一。

一开始,JavaScript 并没有打算成为这种令人印象深刻的世界范围内接受的语言,具有反应式前端、模块化代码存储库以及可通过 npm 安装的数百万个包。

甚至还不太接近,一开始 JavaScript 更像是“粘合剂”,允许您将 HTML 元素的各种显示粘在一起。最初被称为客户端脚本语言,世界上第一个网络浏览器之一 - Netscape Navigator,利用 JavaScript 显示静态 HTML。

当然,这导致了我们现在所说的浏览器之战

浏览器是新的、热门的、爆炸式的——它们是科技行业的下一个重大事件。Mozilla(以前称为 Netscape Communications)、Microsoft Explorer 以及最终的 Chrome 等大玩家都在争夺浏览器的荣耀。

作为这场浏览器战争中每个浏览器背后的驱动力,大公司们正在研究、开发和创建新的和改进的方法来实现自己的客户端脚本语言。

  • Netscape: JavaScript(实际上,Brendan Eich 创建了最初的 JS)

  • 微软: JScript(有人知道那是什么吗?)

作为那个时代的开发人员,我想象到了巨大的挫败感。这些实现有很大不同。开发并不针对所有浏览器,而是针对个别浏览器。

正如我所想象的那样,愤怒的软件工程师拿着干草叉和火把聚集在一起,其数量可与我们世界历史上最恶意的战争的军队相媲美。随着愤怒的开发人员的崛起,我们只有一个需求 - 为所有浏览器选择一种 MO**** FU***** 语言。

(在我的想象中,我们的开发者祖先在这个时代更像是伟大的维京战士,渴望战争,并在争取简化和荣耀的战斗中面临死亡。我最近也在 Hulu 上观看维京人 - 这可能让我的想象力猖獗。 .)

于是,ECMAScript诞生了。

你问的 ECMAScript 是什么?自由、包容性和非疯狂标准化的呼声。

ECMAScript 是所有现代浏览器都试图支持的标准化脚本语言规范。如果您想进行传统的人类语言类比,它确实有许多更像不同方言的实现。

我喜欢将 ECMAScript 视为所有脚本语言和 JavaScript 的始祖,因为它是英雄的儿子 - 英雄中的英雄,战胜一切困难的斗士,因为它太棒了而赢得了所有女士的青睐(但说实话,JavaScript 是各位工程师最常用的软件语言之一)

JavaScript 是源自 ECMAScript 的最流行的方言。

由于 ECMAScript 进入了现实世界,它为软件工程社区做了一些重要的事情。它标准化了维基百科上列出的许多重要内容。

浏览器对 ECMAScript 版本 6 (ES6) 及更高版本的支持仍然不完整,必须转换为 ES5 才能得到完全支持。


什么是 JavaScript(除了 Awesome)?


让我们介绍一些非常重要的 JavaScript 语言特性。在深入研究本文中讨论的 JS 设计模式之前,您需要对 JavaScript 的这些属性有一些了解和背景。


读这篇文章,这里有一个问题要问你...


“什么是 JavaScript?”


一种可能的答案可能是:

“JavaScript 是一种轻量级、解释性、面向对象的编程语言,具有一流的功能,最常被称为网页脚本语言。”

嗯……

呵呵?!

基本上,这个非常复杂的引用是由一个或多个比我聪明得多的人写的,意思是这样的:

  • JS 内存占用低

  • JS很容易实现

  • JS简单易学

  • JS 的语法与其他流行语言(例如 C++ 和 Java)类似

  • JS是一种脚本语言

    • 这意味着它的代码是解释而不是编译的

  • JS有程序支持

  • JS具有面向对象的支持

  • JS 具有函数式编程风格支持

  • JS 对于开发者来说很灵活!(直到S***坏了,然后才痛)

这些是 JavaScript 的属性或特征,您可以在许多其他流行的软件语言中找到它们 - 但是,正如我们许多人都清楚的那样,JavaScript 很时髦并且有自己的节奏。


JavaScript 支持一流函数


一等函数非常强大,但一开始也有些难以掌握。据说一种编程语言具有一流函数只是意味着该语言中的函数被像任何其他变量一样对待。

  • 第一类函数可以:作为参数传递给其他函数

/* We pass a function as the argument */function action (name, payload, callback_function) {
    let context = { name, payload };
    callback_function(context);};action('log-alert', 'hello world', function (context) {
   console.log(
     "The action context: ", context, 
     "The action name: ", context.name, 
     "The action payload: ", context.payload
   );})
  • 第一类函数可以是:由另一个函数返回

function sayHello() {
   return function() {
      console.log("Hello!");
   }}
  • 第一类函数可以:作为值分配给变量

const foo = function() {
   console.log("foobar");}// Invoke it using the variablefoo();

基于 JavaScript 原型


JavaScript 是面向对象的——因此它支持对象。现在,大多数不熟悉 JavaScript 的人会从逻辑上考虑对象,然后是类,甚至可能是继承。

JavaScript 有一些不同的方法......

JavaScript 的普通形式不支持类...相反,JavaScript 使用所谓的基于原型基于实例的 继承

ES6中,正式引入了Class这个术语。在撰写本文时,所有浏览器都完全支持 ES6,因此我们可以使用Class关键字 - 但它在像 JS 这样的原型语言中的工作方式仍然不同。

基于原型的编程

  • 面向对象编程风格

    行为重用(称为继承)是通过作为原型的委托重用现有对象的过程来执行的

  • 我们将在本文的设计模式部分进一步深入探讨。理解原型对于 JS 来说非常重要,但我们暂时不要添加太多细节。


JavaScript 事件循环


您听说过回调函数吗?如果您习惯在 JavaScript 世界中工作,我相信您已经习惯了。

回调函数是作为参数发送到另一个函数的函数(由于函数是一等公民,因此可以接受)。作为参数传递的函数将在事件触发后调用。通常,这用于订阅事件。

示例:鼠标右键单击事件触发要调用的函数 - 因此是回调函数

  • 事件有一个附加的侦听器。

  • 每次该事件触发时(否则该事件将丢失)

  • 消息被发送到消息队列

    • 该消息队列(FIFO - 先进先出)同步处理。

这个过程就是我们所知的 JavaScript事件循环。

每个队列消息

  • 具有关联功能

一旦队列消息出队

  • 运行时在处理任何其他消息之前完全执行该函数。

如果一个函数包含其他函数调用

  • 它们都是在处理队列中的新消息之前执行的。

这称为运行到完成

while (queue.waitForMessage()) {
    queue.processNextMessage();}

队列.waitForMessage()

  • 同步等待新消息。

    • 加工完成后

    • 从队列中处理一条新消息(如果有的话)

    • 每条正在处理的消息都有自己的堆栈

    • 并进行处理,直到堆栈为空。


您是 Clean Code Studio 内容的粉丝吗?

“加入时事通讯以获取更多此类内容!”


您是否听说过 JavaScript 中使用的“非阻塞”或异步术语?

当执行异步操作时

  • 它不会停止或停止运行时

  • 该程序能够处理其他事情

    • 例如:接收用户输入

  • 在等待异步操作完成时

异步操作对于主执行线程来说是非阻塞的。

这是一项非常有用的功能,既可以在 JavaScript 内部使用,也可以在外部用于特定的 JavaScript 用例。异步与同步是 JavaScript 中的一个巨大主题,但如果我们也深入研究,我们永远不会了解我们的
设计模式 - 这是本文讨论的主题。


什么是设计模式


设计模式是软件设计中常见问题的可重用解决方案。我们来看看设计模式的一些类别


原型模式


创建设计模式,你是如何做到的?您是否注意到任何经常重复出现的问题?您是否已经克服了为解决此问题而专门设计的解决方案?假设您的这个解决方案没有得到全球认可和记录。

每次出现或遇到此问题时,您都会使用此解决方案。您创建的这个解决方案是可重用的,整个开发人员社区将是这种模式的受益者。

这不会立即使其成为一种设计模式。程序员可能拥有良好的代码,但只是将一些看起来像模式的东西误认为是实际的设计模式——而归根结底——它不是真正的设计模式。

是什么使某些东西成为实际的设计模式?

答:开发者普遍共识。

如果您能够从大量开发人员那里获得意见,那么您就走在正确的道路上。通过了解创建模式本身的过程,并让自己熟悉现有模式,您就开始学习该过程。任何设计模式都必须经历这个阶段才能成为成熟的模式。这称为原型模式。

如果原型模式满足一定时期的测试所定义的标准,并且必须由不同数量的开发人员进行测试,那么它就是未来的模式。它必须在许多挑战的背景下进行测试,在许多场景中进行分析,并最终通过许多测试和普遍的社区共识被证明是一种有用且可重用的设计模式。

为了展示如何使给定软件语言的开发人员社区认可成熟的模式,我们已经完成了大量的工作和文档。


反模式


同样值得注意的是,与软件中的许多事物一样,给定概念的倒置也是如此。设计模式的反面是什么?

反模式

模式代表了一种不良实践。反模式的一个例子是修改Object类原型。

在 JavaScript 中,一切几乎都继承自Object. JavaScript 使用基于原型的继承,因此在任何情况下你现在都已经改变了一些东西。您已经创建了一个变体,可以改变 JavaScript 中的所有其他设计模式、概念或技术。这是不好的,因此是一种反设计模式。


设计模式分类


设计模式的分类有多种方式,但这里有一个流行的分类。

  • 创意设计模式

  • 结构设计模式

  • 行为设计模式

  • 并发设计模式

  • 架构设计模式


创意设计模式


创建设计模式是用于创建对象的模式。这些是优化创建单个或一组对象的机制的设计模式。

  • 建造者设计模式

  • 工厂设计模式

  • 单例设计模式

  • 原型设计模式

  • 抽象工厂设计模式

都是创意设计模式的例子


结构设计模式


结构设计模式与对象关系相关。这些类型的设计模式确保如果系统的一部分发生变化,整个系统不需要随之改变。

  • 代理设计模式

  • 桥梁设计模式

  • 立面设计模式

  • 适配器设计模式

  • 装饰设计模式

  • 享元设计模式

  • 复合设计模式

都是结构设计模式的例子。


行为设计模式


行为设计模式识别、实现和改进系统中对比对象之间的通信。它们用于支持给定软件系统的对比部分具有同步数据。

  • 状态设计模式

  • 访客设计模式

  • 命令设计模式

  • 纪念品设计模式

  • 迭代器设计模式

  • 中介者设计模式

  • 观察者设计模式

  • 策略设计模式

  • 责任链设计模式

都是行为设计​​模式的例子。


并发设计模式


并发设计模式用于实现多线程编程范例的解决方案。

  • 调度程序设计模式

  • 主动对象设计模式

  • 核反应设计模式

都是并发设计模式的例子


架构设计模式


架构设计模式用于实现架构最佳实践。

  • MVP 设计模式(模型-视图-演示者)

  • MVC设计模式(模型-视图-控制器)

  • MVVM 设计模式(模型-视图-视图模型)

都是*架构设计模式的例子。


设计模式示例


每一种设计模式都代表特定类型问题的特定类型解决方案。最好的设计模式从来都不是通用的。为了成为最好的软件工程师,我们需要学习何时应该使用给定的设计模式。我们需要从上下文的角度了解哪种设计模式是最好的。

对于给定的问题使用不正确的设计模式不仅没有帮助,而且可能会损害我们和我们的应用程序目标。


构造函数模式


在经典的面向对象软件语言中,构造函数是我们首先了解的特殊函数之一。这是我们用来用一组默认属性值初始化对象的函数。

我们如何在 JavaScript 中创建对象,最常见的方法有哪些?

let obj = {}
let obj = Object.create(Object.prototype)
let obj = new Object();

创建对象后,有四种方法(自 ES3 起)可以实际向新创建的 js 对象添加属性。

点符号

obj.key = 'value'

括号表示法

obj['key'] = 'value'

Object.definePropeties 表示法

Object.defineProperties(obj, {
   'keyOne': { value: 'one', writable: true },
   'keyTwo': { value: 'two', writable: false },})

大括号表示法是在 JavaScript 中创建对象的最流行的方法。点符号或方括号是定义属性和设置这些属性值的最流行的方法。


正如我们前面谈到的,JS 实际上并不支持传统的面向对象类。new然而,我们在 JavaScript 中确实有关键字。我们可以通过new关键字支持 javascript 中的构造函数。

我们可以使用函数作为构造函数,最终使用属性初始化对象,并使用 为该对象传入初始属性值new

function Person(name, email, admin) {
   this.name = name
   this.email = email
   this.admin = admin
   this.isAdmin = () => this.admin === true
   this.isNotAdmin = () => this.admin === false}let tim = new Person('Tim', 'tim@gmail.com', false)let sarah = new Person('Sarah', 'sarah@gmail.com', true)tim.isAdmin() // falsetim.isNotAdmin() // truesarah.isAdmin() // truesarah.isNotAdmin() // false

我们可以改进这个语法吗?我们真的想在对象的构造函数中定义函数吗?我们还可以利用对象prototype来向对象添加方法。看看这个语法。

function Person(name, email, admin) {
   this.name = name
   this.email = email
   this.admin = admin}Person.prototype.isAdmin = function () {
   return this.admin === true}Person.prototype.isNotAdmin = function () {
   return this.admin === false}let tim = new Person('Tim', 'tim@gmail.com', false)let sarah = new Person('Sarah', 'sarah@gmail.com', true)tim.isAdmin() // falsetim.isNotAdmin() // truesarah.isAdmin() // truesarah.isNotAdmin() // false

模块设计模式


当 JavaScript 能够实现一些奇特的事情时,它总是让人惊叹不已。是的,有时这些特性令人困惑——然而,这也伴随着实现一些非常强大的模式的能力。

与其他语言相比,JavaScript 的奇怪之处之一是它支持访问修饰符的能力。

在我们深入研究模块模式之前,让我们首先深入研究 JavaScript 中的闭包。理解闭包对于真正理解 JavaScript 中一些最强大的模式至关重要。


JavaScript 闭包


闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此。闭包帮助我们通过作用域来模仿访问修饰符的行为。

我们通过一个例子来学习一下。

let Countable = (function () {
   let count = 0
   return function () {
      return count++
   }})()console.log(Countable()) // 1console.log(Countable()) // 2console.log(Countable()) // 3

在此示例中,我们使用IIFE - 又名立即调用函数表达式。

每次我们调用 countable 时,它​​所绑定的函数都会立即执行。我们能够做到这一点要归功于 JS 中函数作为一等公民的力量。

当这个函数被调用时,我们实际上返回另一个嵌套函数。由于我们无法count从 Countable 外部访问该变量 - 我们通过设计模式的力量,使其成为privateCountable 对象的成员。count是私人的。


通过闭包的力量,我们能够创建具有私有和公共部分的对象。这些称为模块,每当我们需要隐藏对象的某些子部分的行为时,这些模块就非常有用。我们能够修改哪些行为是公开的,哪些部分是私有的而不是公开的。

这是另一个例子:

const Collection = (function() {
   // items is a private property
   let items = [];
   // everything returned engulfed public properties and methods
   return {
      add: function (item) {
         items.push(item)
      },
      remove: function (item) {
         let index = items.indexOf(item)
         if (index >= 0) items.splice(index, 1)
      },
      all: function () {
          return JSON.parse(JSON.stringify(items))
      }
   }})()Collection.add('Tim')Collection.add('Sarah')Collection.add('Raphael')console.log(Collection.all()) // ['Tim', 'Sarah', 'Raphael']Collection.remove('Sarah')console.log(Collection.all()) // ['Tim', 'Raphael']

这种模式允许我们在对象的私有部分和公共部分之间引入清晰的分区。对于具有经典面向对象背景经验的开发人员来说,这个概念很熟悉。

话虽如此,这并不能让一切都像我们希望的那样完美。

如果您想更改成员的可见性怎么办?

您需要更改代码,在使用该成员的所有地方修改它,因为我们需要设置奇怪或不同的设置才能实现模块设计模式

将私有部分更改为公共部分(反之亦然)需要您更改代码中的多个内部依赖点。


揭示模块设计模式


让我们改进上面说明的模块设计模式。我们的主要区别在于,我们将在模块的私有范围内编写整个对象逻辑,然后通过返回匿名对象来公开我们想要公开的部分。

当将私有成员映射到相应的公共成员时,我们还可以更改私有成员的命名。

const Collection = (function () {
   /* Private Members */
   let items = []
   function all () { 
      return JSON.parse(JSON.stringify(items)) 
   }
   function add (item) { 
      items.push(item) 
   }   
   function remove (item) {
     let index = items.indexOf(item)
     if (index >= 0) items.splice(index, 1)
   }
   /* Public Members */
   return {
      addItem: add,
      allItems: all,
      removeItem: remove,
   }})()Collection.addItem('Tim')Collection.addItem('Sam')Collection.addItem('Ben')console.log(Collection.allItems()) // ['Tim', 'Sam', 'Ben']Collection.remove('Sam')console.log(Collection.allItems()) // ['Tim', 'Ben']

上面直接显示的这个示例就是所谓的揭示模块模式。这是我们能够实现模块模式的至少 3 种不同方式之一。

揭示模块模式与模块设计模式的所有其他变体之间有什么区别?

差异主要取决于公共成员的引用方式。结果,揭示模块设计模式更容易使用和修改。

话虽如此,这种设计模式在某些情况下可能很脆弱(请记住,没有一种设计模式是普遍适用的)。

当询问您是否应该使用揭示模块模式时,需要考虑以下几个有问题的情况。


    1. 私有函数是指公共函数。在这种情况下,我们无法使用此设计模式覆盖公共函数。当我们尝试覆盖它时,由于私有函数继续引用该函数的私有实现,我们将给我们的软件引入一个错误。

    1. 如果我们有一个指向私有变量的公共成员,然后继续尝试从模块外部覆盖公共成员,则我们不应该使用此设计模式。在这种情况下,其他函数仍然会引用变量的私有值,从而在我们的软件中引入错误。


单例设计模式


单例设计模式用于我们只需要一个类的一个实例的情况。Singleton 设计模式属于创建设计模式类别。

想象一下,例如,我们需要一个对象,其中包含在运行时甚至开始之前定义的应用程序的一些配置设置。在这些场景中,没有必要每次需要此配置对象时都创建一个全新的对象。用户定义的配置设置需要一次性加载到一个对象中,以便我们的 JS 运行时可以访问配置设置,但我们不需要每次尝试访问配置设置时都重新创建该对象。

const Singleton = (function () {
   // Private config 
   let config;
   function initializedConfigurationSettings (values) {
     this.random = Mathod.random()
     values = values || {}
     this.number = values.number || 5
     this.size = values.size || 10
   } 
  return {
     getConfig: function (values) {
       // we initialize the singleton value only once
       if (config === undefined) {
         config = new initializedConfigurationSettings(values)
       }
       return config
     }
  }}();const ConfigurationSettings = singleton.getConfig({ app: 'HelloWorld', environment: 'local' })console.log(ConfigurationSettings) // { app: 'HelloWorld', environment: 'local' }ConfigurationSettings.getConfig({ "number": 8 })// same randomDecimalValue as in the first config - aka we've proven it's the same object

在此示例中,您可以看到我们生成了一个随机数。如果您要使用此代码 - 第一次调用后,随机生成的数字将是相同的singleton.getConfig。这是我们证明单例对象每次返回相同对象的方法。我们只创建该对象一次,然后每次都返回相同的配置对象。


观察者设计模式


在我看来,观察者设计模式是最强大的设计模式之一——尤其是在 JavaScript 中。

观察者设计模式是一种行为设计模式。我们可以通过这种设计模式来改善软件应用程序的对比部分之间的通信。

这种设计模式在实现时确实有多种变体,但其最基本的形式有两个主要部分。

  • 第一部分:主题

  • 第二部分:观察员

主题负责处理与某个主题相关的所有操作。观察家订阅了这个主题。

观察者可以订阅取消订阅主题。


想象一下我们有两种类型的对象:

一个客户。

一家商店。

客户对特定品牌的产品(例如:iPhone)感兴趣,该产品很快就会在商店中上架。

让客户每天访问商店并检查产品可用性是资源密集型的。相反,客户可以订阅商店提供的 iPhone 主题。

解决方案:

具有某种有趣状态的客体就是主体。由于它还将通知其他对象有关其状态的更改,我们将其称为发布者

所有其他对象(在我们的例子中为客户)都将成为订阅

let publisher = {}(function (container) {
   // represents a unique subscription id to a topic
   let id = 0 
   container.subscribe = function (topic, callback) {
      if (!(topic in container)) container[topic] = []
      container[topic].push({ id: id++, callback: callback })
      return id
   }
   container.unsubscribe = function (topic, id) {
     let subscribers = []
     for (let subscriber of container[topic]) 
        if (subscriber.id !== id) 
           subscribers.push(subscriber)
     container[topic] = subscribers
   }
   container.publish = function (topic, data) {
      for (let subscriber of container[topic]) 
          subscriber.callback(data)
   }})(publisher)let subscription_1 = publisher.subscribe('mouseClicked', function (data) {
    console.log(
        "Sam's callback for mouse click: ", 
        "Event Data: ",
        JSON.stringify(data)
    )})let subscription_2 = publisher.subscribe('mouseHovered', function (data) { 
    console.log(
        "Sam's callback for mouse hovered: ", 
        "Event Data: ",
        JSON.stringify(data)
    ) })let subscription_3 = publisher.subscribe('mouseClicked', function (data) {
    console.log(
       "Sarah's callback function for mouse click: ", 
       "Event Data: ",
       JSON.stringify(data)
    )})publisher.publish('mouseClicked', { data: 'data1' })publisher.publish('mouseHovered', { data: 'data2' })// unsubsribe from an event publisher.unsubcribe('mouseClicked', subscription_3)publisher.publish('mouseClicked', { data: 'data1' })publisher.publish('mouseHovered', { data: 'data2' })

当我们需要基于正在触发的单个事件执行多个操作时,观察者设计模式非常有用。

例子:

想象一个场景,我们需要对 API 进行多个 AJAX 调用,然后我们需要更进一步,根据初始调用集返回的数据结果执行其他 AJAX 调用。

您必须将 AJAX 调用嵌套在另一个调用中,这可能会进入一种称为回调地狱的情况。使用发布者/订阅者模式是一种更优雅的解决方案。

观察者设计模式的缺点

  • 对我们系统的各个部分进行困难的测试。


原型设计模式


正如本文全文所述,JS 不支持经典 OOP 本机理解中的类。

因此,对象之间的继承是使用基于原型的编程来实现的。这使我们能够创建可以充当JavaScript 中创建的其他对象的原型的对象。原型对象用作构造函数创建的每个对象的蓝图。

让我们在 JS 中展示这个原型模式实现的简单实现。

let PersonPrototype = {
   hi: function () { console.log(`Hello, my name is ${this.name}, and I'm ${this.age}.`) },
   bye: function () { console.log(`I'm ${this.name} and I'm saying bye now!`) }}function Person (name, age) {
    age = age || 26
    name = name || "John Doe"
    function constructorFunction(name, age) {
        this.age = age
        this.name = name
    }
    constructorFunction.prototype = PersonPrototype
    let obj = new constructorFunction(name, age)
    return obj}let person1 = Person()let person2 = Person("Tim", 38)person1.hi() // "hello, my name is John Doe and I'm 26person2.hi() // "hello, my name is Tim and I'm 38

原型是继承在 JS 中的工作方式,这只是其实现的一个简单示例。


命令设计模式


当我们想要将执行命令的对象与发出我们想要执行的命令的对象分离时,我们可以使用命令设计模式。

例子:

想象一下这样一种情况:我们的应用程序对给定的应用程序服务使用大量 API 调用。这些 API 服务发生变化。

理解为这个问题实现可靠的编码解决方案的挑战中的这种奇怪现象,我们可以使用命令设计模式。

我们想要实现一个抽象层。该抽象层将调用 API 服务的对象与告诉它们何时调用 API 服务的对象分开。此实现将避免需要在需要调用服务的所有位置修改代码。相反,我们必须更改本身进行调用的对象 - 这就是说我们只需要在一处而不是多个地方进行更改。

谈到设计模式时,一个重要的要点是,我们必须习惯于理解在决定使用任何给定设计模式时所做的权衡。

我们是否添加了不需要的抽象层,或者我们是否正在解决需要抽象层正确解决的依赖侵蚀问题?

let invoker = {
   add: function (x, y) { return x + y },
   subtract: (x, y) { return x - y },}let manager = {
   execute: function (name, args) {
      if (name in invoker) { 
          return invoker[name].apply(invoker, [].slice.call(arguments, 1))
      }
      return false
   }}console.log(manager.execute("add", 3, 5)) // 8console.log(manager.execute("subtract", 5, 3)) // 2

立面设计模式


什么是门面设计模式?使用这种设计模式,我们能够在公开显示的内容和幕后实现的内容之间创建一个抽象层。这种设计模式对于提高可读性非常有用。

这种模式的一个很好的例子是来自 DOM 操作库(例如 jQuery、Dojo 或 D3)的选择器。您可能已经注意到使用这些库时它们具有非常强大的选择器功能;您可以编写复杂的查询,例如:

jQuery(".parent .child div.span")

在这个例子中,选择特征语法被简化了很多。尽管表面上看起来很简单,但所实现的场景背后的逻辑要复杂得多。在幕后,我们做了很多工作,但面向外部的 API 最终用户(在本例中开发人员是最终用户)被简化了。

我们喜欢简化:)


下一步


设计模式是软件工程师工具带中最强大的工具之一 - 如果您希望将自己变成一名高级 JavaScript 开发人员,那么您应该了解大量的设计模式。

知道如何、何时、何地实现设计模式以及权衡,这些都是领导团队的 JavaScript 工程师应该非常了解的特征。

在理解这些设计模式时,重构大师是一个很好的资源。

我们可以在本文中展示一百多个设计原则示例,但事实是一篇文章无法涵盖所有​​可用的设计原则。

我强烈建议将每周学习两个新的设计原理作为目标。一年后,您将掌握 104 条设计原则。作为软件工程师,您对任何团队和组织的价值都会增加数倍。

设计模式:可重用的面向对象软件的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值