百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何写出优雅的 JS 代码,变量和函数的正确写法

myzbx 2025-01-12 16:51 16 浏览

在开发中,变量名,函数名一般要做到清晰明了,尽量做到看名字就能让人知道你的意图,所以变量和函数命名是挺重要,今天来看看如果较优雅的方式给变量和函数命名。

一、变量

使用有意义和可发音的变量名

// 不好的写法 const yyyymmdstr = moment().format("YYYY/MM/DD");  // 好的写法 const currentDate = moment().format("YYYY/MM/DD"); 

对同一类型的变量使用相同的词汇

// 不好的写法 getUserInfo(); getClientData(); getCustomerRecord();  // 好的写法 getUser(); 

使用可搜索的名字

我们读的会比我们写的多得多,所以如果命名太过随意不仅会给后续的维护带来困难,也会伤害了读我们代码的开发者。让你的变量名可被读取,像 buddy.js 和 ESLint 这样的工具可以帮助识别未命名的常量。

// 不好的写法 // 86400000 的用途是什么? setTimeout(blastOff, 86400000);  // 好的写法 const MILLISECONDS_IN_A_DAY = 86_400_000; setTimeout(blastOff, MILLISECONDS_IN_A_DAY); 

使用解释性变量

// 不好的写法 const address = "One Infinite Loop, Cupertino 95014"; const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; saveCityZipCode(   address.match(cityZipCodeRegex)[1],   address.match(cityZipCodeRegex)[2] );   // 好的写法 const address = "One Infinite Loop, Cupertino 95014"; const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; const [_, city, zipCode] = address.match(cityZipCodeRegex) || []; saveCityZipCode(city, zipCode); 

避免费脑的猜测

显式用于隐式

// 不好的写法 const locations = ["Austin", "New York", "San Francisco"]; locations.forEach(l => {   doStuff();   doSomeOtherStuff();   // ...   // ...   // ...   // 等等,“l”又是什么?   dispatch(l);  // 好的写法 const locations = ["Austin", "New York", "San Francisco"]; locations.forEach(location => {   doStuff();   doSomeOtherStuff();   // ...   // ...   // ...   dispatch(location); }); 

无需添加不必要的上下文

如果类名/对象名已经说明了,就无需在变量名中重复。

// 不好的写法 const Car = {   carMake: "Honda",   carModel: "Accord",   carColor: "Blue" };  function paintCar(car) {   car.carColor = "Red"; } // 好的写法 const Car = {   make: "Honda",   model: "Accord",   color: "Blue" };  function paintCar(car) {   car.color = "Red"; } 

使用默认参数代替逻辑或(与)运算

// 不好的写法 function createMicrobrewery(name) {   const breweryName = name || "Hipster Brew Co.";   // ... } // 好的写法 function createMicrobrewery(name = "Hipster Brew Co.") {   // ... } 

二、函数

函数参数(理想情况下为2个或更少)

限制函数参数的数量是非常重要的,因为它使测试函数变得更容易。如果有三个以上的参数,就会导致组合爆炸,必须用每个单独的参数测试大量不同的情况。

一个或两个参数是理想的情况,如果可能,应避免三个参数。除此之外,还应该合并。大多数情况下,大于三个参数可以用对象来代替。

// 不好的写法 function createMenu(title, body, buttonText, cancellable) {   // ... }  createMenu("Foo", "Bar", "Baz", true);  // 好的写法 function createMenu({ title, body, buttonText, cancellable }) {   // ... }  createMenu({   title: "Foo",   body: "Bar",   buttonText: "Baz",   cancellable: true }); 

函数应该只做一件事

这是目前为止软件工程中最重要的规则。当函数做不止一件事时,它们就更难组合、测试和推理。可以将一个函数隔离为一个操作时,就可以很容易地重构它,代码也会读起来更清晰。

// 不好的写法 function emailClients(clients) {   clients.forEach(client => {     const clientRecord = database.lookup(client);     if (clientRecord.isActive()) {       email(client);     }   }); }  // 好的写法  function emailActiveClients(clients) {   clients.filter(isActiveClient).forEach(email); }  function isActiveClient(client) {   const clientRecord = database.lookup(client);   return clientRecord.isActive(); } 

函数名称应说明其作用

