当前位置: 首页 > 科技观察

如何写出优雅有魅力的JavaScript代码

时间:2023-03-19 22:07:44 科技观察

前言在我们平时的工作和开发中,大部分都是大人共同开发的公共项目;我们平时开发写代码的时候,都会考虑代码的可读性和复用性以及扩展性。干净的代码不仅质量更可靠,也为后期的维护和升级打下了良好的基础。我们从以下几个方面来讨论:变量1.变量命名一般我们在定义变量的时候,需要使用有意义的词汇命令,这样才能互相理解//badcodeconstyyyymmdstr=moment().format('YYYY/MM/DD');//bettercodeconstcurrentDate=moment().format('YYYY/MM/DD');2.可以说是通过一个变量产生了一个新的变量,而这个新的变量也是需要命名的,也就是说,当每个变量都是一见他就知道他干什么的。//badcodeconstADDRESS='OneInfiniteLoop,Cupertino95014';constCITY_ZIP_CODE_REGEX=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1],ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);//bettercodeconstADDRESS='OneInfiniteLoop,Cupertino95014';constCITY_ZIP_CODE_REGEX=/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;const[,city,zipCode]=ADDRESS.match(CITY_ZIP_CODE_REGEX)||[];saveCityZipCode(city,zipCode);3.形参命名在for、forEach、map的循环中,直接//badcodeconstlocations=['Austin','NewYork','SanFrancisco'];locations.map((l)=>{doStuff();doSomeOtherStuff();//...//...//...//需要查看其他代码以确定“l”的作用。dispatch(l);});//bettercodeconstlocations=['Austin','NewYork','SanFrancisco'];locations.forEach((location)=>{doStuff();doSomeOtherStuff();//...//...//...调度(位置);});4.避免无意义的前缀。比如我们只创建一个对象,就不需要在每个对象的属性中都加上对象名//badcodeconstcar={carMake:'Honda',carModel:'Accord',carColor:'Blue'};functionpaintCar(car){car.carColor='Red';}//bettercodeconstcar={make:'Honda',model:'Accord',color:'Blue'};functionpaintCar(car){car.color='Red';}5、默认值//badcodefunctioncreateMicrobrewery(name){constbreweryName=name||'HipsterBrewCo.';//...}//bettercodefunctioncreateMicrobrewery(name='HipsterBrewCo.'){//...}函数1.如果一般参数比较多,使用ES6解构传参的方式//badcodefunctioncreateMenu(title,body,buttonText,cancellable){//...}//bettercodefunctioncreateMenu({title,body,buttonText,cancellable}){//...}//bettercodecreateMenu({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true});2.简化处理一个方法最好只做一件事,不要过多处理,这样代码的可读性很高//badcodefunctionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}//bettercodefunctionemailActiveClients(clients){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}3.对象设置默认属性//badcodeconstmenuConfig={title:null,body:'Bar',buttonText:null,cancellable:true};functioncreateMenu(config){config.title=config.title||'Foo';config.body=config.body||'Bar';config.buttonText=config.buttonText||'Baz';config.cancellable=config.cancellable!==undefined?config.cancellable:true;}createMenu(menuConfig);//bettercodeconstmenuConfig={title:'Order',//'body'keyismissingbuttonText:'Send',cancellable:true};4.避免副作用函数接收一个值并返回一个新值。其他行为称为副作用,如修改全局变量、对文件进行IO操作等。当函数确实需要副作用时,如对文件进行IO操作,请不要使用多个函数/类进行文件操作,以及只使用一个函数/类进行处理。这意味着需要在唯一的地方处理副作用。副作用的三个天坑:随意修改可变数据类型,随意共享没有数据结构的状态,不在统一的地方处理副作用。//badcode//全局变量被函数引用//现在这个变量已经从字符串变成了数组。如果有其他函数引用,会出现不可预见的错误。varname='RyanMcDermott';functionsplitIntoFirstAndLastName(){name=name.split('');}splitIntoFirstAndLastName();console.log(name);//['Ryan','McDermott'];//更好的代码varname='RyanMcDermott';varnewName=splitIntoFirstAndLastName(name)functionssplitIntoFirstAndLastName(name){returnname.split('');}console.log(name);//'RyanMcDermott';console.log(newName);//['Ryan','麦克德莫特'];在JavaScript中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:假设我们写一个购物车,通过addItemToCart()方法向购物车添加商品,修改购物车数组。此时调用purchase()方法进行购买,由于传递了引用,得到的购物车数组正好是最新的数据。看起来不错,对吧?如果用户点击购买时网络出现故障,则purchase()方法不断重复调用,同时用户添加了新商品,网络又恢复了。那么purchase()方法获取购物车数组是错误的。为了避免这个问题,我们需要克隆购物车数组,并在每次添加新商品时返回一个新数组。//badcodeconstaddItemToCart=(cart,item)=>{cart.push({item,date:Date.now()});};//bettercodeconstaddItemToCart=(cart,item)=>{return[...cart,{item,date:Date.now()}]};5.全局方法在JavaScript中,永远不要污染全局环境,这会导致生产环境出现不可预知的bug。例如,假设你在Array.prototype上添加了一个diff方法来判断两个数组之间的差异。而你的同事也要做类似的事情,只不过他的diff方法是用来判断两个数组首元素的区别的。显然,你的方法会引起冲突。遇到此类问题,我们可以使用ES2015/ES6语法来扩展Array。//badcodeArray.prototype.diff=functiondiff(comparisonArray){consthash=newSet(comparisonArray);returnthis.filter(elem=>!hash.has(elem));};//bettercodeclassSuperArrayextendsArray{diff(comparisonArray){consthash=newSet(比较数组);returnthis.filter(elem=>!hash.has(elem));}}6.避免类型检查JavaScript是无类型的,这意味着你可以传递任何类型的参数。这种自由度很容易让人烦恼,不自觉的就会去查类型。您真的需要检查类型还是您的API设计有问题?//badcodefunctiontravelToTexas(vehicle){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation('texas'));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation('texas'));}}//bettercodefunctiontravelToTexas(vehicle){vehicle.move(this.currentLocation,newLocation('texas'));}如果需要做statictypingCheck,比如字符串,整数等,建议使用TypeScript,否则你的代码会变得又臭又长。//badcodefunctioncombine(val1,val2){if(typeofval1==='number'&&typeofval2==='number'||typeofval1==='string'&&typeofval2==='string'){returnval1+val2;}thrownewError('MustbeoftypeStringorNumber');}//bettercodefunctioncombine(val1,val2){returnval1+val2;}复杂的条件判断我们在写js代码的时候经常会遇到复杂的逻辑判断,通常可以使用if/else或者switch来实现多个条件判断,但是这会有问题。随着逻辑复杂度的增加,代码中的if/else/switch会越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑1、if/elseclicklistbuttonevent/***buttonclickevent*@param{number}status活动状态:1团启动进行中2团启动失败3产品售罄4团启动成功5系统取消*/constonButtonClick=(status)=>{if(status==1){sendLog('processing')jumpTo('IndexPage')}elseif(status==2){sendLog('fail')jumpTo('FailPage')}elseif(status==3){sendLog('fail')jumpTo('FailPage')}elseif(status==4){sendLog('success')jumpTo('SuccessPage')}elseif(status==5){sendLog('cancel')jumpTo('CancelPage')}else{sendLog('other')jumpTo('Index')}}从上面我们可以看出通过不同的状态完成了不同的事情,代码看起来很丑陋,你可以很容易地提出这段代码的重写方案,switchappearance:2,switch/case/***按钮点击事件*@param{number}status活动状态:1group开团中2开团失败3产品售罄4开团成功5系统取消*/constonButtonClick=(status)=>{switch(status){case1:sendLog('processing')jumpTo('IndexPage')breakcase2:case3:sendLog('fail')jumpTo('FailPage')breakcase4:sendLog('成功')jumpTo('SuccessPage')breakcase5:sendLog('cancel')jumpTo('CancelPage')breakdefault:sendLog('other')jumpTo('Index')break}}这个看起来比if/else清楚多了,细心的同学还发现了一个小窍门。当case2和case3的逻辑相同时,执行语句和break可以省略,case2的情况下会自动执行case3的逻辑。3.存入Object,判断条件为对象名称的属性,处理逻辑作为对象的属性值。单击按钮时,通过查找对象属性进行逻辑判断。这种写法特别适合一元条件判断的情况。compactions={'1':['processing','IndexPage'],'2':['fail','FailPage'],'3':['fail','FailPage'],'4':['success','SuccessPage'],'5':['cancel','CancelPage'],'default':['other','Index'],}/***按钮点击事件*@param{number}status活动状态:1团启动进行中2团启动失败3产品售罄4团启动成功5系统取消*/constonButtonClick=(status)=>{letaction=actions[status]||actions['default'],logName=action[0],pageName=action[1]sendLog(logName)jumpTo(pageName)}4、存入Mapconstactions=newMap([[1,['processing','IndexPage']],[2,['fail','FailPage']],[3,['fail','FailPage']],[4,['success','SuccessPage']],[5,['cancel','CancelPage']],['default',['other','Index']]])/***按钮点击事件*@param{number}status活动状态:1团开始进行中2团开始失败3商品售罄4开启组成功5系统取消*/constonButtonClick=(status)=>{letaction=actions.get(status)||actions.get('default')sendLog(action[0])jumpTo(action[1])}w像这样的仪式在es6中使用Map对象更好吗?Map对象和Object对象有什么区别?一个对象通常有它自己的原型,所以一个对象总是有一个“原型”键。对象的键只能是字符串或符号,但映射的键可以是任何值。通过size属性可以很容易的得到一个Map的key-value对的个数,而一个object的key-value对的个数只能手动确认。代码风格常量大写//badcodeconstDAYS_IN_WEEK=7;constdaysInMonth=30;constsongs=['BackInBlack','StairwaytoHeaven','HeyJude'];constArtists=['ACDC','LedZeppelin','TheBeatles'];functioneraseDatabase(){}functionrestore_database(){}classanimal{}classAlpaca{}//bettercodeconstDAYS_IN_WEEK=7;constDAYS_IN_MONTH=30;constSONGS=['BackInBlack','StairwaytoHeaven','HeyJude'];constARTISTS=['ACDC','LedZeppelin','TheBeatles'];functioneraseDatabase(){}functionrestoreDatabase(){}classAnimal{}classAlpaca{}首先声明然后调用//badcodeclassPerformanceReview{constructor(employee){this.employee=employee;}lookupPeers(){returndb.lookup(this.employee,'peers');}lookupManager(){returndb.lookup(this.employee,'manager');}getPeerReviews(){constpeers=this.lookupPeers();//...}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getManagerReview(){constmanager=this.lookupManager();}getSelfReview(){//...}}constreview=newPerformanceReview(employee);审查。perfReview();//更好的代码classPerformanceReview{constructor(employee){this.employee=employee;}perfReview(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();}getPeerReviews(){constpeers=this.lookupPeers();//...}lookupPeers(){returndb.lookup(this.employee,'peers');}getManagerReview(){constmanager=this.lookupManager();}lookupManager(){returndb.lookup(this.employee,'经理');}getSelfReview(){//...}}constreview=newPerformanceReview(员工);review.perfReview();