使用适当的设计模式可以帮助您编写更好、更易于理解的代码。这样的代码也更容易维护。但是,重要的是不要过度使用它们。在使用设计模式之前,您应该仔细考虑您的问题是否符合设计模式。当你开始一个新项目时,你不会立即开始编码。您必须定义项目的目的和范围,然后列出项目特征或规范。之后,您就可以开始编写代码了,或者,如果您正在从事更复杂的项目,您应该选择最适合该项目的设计模式。什么是设计模式?在软件工程中,设计模式是软件设计中常见问题的可重用解决方案。设计模式代表了经验丰富的软件开发人员使用的最佳实践。设计模式可以被认为是编程模板。为什么要使用设计模式?许多程序员要么认为设计模式是浪费时间,要么不知道如何正确应用它们。但是,使用正确的设计模式可以帮助您编写更好、更易于理解的代码。这样的代码也更容易维护。最重要的是,设计模式为软件开发人员提供了一个共同的词汇来谈论。它们使学习代码的人能够快速理解代码的意图。例如,如果你在你的项目中使用装饰器模式,新程序员会立即知道那段代码在做什么,他们可以更多地专注于解决业务问题,而不是试图理解那段代码在做什么。现在我们知道什么是设计模式以及它们为什么重要了。接下来,让我们深入了解应用于JavaScript的各种设计模式。模块模式模块是一段独立的代码,因此我们可以在不影响代码其他部分的情况下更新模块。模块还允许我们通过为变量创建单独的范围来避免命名空间污染。当模块与其他代码片段松散耦合时,我们也可以在其他项目中重用它们。模块是任何现代JavaScript应用程序不可或缺的一部分,有助于保持代码整洁、隔离和组织。在JavaScript中创建模块的方法有很多种,模块模式就是其中之一。Bit等平台可帮助您将模块和组件转变为可在任何项目中共享、发现和开发的共享构建块。无需任何重构即可以快速且可扩展的方式共享和重用代码。与其他编程语言不同,JavaScript没有访问修饰符,也就是说,您不能将变量声明为私有或公共。因此,模块模式也被用来模拟封装的概念。此模式使用iife(立即调用的函数表达式)、闭包和函数作用域模拟此概念,例如:;}return{publicMethod:function(){privateMethod();}}})();myModule.publicMethod();既然是iife,代码会立即执行,返回的对象会赋值给myModule变量。由于它是一个闭包,返回的对象仍然可以访问iife中定义的函数和变量,即使在iife结束之后。因此,在iife中定义的变量和函数基本上对外部作用域是隐藏的,这使得它对myModule变量是私有的。执行代码后,myModule变量如下:constmyModule={publicMethod:function(){privateMethod();}};因此,我们可以调用publicMethod(),后者又调用privateMethod(),例如://print'HelloWorld'module.publicMethod();RevealingtheModulePatternRevealingtheModulePattern是ChristianHeilmann的模块模式的略微改进版本。模块模式的问题是我们必须创建新的公共函数来调用私有函数和变量。在这个模式中,我们将返回对象的属性映射到我们想要公开的私有函数。这就是它被称为揭示模块模式的原因,例如:;}functionpublicSetName(name){privateVar=name;}functionpublicGetName(){privateFunction();}/**将所需的方法和变量分配给对象属性*/return{setName:publicSetName,greeting:publicVar,getName:publicGetName};})();myRevealingModule.setName('Mark');//打印名称:MarkmyRevealingModule.getName();这种模式让我们更容易理解哪些函数和变量是可公开访问的,有助于提高代码的可读性。执行代码后,myRevealingModule看起来像这样:constmyRevealingModule={setName:publicSetName,greeting:publicVar,getName:publicGetName};我们可以调用myrevealingmodule.setname('Mark'),这是对方法publicSetName和myRevealingModule的引用。getName()是对内部方法publicGetName的引用,例如:myRevealingModule.setName('Mark');//打印名称:MarkmyRevealingModule.getName();与模块模式相比,暴露模块模式的优点如下:通过修改return语句,只需一行代码,我们就可以将一个成员从public变为private,反之亦然。返回的对象不包含任何函数定义,所有右手表达式都定义在iife中,使代码清晰易读。ES6模块在ES6之前,JavaScript没有内置模块,开发者只能依赖第三方库或模块模式来实现模块。但是在ES6中,JavaScript有原生模块。ES6模块存储在文件中。每个文件只能有一个模块。默认情况下,模块中的所有内容都是私有的。函数、变量和类都使用export关键字公开。模块内的代码始终以严格模式运行。导出模块有多种方法来暴露函数和变量声明:在函数和变量声明之前添加export关键字,例如://utils.jsexportconstgreeting='HelloWorld';exportfunctionsum(num1,num2){console.日志('总和:',num1,num2);returnnum1+num2;}exportfunctionsubtract(num1,num2){console.log('Subtract:',num1,num2);returnnum1-num2;}//this是一个私有函数functionprivateLog(){console.log('PrivateFunction');}在代码末尾添加export关键字,里面包含了我们的函数名和变量名想要公开,例如://utils.jsfunctionmultiply(num1,num2){console.log('Multiply:',num1,num2);returnnum1*num2;}functiondivide(num1,num2){console.log('Divide:',num1,num2);返回num1/num2;}//这是一个私有函数functionprivateLog(){console.log('PrivateFunction');}export{multiply,divide};导入模块类似于导出模块。使用import,有多种方法可以导入一个模块:onceImportmultipleitems://main.js//importmultipleitemsimport{sum,multiply}from'./utils.js';console.log(sum(3,7));console.log(乘以(3,7));导入所有模块://main.js//导入所有模块import*asutilsfrom'./utils.js';console.log(utils.sum(3,7));console.log(utils.multiply(3,7));指导输入和输出的别名如果你想避免命名冲突,你可以在输出和导入时更改名称,例如:重命名output://utils.jsfunctionsum(num1,num2){console.log('Sum:',num1,num2);返回数字1+数字2;}functionmultiply(num1,num2){console.log('Multiply:',num1,num2);返回num1*num2;}export{sumasadd,multiply};重命名import://main.jsimport{add,multiplyasmult}from'./utils.js';控制台日志(添加(3、7));console.log(mult(3,7));单例模式单例对象是只能实例化一次的对象。如果一个类的实例不存在,单例模式将创建一个新的类实例。如果实例存在,它只返回对该对象的引用。对构造函数的任何重复调用都将获得相同的对象。JavaScript语言一直内置单例,但我们不称其为单例,我们称其为对象字面量,例如:constuser={name:'Peter',age:25,job:'Teacher',greet:function(){console.log('你好!');}};因为JavaScript中的每个对象都占据一个唯一的内存位置,所以当我们调用用户对象时,我们实际上是在返回对该对象的引用。如果我们尝试将user变量复制到另一个变量并修改该变量,例如:constuser1=user;user1.name='标记';我们会看到两个对象都被修改了,因为在JavaScript中,对象是通过引用传递的,而不是通过值传递的。因此,内存中只有一个对象,eg://print'Mark'console.log(user.name);//打印'Mark'console.log(user1.name);//打印trueconsole.log(user===user1);单例模式可以使用构造函数来实现,例如:letinstance=null;functionUser(){if(instance){returninstance;}实例=这个;this.name='彼得';这个年龄=25;returninstance;}constuser1=newUser();constuser2=newUser();//printtrueconsole.log(user1===user2);调用此构造函数时,它将检查实例对象是否存在。如果该对象不存在,它会将此变量分配给一个实例变量。如果该对象存在,它只返回该对象。单例也可以使用模块模式来实现,例如:constsingleton=(function(){letinstance;functioninit(){return{name:'Peter',age:24,};}return{getInstance:function(){if(!instance){instance=init();}returninstance;}}})();constinstanceA=singleton.getInstance();constinstanceB=singleton.getInstance();//printtrueconsole.log(实例A===实例B);在上面的代码中,我们通过调用singleton.getInstance方法创建了一个新的实例。如果实例已经存在,则该方法只返回实例,如果实例不存在,则调用init()函数创建一个新实例。工厂模式工厂模式使用工厂方法创建对象,而无需指定所创建对象的确切类或构造函数。工厂模式用于在不暴露实例化逻辑的情况下创建对象。当我们需要根据某些条件生成不同的对象时,可以使用这种模式,例如:classCar{constructor(options){this.doors=options.doors||4;this.state=options.state||'全新';this.color=options.color||'白色的';}}classTruck{constructor(options){this.doors=options.doors||4;this.state=options.state||'用过的';这个.color=options.color||'黑色的';}}classVehicleFactory{createVehicle(options){if(options.vehicleType==='car'){returnnewCar(options);}elseif(options.vehicleType==='truck'){returnnewTruck(options);}}}在这里,我创建了一个Car类和一个Truck类(带有一些默认值)来创建新的Car和Truck对象。我还定义了一个VehicleFactory类,它根据选项对象中收到的vehicleType属性创建并返回一个新对象。constfactory=newVehicleFactory();const汽车=工厂。createVehicle({vehicleType:'car',doors:4,color:'silver',state:'BrandNew'});const卡车=工厂。createVehicle({vehicleType:'truck',doors:2,color:'white',state:'used'});//打印Car{doors:4,state:"BrandNew",color:"silver"}console.log(car);//打印Truck{doors:2,state:"used",color:"white"}console.log(truck);我创建了一个VehicleFactory类的新对象工厂。之后,我们可以调用factory.createVehicle方法,传入一个options对象,其vehicleType属性值为car或truck。装饰器模式装饰器模式用于在不修改现有类或构造函数的情况下扩展对象的功能。此模式可用于向对象添加功能,而无需修改使用它们的底层代码。这是此模式的一个简单示例:functionCar(name){this.name=name;//默认值this.color='White';}//创建一个新的装饰对象consttesla=newCar('TeslaModel3');//用新函数装饰对象tesla.setColor=function(color){this.color=color;}tesla.setPrice=function(price){this.price=price;}tesla.setColor('black');特斯拉.setPrice(49000);//打印blackconsole.log(tesla.color);这种模式的一个更实际的例子是,汽车的价格取决于它有多少功能。如果没有装饰器模式,我们将不得不为不同的特征组合创建不同的类,每个类都有一个成本方法来计算成本,例如:classCar(){}classCarWithAC(){}classCarWithAutoTransmission{}classCarWithPowerLocks{}classCarWithACandPowerLocks{}但使用装饰器模式,我们可以创建一个基类Car并使用装饰器函数为其对象添加不同配置的成本,例如:classCar{constructor(){//defaultvaluethis.成本=功能(){返回20000;}}}//装饰函数functioncarWithAC(car){car??.hasAC=true;constprevCost=car.cost();car.cost=function(){返回prevCost+500;}}//装饰函数functioncarWithAutoTransmission(car){car??.hasAutoTransmission=true;constprevCost=car.cost();car.cost=function(){返回prevCost+2000;}}//装饰函数functioncarWithPowerLocks(car){car??.hasPowerLocks=true;constprevCost=car.cost();car.cost=function(){返回prevCost+500;首先,我们创建一个基类Car来创建Car对象。然后,为您要添加的功能创建装饰器,将Car对象作为参数传递。然后我们重写这个对象的成本函数,它返回更新后的汽车成本,并向对象添加一个新属性,指示添加了哪些功能。要添加新功能,我们可以这样做:constcar=newCar();console.log(car.cost());carWithAC(car);carWithAutoTransmission(car);carWithPowerLocks(car);最后,我们可以像下面这样计算汽车的成本://计算汽车的总成本console.log(car.cost());总结我们已经看到了JavaScript中使用的各种设计模式,但是有一些设计模式可以在JavaScript中实现,我在这里没有介绍。虽然了解各种设计模式很重要,但不要过度使用它们也很重要。在使用设计模式之前,您应该仔细考虑您的问题是否符合设计模式。要知道一种模式是否适合您的问题,您应该研究设计模式和该设计模式的应用。
