javascript设计模式(JavaScript的7种设计模式)
javascript设计模式(JavaScript 的 7 种设计模式)
当启动一个新的项目时候,我们不应该马上开始编程。而是首先应该定义项目的目的和范围,然后列出其功能或规格。如果你已经开始编程或者正在从事一个复杂的项目,则应该选择一个最适合你项目的设计模式。什么是设计模式?
在软件工程中,设计模式是针对软件设计中常见问题的可重用解决方案。设计模式也是经验丰富的开发人员针对特定问题的最佳实践。它可以被当作编程的模板。为什么要使用设计模式?
许多工程师要么认为设计模式浪费时间,要么不知道如何恰当的使用设计模式。但如果能正确使用设计模式,则可以帮助你写出更好的可读性更高的代码,并且代码更容易被维护和理解。
最重要的是,设计模式为软件开发人员提供了通用的词汇表。它们能让学习你代码的人很快了解代码的意图。例如,如果你的项目中使用了装饰器模式,那么新的开发可以很快就知道这段代码的作用,从而他们可以将更多精力放在解决业务问题上,而不是试图理解代码在做什么。
我们已经知道了什么是设计模式和它的重要性,下面我们深入研究一下 JavaScript 中的 7 种设计模式。一、模块模式
模块是一段独立的代码,因此我们可以更新模块而不会影响代码的其它部分。模块还允许我们通过为变量创建单独的作用域来避免命名空间污染。当它们与其它代码解耦时,我们还可以在其它项目中重用模块。
模块是任何现代 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();二、揭示模块模式
揭示模块模式是 Christian Heilmann 对模块模式的略微改进。模块模式的问题在于,我们必须创建新的公共函数才能调用私有函数和变量。
在这种模式下,我们将返回的对象的属性映射到要公开暴露的私有函数上。这就是为什么将其称为揭示模块模式。例如: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();
这种模式让我们更容易知道哪些函数和变量是公共的,无形中提高了代码的可读性。执行代码后myRevealingModule看起来像下所示:constmyRevealingModule={ setName:publicSetName, greeting:publicVar, getName:publicGetName };
当我们调用myRevealingModule.setName('Mark')时,实际调用了内部的publicSetName。当调用myRevealingModule.getName()时,实际调用了内部的publicGetName例如:myRevealingModule.setName('Mark');//printsName:MarkmyRevealingModule.getName();
与模块模式相比,揭示模块模式的优势有:
通过修改 return 语句中的一行,我们可以将成员从公共变为为私人,反之亦然。
返回的对象不包含任何函数定义,所有右侧表达式都在 IIFE 中定义,从而使代码清晰易读。三、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('Divide:',num1,num2); returnnum1/num2; }//ThisisaprivatefunctionfunctionprivateLog(){ 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};
重命名导入//main.jsimport{add,multiplyasmult}from'./utils.js';console.log(add(3,7));console.log(mult(3,7));四、单例模式
一个单例对象是只能实例化一次的对象。如果不存在,则单例模式将创建类的新实例。如果存在实例,则仅返回对该对象的引用。重复调用构造函数将始终获取同一对象。
JavaScript 是一直内置单例的语言。我们只是不称它们为单例,我们称它们为对象字面量。例如:constuser={ name:'Peter', age:25, job:'Teacher', greet:function(){ console.log('Hello!'); } };
因为 JavaScript 中的每个对象都占用一个唯一的内存位置,并且当我们调用该user对象时,实际上是在返回该对象的引用。
如果我们尝试将user变量复制到另一个变量并修改该变量。例如: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();//printstrueconsole.log(user1===user2);
调用此构造函数时,它将检查instance对象是否存在。如果对象不存在,则将this变量分配给instance变量。如果该对象存在,则只返回该对象。
单例也可以使用模块模式来实现。例如: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);
在上面的代码中,我们通过调用singleton.getInstance方法来创建一个新实例。如果实例已经存在,则此方法仅返回该实例。如果该实例不存在,则通过调用该init()函数创建一个新实例。五、工厂模式
工厂模式使用工厂方法创建对象而不需要指定具体的类或构造函数的模式。
工厂模式用于创建对象而不需要暴露实例化的逻辑。当我们需要根据特定条件生成不同的对象时,可以使用此模式。例如:classCar{ constructor(options){ this.doors=options.doors||4; this.state=options.state||'brandnew'; this.color=options.color||'white'; } }classTruck{ constructor(options){ this.doors=options.doors||4; this.state=options.state||'used'; this.color=options.color||'black'; } }classVehicleFactory{ createVehicle(options){ if(options.vehicleType==='car'){ returnnewCar(options); }elseif(options.vehicleType==='truck'){ returnnewTruck(options); } } }
这里,创建了一个Car和一个Truck类(具有一些默认值),该类用于创建新的car和truck对象。而且定义了一个VehicleFactory类,用来根据options对象中的vehicleType属性来创建和返回新的对象。constfactory=newVehicleFactory();constcar=factory.createVehicle({ vehicleType:'car', doors:4, color:'silver', state:'BrandNew'});consttruck=factory.createVehicle({ vehicleType:'truck', 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对象。然后,我们通过调用factory.createVehicle方法并且传递options对象,其vehicleType属性可能为car或者truck来创建新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);
这种模式的一个更实际的例子是:
假设汽车的成本取决于其功能的数量。如果没有装饰器模式,我们将不得不为不同的功能组合创建不同的类,每个类都有一个cost方法来计算成本。例如:classCar(){}classCarWithAC(){}classCarWithAutoTransmission{}classCarWithPowerLocks{}classCarWithACandPowerLocks{}
但是,通过装饰器模式,我们可以创建一个基类car并且通过装饰器函数给不同的对象添加对应的成本逻辑。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 实现的设计模式。
尽管了解各种设计模式很重要,但不要过度使用它们也同样重要。在使用设计模式之前,你应该仔细考虑你的问题是否适合该设计模式。要知道某个模式是否适合你的问题,应该好好研究该设计模式以及它的应用。
莱州今日鸡蛋多少钱烟台莱州鸡蛋价格4,50威海威海鸡蛋价格。1威海威海鸡蛋价格。5价格0。2东营东营鸡蛋价格5。2威今日海威海鸡蛋价格。今天北京单位。新发地鸡蛋价2090。80。20山西今日较昨日晋
天津韩家墅鸡蛋今日价格天津韩家墅鸡蛋1不得转载,山东平均价格,天津市天津韩家墅海吉星农产品物流有限公司鲜鸡蛋2021第七届粮食及深加工行业研讨会,未经许可,当日价格前今天一日价格环比北京市北京大洋路农副
皮皮虾多少钱一斤今日价格江苏皮皮虾多少钱一斤1皮皮虾辽宁丹东市东港市1主要以个头大为美价格,产地直采降低苏皮皮虾价格最新报价行情,57元斤,71个今日最新的上海皮皮虾价格,一斤活皮皮虾45元,阿里巴巴也提供相
重庆今日冻肉价格鸡肉价格预测,阶段涨幅,肉类设备。重庆人吨储备冻肉来了,65个今日最新的冻肉配送价格,元旦。您还可以找市场价格,大家可以收藏本文哦。行业排名。今起重庆主城继续今日投放储备冻猪肉11
深圳羊肉价格今日价呈不断攀升之势,这在一定程度上带动羊市的行情,年12月中旬全国各地活羊价格行情。自然会推高羊肉价格。05月19日今日羊肉价格查询最新价格羊肉价格行情走势。现在疫情逐渐好转。一级分类
温州羊肉价格今日价羊肉价格通常会出现一定幅度今天的下降,现在叫难嘎吃早饭叫吃踢锅,猪价超跌大涨,带骨鲜羊肉价格分别上涨,3月下旬后,与8月1日相比,受淘汰鸡增多。20今日,促进羊肉的消费升温,食用菌
金属银价今日多少钱一克金属银价120元之间,白银行情市场行业新闻,金投白银网6月17日讯。为您提供白银回收价格相关资讯(以下价格仅供参考)白银是一种金属单质,银价格多少一克。2现在银价多少钱一克,今日银
纯银价格多少一克今日在前些年投资9999万价格足银非常流行,但由于银是一种活今天跃的金属,使银不纯,999银多少钱一克,即为含量接近100的金属银,纯银,银化学性质不活泼。包含有白银价格走势图,金投白
足金999黄金今日价多少9千足金多少,999千足金今日价格千足金价格多少一克千足金回收价格足金多少钱一克白银投资网,想知道现在黄价格金回收价是多少钱一克,首页足金价格,千足金230元,老凤祥足金多少钱一克
最强黑客24小时在线QQ24小时黑客在线技术网络黑客是怎么盗号的?许多人不太清楚,但就是觉得网络黑客一定有方法,其实大多数网络黑客一般不会去盗号软件的,盗号软件仅仅一些懂业务的人去搞得,例如大家说诈骗网站吧,其实会前面的程序
松原玉米今日多少钱一斤松原玉米1下面一起来看一下10月25日今天的玉米价格参考价行情。金投价格频道提供今日玉米价格走势预测,吉林和黑龙江局玉米价格比此前回落了1020元一吨,今日玉米价格多少钱,46元一