// 不好的写法 function addToDate(date, month) {   // ... }  const date = new Date();  // 从函数名称很难知道添加什么 addToDate(date, 1);  // 好的写法 function addMonthToDate(month, date) {   // ... }  const date = new Date(); addMonthToDate(1, date); 

函数应该只有一个抽象层次

当有一个以上的抽象层次函数,意味该函数做得太多了,需要将函数拆分可以实现可重用性和更简单的测试。

// 不好的写法 function parseBetterJSAlternative(code) {   const REGEXES = [     // ...   ];    const statements = code.split(" ");   const tokens = [];   REGEXES.forEach(REGEX => {     statements.forEach(statement => {       // ...     });   });    const ast = [];   tokens.forEach(token => {     // lex...   });    ast.forEach(node => {     // parse...   }); }  // 好的写法 function parseBetterJSAlternative(code) {   const tokens = tokenize(code);   const syntaxTree = parse(tokens);   syntaxTree.forEach(node => {     // parse...   }); }  function tokenize(code) {   const REGEXES = [     // ...   ];    const statements = code.split(" ");   const tokens = [];   REGEXES.forEach(REGEX => {     statements.forEach(statement => {       tokens.push(/* ... */);     });   });    return tokens; }  function parse(tokens) {   const syntaxTree = [];   tokens.forEach(token => {     syntaxTree.push(/* ... */);   });    return syntaxTree; } 

删除重复的代码

尽量避免重复的代码,重复的代码是不好的,它意味着如果我们需要更改某些逻辑,要改很多地方。

通常,有重复的代码,是因为有两个或多个稍有不同的事物,它们有很多共同点,但是它们之间的差异迫使我们编写两个或多个独立的函数来完成许多相同的事情。 删除重复的代码意味着创建一个仅用一个函数/模块/类就可以处理这组不同事物的抽象。

获得正确的抽象是至关重要的,这就是为什么我们应该遵循类部分中列出的 「SOLID原则」。糟糕的抽象可能比重复的代码更糟糕,所以要小心!说了这么多,如果你能做一个好的抽象,那就去做吧!不要重复你自己,否则你会发现自己在任何时候想要改变一件事的时候都要更新多个地方。

「设计模式的六大原则有」

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。下面我们来分别看一下这六大设计原则。

「不好的写法」

function showDeveloperList(developers) {   developers.forEach(developer => {     const expectedSalary = developer.calculateExpectedSalary();     const experience = developer.getExperience();     const githubLink = developer.getGithubLink();     const data = {       expectedSalary,       experience,       githubLink     };      render(data);   }); }  function showManagerList(managers) {   managers.forEach(manager => {     const expectedSalary = manager.calculateExpectedSalary();     const experience = manager.getExperience();     const portfolio = manager.getMBAProjects();     const data = {       expectedSalary,       experience,       portfolio     };      render(data);   }); } 

「好的写法」

function showEmployeeList(employees) {   employees.forEach(employee => {     const expectedSalary = employee.calculateExpectedSalary();     const experience = employee.getExperience();      const data = {       expectedSalary,       experience     };      switch (employee.type) {       case "manager":         data.portfolio = employee.getMBAProjects();         break;       case "developer":         data.githubLink = employee.getGithubLink();         break;     }      render(data);   }); } 

使用Object.assign设置默认对象

「不好的写法」

const menuConfig = {   title: null,   body: "Bar",   buttonText: null,   cancellable: true };  function createMenu(config) {   configconfig.title = config.title || "Foo";   configconfig.body = config.body || "Bar";   configconfig.buttonText = config.buttonText || "Baz";   configconfig.cancellable =     config.cancellable !== undefined ? config.cancellable : true; }  createMenu(menuConfig); 

「好的写法」

