开始一个新项目时,我们不应该马上开始编程。相反,您应该首先定义项目的目的和范围,然后列出其特性或规格。如果您已经开始编程或正在从事一个复杂的项目,您应该选择最适合您的项目的设计模式。什么是设计模式?在软件工程中,设计模式是针对软件设计中常见问题的可重用解决方案。设计模式也是经验丰富的开发人员针对特定问题的最佳实践。它可以用作编程模板。为什么要使用设计模式?许多工程师要么认为设计模式是浪费时间,要么不知道如何正确使用设计模式。但是如果你能正确地使用设计模式,它可以帮助你写出更好、更具可读性的代码,代码也更容易维护和理解。最重要的是,设计模式为软件开发人员提供了一个通用词汇表。它们使研究您的代码的人能够快速理解代码的意图。例如,如果你在项目中使用了装饰器模式,新开发人员可以很快知道这段代码做了什么,这样他们就可以更专注于解决业务问题,而不是试图去理解这段代码在做什么。现在我们知道什么是设计模式及其重要性,让我们深入了解JavaScript中的7种设计模式。1.模块模式模块是一段独立的代码,所以我们可以更新模块而不影响其他部分的代码。模块还允许我们通过为变量创建单独的范围来避免名称空间污染。当模块与其他代码解耦时,我们还可以在其他项目中重用模块。模块是任何现代JavaScript应用程序不可或缺的一部分,有助于保持代码整洁、独立和有条理。在JavaScript中创建模块的方法有很多种,模块模式就是其中之一。与其他编程语言不同,JavaScript没有访问修饰符,也就是说,您不能将变量声明为私有或公共。因此,模块模式也可以用来模拟封装的概念。模块模式使用IIFE(立即调用函数表达式)、闭包和函数作用域来模拟封装的概念。例如:constmyModule=(function(){constprivateVariable='HelloWorld';functionprivateMethod(){console.log(privateVariable);}return{publicMethod:function(){privateMethod();}}})();myModule.publicMethod();由于是IIFE,代码会立即执行,返回的对象会赋值给myModule变量。由于闭包,即使在IIFE完成后,返回的对象仍然可以访问IIFE中定义的函数和变量。因此,在IIFE内部定义的变量和函数对外部是不可见的,使它们成为myModule模块的私有成员。执行代码后,myModule变量如下所示:constmyModule={publicMethod:function(){privateMethod();}};所以当我们调用publicMethod()时,它会调用privateMethod()示例://Prints'HelloWorld'module.publicMethod();二、揭示模块模式揭示模块模式是ChristianHeilmann对模块模式的轻微改进。模块模式的问题在于我们必须创建新的公共函数才能调用私有函数和变量。在此模式中,我们将返回对象的属性映射到要公开的私有函数。这就是为什么它被称为揭示模块模式。例如:constmyRevealingModule=(function(){letprivateVar='Peter';constpublicVar='HelloWorld';functionprivateFunction(){console.log('Name:'+privateVar);}functionpublicSetName(name){privateVar=name;}functionpublicGetName(){privateFunction();}/**revealmethodsandvariablesbyassigningthemtoobjectproperties*/return{setName:publicSetName,greeting:publicVar,getName:publicGetName};})();myRevealingModule.setName('Mark');//printsName:MarkmyRevealingModule.getName();这种模式让我们更容易知道哪些函数和变量是public的,无形中提高了代码的可读性。执行代码后myRevealingModule看起来像这样:constmyRevealingModule={setName:publicSetName,greeting:publicVar,getName:publicGetName};当我们调用myRevealingModule.setName('Mark')时,实际上调用了内部的publicSetName。在调用myRevealingModule.getName()时,实际上调用了内部的publicGetName。例如:myRevealingModule.setName('Mark');//printsName:MarkmyRevealingModule.getName();与模块模式相比,揭示模块模式的优点是:通过修改return语句中的一行,我们可以将一个成员从public变为private,反之亦然。返回的对象不包含任何函数定义,所有右侧表达式都定义在IIFE中,使代码清晰易读。3.ES6模块在ES6之前,JavaScript没有内置模块,因此开发者不得不依赖第三方库或模块模式来实现模块。但是从ES6开始,JavaScript有了内置的模块。ES6模块存储为文件。每个文件只能有一个模块。默认情况下,模块内的所有内容都是私有的。使用export关键字公开函数、变量和类。模块内的代码始终以严格模式运行。3.1导出模块导出函数和变量声明的方式有两种:在函数和变量声明前加上export关键字。例如://utils.jsexportconstgreeting='HelloWorld';exportfunctionsum(num1,num2){console.log('Sum:',num1,num2);returnnum1+num2;}exportfunctionsubtract(num1,num2){console.log('Subtract:',num1,num2);returnnum1-num2;}//ThisisaprivatefunctionfunctionprivateLog(){console.log('PrivateFunction');}在代码末尾添加export关键字,暴露函数和变量。例如://utils.jsfunctionmultiply(num1,num2){console.log('Multiply:',num1,num2);returnnum1*num2;}functiondivide(num1,num2){console.log('除法:',num1,num2);returnnum1/num2;}//这是一个privatefunctionfunctionprivateLog(){console.log('PrivateFunction');}export{multiply,divide};3.2导入模块和导出模块类似,有两种导入模块的方法,使用import关键字。例如:一次导入多个项目//main.js//importingmultipleitemsimport{sum,multiply}from'./utils.js';console.log(sum(3,7));console.log(multiply(3,7));导入所有模块//main.js//importingallofmoduleimport*asutilsfrom'./utils.js';console.log(utils.sum(3,7));console.log(utils.multiply(3,7));3.3导入导出使用别名重命名导出//utils.jsfunctionsum(num1,num2){console.log('Sum:',num1,num2);returnnum1+num2;}functionmultiply(num1,num2){console.log('Multiply:',num1,num2);returnnum1*num2;}export{sumasadd,multiply};renameimport//main.jsimport{add,multiplyasmult}from'./utils.js';console.log(添加(3,7));控制台日志(多(3,7));4.单例模式单例对象是只能被实例化一次的对象。如果类不存在,单例模式将创建一个新的类实例。如果实例存在,则只返回对该对象的引用。重复调用构造函数将始终获得相同的对象。JavaScript是一直内置单例的语言。我们只是不称它们为单例,我们称它们为对象文字。例如:constuser={name:'Peter',age:25,job:'Teacher',greet:function(){console.log('Hello!');}};因为JavaScript中的每个对象都占据一个唯一的内存位置,当我们调用用户对象时,我们实际上返回了对该对象的引用。如果我们尝试将用户变量复制到另一个变量并修改该变量。例如:constuser1=user;user1.name='Mark';我们会看到两个对象都被修改了,因为JavaScript中的对象是通过引用而不是值传递的。因此,内存中只有一个对象。例如://prints'Mark'console.log(user.name);//prints'Mark'console.log(user1.name);//printstrueconsole.log(user===user1);您可以使用构造函数来实现单例模式。例如:letinstance=null;functionUser(){if(instance){returninstance;}instance=this;this.name='Peter';this.age=25;returninstance;}constuser1=newUser();constuser2=newUser();//打印trueconsole.log(user1===user2);调用此构造函数时,它将检查实例对象是否存在。如果对象不存在,则将this变量赋值给实例变量。该对象仅在存在时返回。单例也可以使用模块模式来实现。例如:constsingleton=(function(){letinstance;functioninit(){return{name:'Peter',age:24,};}return{getInstance:function(){if(!instance){instance=init();}returninstance;}}})();constinstanceA=singleton.getInstance();constinstanceB=singleton.getInstance();//printstrueconsole.log(instanceA===instanceB);在上面的代码中,我们调用了单例。getInstance方法来创建一个新的实例。如果实例已经存在,则此方法只返回该实例。如果实例不存在,则通过调用init()函数创建一个新实例。五、工厂模式工厂模式使用工厂方法创建对象而不指定具体的类或构造函数模式。工厂模式用于在不暴露实例化逻辑的情况下创建对象。当我们需要根据特定条件生成不同的对象时,可以使用这种模式。例如:classCar{constructor(options){this.doors=options.doors||4;this.state=options.state||'brandnew';this.color=options.color||'white';}}classTruck{构造函数(选项){this.doors=options.doors||4;this.state=options.state||'used';this.color=options.color||'black';}}classVehicleFactory{createVehicle(选项){if(options.vehicleType==='car'){returnnewCar(options);}elseif(options.vehicleType==='truck'){returnnewTruck(options);}}}在这里,一辆汽车和一辆卡车是创建的类(具有一些默认值),用于创建新的汽车和卡车对象。并定义了一个VehicleFactory类,用于根据选项对象中的vehicleType属性创建和返回新对象。constfactory=newVehicleFactory();constcar=factory.createVehicle({vehicleType:'car',doors:4,color:'silver',state:'BrandNew'});consttruck=factory.createVehicle({vehicleType:'卡车',doors:2,color:'white',state:'used'});//PrintsCar{doors:4,state:"BrandNew",color:"silver"}console.log(car);//PrintsTruck{doors:2,state:"used",color:"white"}console.log(truck);我为VehicleFactory类创建了一个新的工厂对象。然后,我们通过调用factory.createVehicle方法并传递一个其vehicleType属性可以是汽车或卡车的选项对象来创建一个新的Car或Truck对象。六、装饰者模式装饰者模式用于在不修改现有类或构造函数的情况下扩展对象的功能。此模式可用于在不修改底层代码的情况下向对象添加特征。这种模式的一个简单示例是:functionCar(name){this.name=name;//Defaultvaluesthis.color='White';}//CreatinganewObjecttodecorateconsttesla=newCar('TeslaModel3');//Decoratingtheobjectwithnewfunctionalitytesla.setColor=function(color){this.color=color;}tesla.setPrice=function(price){this.price=price;}tesla.setColor('black');tesla.setPrice(49000);//printsblackconsole.log(tesla.color);这种模式的一个更现实的例子:假设一辆汽车的成本取决于其特征的数量。如果没有装饰器模式,我们将不得不为不同的功能组合创建不同的类,每个类都有一个成本方法来计算成本。例如:classCar(){}classCarWithAC(){}classCarWithAutoTransmission{}classCarWithPowerLocks{}classCarWithACandPowerLocks{}但是,通过装饰器模式,我们可以创建一个基类汽车,并通过装饰器函数为不同的对象添加相应的成本逻辑。classCar{constructor(){//DefaultCostthis.cost=function(){return20000;}}}//DecoratorfunctionfunctioncarWithAC(car){car.hasAC=true;constprevCost=car.cost();car.cost=function(){returnprevCost+500;}}//DecoratorfunctionfunctioncarWithAutoTransmission(car){car.hasAutoTransmission=true;constprevCost=car.cost();car.cost=function(){returnprevCost+2000;}}//DecoratorfunctionfunctioncarWithPowerLocks(car){car.hasPowerLocks=true;constprevCost=car.cost();car.cost=function(){returnprevCost+500;}}首先,我们创建了基类Car。然后为要添加的属性创建一个装饰器,这个装饰器将Car对象作为参数。然后,它通过返回更新后的汽车成本来覆盖对象的成本函数,并添加一个属性来标识是否已添加功能。要添加新功能,我们只需执行以下操作:constcar=newCar();console.log(car.cost());carWithAC(car);carWithAutoTransmission(car);carWithPowerLocks(car);最后,我们可以这样计算汽车的成本://Calculatingtotalcostofthecarconsole.log(car.cost());结束语我们已经了解了JavaScript中使用的各种设计模式,但是这里没有涵盖可以在JavaScript中实现的任何设计模式。虽然了解各种设计模式很重要,但不要过度使用它们同样重要。在使用一种设计模式之前,你应该仔细考虑你的问题是否适合那种设计模式。要知道一种模式是否适合你的问题,你应该研究设计模式及其应用。