const menuConfig = {   title: "Order",   // User did not include 'body' key   buttonText: "Send",   cancellable: true };  function createMenu(config) {   config = Object.assign(     {       title: "Foo",       body: "Bar",       buttonText: "Baz",       cancellable: true     },     config   );    // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}   // ... }  createMenu(menuConfig); 

不要使用标志作为函数参数

标志告诉使用者,此函数可以完成多项任务,函数应该做一件事。如果函数遵循基于布尔的不同代码路径,请拆分它们。

// 不好的写法 function createFile(name, temp) {   if (temp) {     fs.create(`./temp/${name}`);   } else {     fs.create(name);   } }  // 好的写法 function createFile(name) {   fs.create(name); }  function createTempFile(name) {   createFile(`./temp/${name}`); } 

避免副作用(第一部分)

如果函数除了接受一个值并返回另一个值或多个值以外,不执行任何其他操作,都会产生副作用。副作用可能是写入文件,修改某些全局变量,或者不小心将你的所有资金都汇给了陌生人。

「不好的写法」

let name = "Ryan McDermott";  function splitIntoFirstAndLastName() {   namename = name.split(" "); }  splitIntoFirstAndLastName();  console.log(name); // ['Ryan', 'McDermott']; 

「好的写法」

function splitIntoFirstAndLastName(name) {   return name.split(" "); }  const name = "Ryan McDermott"; const newName = splitIntoFirstAndLastName(name);  console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott']; 

避免副作用(第二部分)

在JavaScript中,原始类型值是按值传递,而对象/数组按引用传递。对于对象和数组,如果有函数在购物车数组中进行了更改(例如,通过添加要购买的商品),则使用该购物车数组的任何其他函数都将受到此添加的影响。那可能很棒,但是也可能不好。来想象一个糟糕的情况:

用户单击“购买”按钮,该按钮调用一个purchase 函数,接着,该函数发出一个网络请求并将cart数组发送到服务器。由于网络连接不好,purchase函数必须不断重试请求。现在,如果在网络请求开始之前,用户不小心点击了他们实际上不需要的项目上的“添加到购物车”按钮,该怎么办?如果发生这种情况,并且网络请求开始,那么购买函数将发送意外添加的商品,因为它有一个对购物车数组的引用,addItemToCart函数通过添加修改了这个购物车数组。

一个很好的解决方案是addItemToCart总是克隆cart数组,编辑它,然后返回克隆。这可以确保购物车引用的其他函数不会受到任何更改的影响。

关于这种方法有两点需要注意:

  • 可能在某些情况下,我们确实需要修改输入对象,但是当我们采用这种编程实践时,会发现这种情况非常少见,大多数东西都可以被改造成没有副作用。
  • 就性能而言,克隆大对象可能会非常昂贵。幸运的是,在实践中这并不是一个大问题,因为有很多很棒的库使这种编程方法能够快速进行,并且不像手动克隆对象和数组那样占用大量内存。
// 不好的写法 const addItemToCart = (cart, item) => {   cart.push({ item, date: Date.now() }); };  // 好的写法 const addItemToCart = (cart, item) => {   return [...cart, { item, date: Date.now() }]; }; 

不要写全局函数

污染全局变量在 JS 中是一种不好的做法,因为可能会与另一个库发生冲突,并且在他们的生产中遇到异常之前,API 的用户将毫无用处。让我们考虑一个示例:如果想扩展 JS 的原生Array方法以具有可以显示两个数组之间差异的diff方法,该怎么办?可以将新函数写入Array.prototype,但它可能与另一个尝试执行相同操作的库发生冲突。如果其他库仅使用diff来查找数组的第一个元素和最后一个元素之间的区别怎么办?这就是为什么只使用 ES6 类并简单地扩展Array全局会更好的原因。

// 不好的写法 Array.prototype.diff = function diff(comparisonArray) {   const hash = new Set(comparisonArray);   return this.filter(elem => !hash.has(elem)); };  // 好的写法 class SuperArray extends Array {   diff(comparisonArray) {     const hash = new Set(comparisonArray);     return this.filter(elem => !hash.has(elem));   } } 

尽量使用函数式编程而非命令式

JavaScript不像Haskell那样是一种函数式语言,但它具有函数式的风格。函数式语言可以更简洁、更容易测试。如果可以的话,尽量喜欢这种编程风格。

「不好的写法」

const programmerOutput = [   {     name: "Uncle Bobby",     linesOfCode: 500   },   {     name: "Suzie Q",     linesOfCode: 1500   },   {     name: "Jimmy Gosling",     linesOfCode: 150   },   {     name: "Gracie Hopper",     linesOfCode: 1000   } ];  let totalOutput = 0;  for (let i = 0; i < programmerOutput.length; i++) {   totalOutput += programmerOutput[i].linesOfCode; } 

「好的写法」

const programmerOutput = [   {     name: "Uncle Bobby",     linesOfCode: 500   },   {     name: "Suzie Q",     linesOfCode: 1500   },   {     name: "Jimmy Gosling",     linesOfCode: 150   },   {     name: "Gracie Hopper",     linesOfCode: 1000   } ];  const totalOutput = programmerOutput.reduce(   (totalLines, output) => totalLines + output.linesOfCode,   0 ); 

封装条件

// 不好的写法 if (fsm.state === "fetching" && isEmpty(listNode)) {   // ... }  // 好的写法 function shouldShowSpinner(fsm, listNode) {   return fsm.state === "fetching" && isEmpty(listNode); }  if (shouldShowSpinner(fsmInstance, listNodeInstance)) {   // ... } 

避免使用非条件

// 不好的写法 function isDOMNodeNotPresent(node) {   // ... }  if (!isDOMNodeNotPresent(node)) {   // ... }  // 好的写法 function isDOMNodePresent(node) {   // ... }  if (isDOMNodePresent(node)) {   // ... } 

避免使用过多条件

这似乎是一个不可能完成的任务。一听到这个,大多数人会说,“没有if语句,我怎么能做任何事情呢?”答案是,你可以在许多情况下使用多态性来实现相同的任务。

第二个问题通常是,“那很好,但是我为什么要那样做呢?”答案是上面讲过一个概念:一个函数应该只做一件事。当具有if语句的类和函数时,这是在告诉你的使用者该函数执行不止一件事情。

「不好的写法」

class Airplane {   // ...   getCruisingAltitude() {     switch (this.type) {       case "777":         return this.getMaxAltitude() - this.getPassengerCount();       case "Air Force One":         return this.getMaxAltitude();       case "Cessna":         return this.getMaxAltitude() - this.getFuelExpenditure();     }   } } 

「好的写法」

class Airplane {   // ... }  class Boeing777 extends Airplane {   // ...   getCruisingAltitude() {     return this.getMaxAltitude() - this.getPassengerCount();   } }  class AirForceOne extends Airplane {   // ...   getCruisingAltitude() {     return this.getMaxAltitude();   } }  class Cessna extends Airplane {   // ...   getCruisingAltitude() {     return this.getMaxAltitude() - this.getFuelExpenditure();   } } 

避免类型检查

JavaScript 是无类型的,这意味着函数可以接受任何类型的参数。有时q我们会被这种自由所困扰,并且很想在函数中进行类型检查。有很多方法可以避免这样做。首先要考虑的是一致的API。

// 不好的写法 function travelToTexas(vehicle) {   if (vehicle instanceof Bicycle) {     vehicle.pedal(this.currentLocation, new Location("texas"));   } else if (vehicle instanceof Car) {     vehicle.drive(this.currentLocation, new Location("texas"));   } }  // 好的写法 function travelToTexas(vehicle) {   vehicle.move(this.currentLocation, new Location("texas")); } 

不要过度优化

现代浏览器在运行时做了大量的优化工作。很多时候,如果你在优化,那么你只是在浪费时间。有很好的资源可以查看哪里缺乏优化,我们只需要针对需要优化的地方就行了。

// 不好的写法  // 在旧的浏览器上,每一次使用无缓存“list.length”的迭代都是很昂贵的 // 会为“list.length”重新计算。在现代浏览器中,这是经过优化的 for (let i = 0, len = list.length; i < len; i++) {   // ... }  // 好的写法 for (let i = 0; i < list.length; i++) {   // ... } 

原文:https://developer.51cto.com/art/202005/616073.htm

作者:前端小智

相关推荐

路痴的福音 谷歌地图AR实景导航Live View正式上线

2月份起,谷歌地图开始测试一项新功能,即是在行人导航功能中加入AR实景导航。用户可以通过设备屏幕,在实际的街道中见到路线指示,使导航功能不只在地图上,而是更融合在真实环境中。谷歌地图AR实景导航(图源...

Google地图怎么设置中文 谷歌地图app设置语言为中文

Google地图是一款知名的地图导航客户端,这是一款很好用的地图软件,Google地图怎么设置中文呢,不少人可能不是很清楚,下面就和小编一起来看看吧!Google地图怎么设置中文方法1、点击打开谷歌地...

谷歌地图说这里能过

来源:日本沙雕日常谷歌地图说这里能过#微博新鲜事#

谷歌地图已可离线导航 仅安卓机可用

谷歌已经在今年的谷歌IO大会上确认了地图离线导航功能,如今该功能已经可以在安卓机上使用了。这对网络资源较为贫瘠且相对昂贵的国家来说可谓提供了便利。用户需要提前下载所需旅程的部分地图,虽然不能获得实时路...

谷歌地图测试速度更快的AR实时视图

上个月谷歌为了庆祝谷歌地图成立15周年,为谷歌地图推出了新的图标,并且重新设计了移动应用程序。谷歌还预览了一些即将推出的功能,现在正在测试地图导航之外更快的实时视图(LiveView)访问。谷歌之...

谷歌地图新功能 离线地图可导航和搜索

【中关村在线软件资讯】5月29日消息:在今天凌晨召开的GoogleI/O开发者大会上,谷歌公布了一些关于地图的新功能。谷歌地图离线模式新版谷歌地图有更好用的离线地图,可以在无网络的情况下搜索地点、查...

谷歌地图变这样,谁还花钱去旅游?

足不出户,在手机上能身临其境的游览世界各地。文章来源:创下一个新ID:cxygx1作者:创新君编辑:卝生话说在前天的GoogleI/O2022开发人员活动中,谷歌推出了一种全新的地图模式,可以...

新版谷歌地图将添新功能:知道你想去哪儿

据外媒TheVerge报道,谷歌即将为安卓版谷歌地图增加一些新的功能,从而使之变得更加智能,比如可以推算出用户的目的地等。新版谷歌地图将添新功能(图片来自TheVerge)报道称,升级后的谷歌地图将会...

谷歌地图安卓版获效率改进,11.136.x更新引入“表单风格”卡片

IT之家7月16日消息,谷歌在今年2月宣布将对自家地图应用进行大修,目前相关更新已经实装入谷歌地图11.136.x版本中,主要围绕UI进行效率改进。谷歌提到,现在用户在查找地址时,...

谷歌地图安卓/iOS版界面大修,超漂亮

IT之家(www.ithome.com):谷歌地图安卓/iOS版界面大修,超漂亮IT之家报道,Android5.0已经正式到来,谷歌旗下的应用为了迎接安卓5.0都采用了全新的MaterialDes...

谷歌地图在美国启用“美国湾”称呼

参考消息网2月11日报道据法新社2月11日报道,美国总统特朗普10日对谷歌地图将墨西哥湾更名为“美国湾”表示欢迎,这符合他在1月底重返白宫后签署的法令之一。这一占超主导地位的地图服务现在为位于美国的用...

外交部回应谷歌地图涉南海标注:南海一直是国际社会公认通用地名,被广泛接受

【环球时报-环球网报道记者李萌】在4月15日外交部例行记者会上,有记者提问称,据报道,谷歌地图显示了“西菲律宾海”的名称,此前这里显示的是南海。有人称这有助于保护菲律宾的主权,请问中方对此有何评论?...

谷歌地图首曝数据:覆盖全球98%居住区,已拍千万英里街景

12月13日,谷歌透露了其街景车(StreetViewcar)等设备为绘制世界地图所做的工作。目前,谷歌已经捕获了超过1000万英里的街景图像,这个距离相当于绕地球400圈。旗下航空地图服务谷歌地...

美媒:谷歌称,当联邦地图作出更改时,谷歌地图将使用“迪纳利峰”及“墨西哥湾”新名称

来源:环球网【环球网报道】据美国全国广播公司(NBC)等媒体报道,美国谷歌公司27日称,当联邦地图作出更改时,谷歌地图将使用“迪纳利峰”和“墨西哥湾”的新名称,即“麦金利山”和“美国湾”。本月20日...

谷歌地图迎来15周年重大更新 界面重新设计 新增贴心功能

昨日,恰逢谷歌地图15周年生日,谷歌地图便迎来重大更新。不仅仅界面重新设计,还添加了许多贴心功能。名为“TransitAttributes”的新功能会根据过去用户共享的详细信息,向人们提供有关公共场...