JS 代码中变量和函数的正确写法总结

简介: **代码规范与最佳实践摘要**1. 使用可读性强的变量名,如`currentDate`代替`yyyymmdstr`。2. 对同一类型变量使用相似命名,如`getUser()`代替`getUserInfo()`。3. 变量名应具有描述性,避免使用难以理解的数字,如`MILLISECONDS_IN_A_DAY`代替`86400000`。

一、变量

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

// 不好的写法 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 ); 代码参考示例:http://zhounian.jxmayflora.com/sitemap.html
封装条件

// 不好的写法 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")); }
不要过度优化

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

相关文章
|
3天前
|
JavaScript 前端开发 Python
用python执行js代码:PyExecJS库
文章讲述了如何使用PyExecJS库在Python环境中执行JavaScript代码,并提供了安装指南和示例代码。
22 1
用python执行js代码:PyExecJS库
|
1天前
|
JavaScript 前端开发 安全
JavaScript函数详解
JavaScript函数的详细解析,包括函数的定义和调用方式(如一般格式、匿名函数、构造函数、自调用函数、箭头函数和严格模式)、函数参数(arguments对象、可变参数、默认参数值)、闭包的概念和应用实例。
JavaScript函数详解
|
2天前
|
存储 前端开发 JavaScript
前端基础(二)_JavaScript变量、JavaScript标识符、JavaScript获取元素、JavaScript的鼠标事件
本文介绍了JavaScript变量的声明和使用、标识符的命名规则、如何获取和操作HTML元素,以及JavaScript的鼠标事件处理,通过示例代码展示了这些基础知识点在实际开发中的应用。
13 2
前端基础(二)_JavaScript变量、JavaScript标识符、JavaScript获取元素、JavaScript的鼠标事件
|
1天前
|
JavaScript 前端开发 Python
python执行js代码
本文档详细介绍如何安装Node.js环境及PyExecJS库,并提供示例代码展示其功能。首先,通过指定链接安装Node.js,安装完毕后可在命令行中输入`node --version`来验证安装是否成功。接着,使用`pip install PyExecJS`安装PyExecJS库,该库允许Python程序执行JavaScript代码。文档还提供了多个示例代码,展示了如何在Python环境中执行和编译JavaScript代码,并可以选择特定的JavaScript运行时环境,如Node.js或JScript。最后,通过具体案例展示了PyExecJS的功能与使用方法。
10 3
|
1天前
|
JavaScript 前端开发
js 变量作用域与解构赋值| 22
js 变量作用域与解构赋值| 22
|
1天前
|
缓存 JavaScript 前端开发
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
该文章详细讲解了JavaScript中的作用域、闭包概念及其应用场景,并简要分析了函数柯里化的使用。
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
|
3天前
|
JavaScript 前端开发
js防抖函数返回值问题解决方案
本文介绍了如何在JavaScript中创建一个带有返回值的防抖函数,通过结合Promise来实现。这种防抖函数可以在事件触发一定时间后再执行函数,并能处理异步操作的返回值。文章提供了防抖函数的实现代码和如何在实际项目中使用该防抖函数的示例。
14 1
|
1天前
|
Web App开发 JavaScript 前端开发
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
|
2天前
|
存储 JavaScript 前端开发
webSocket+Node+Js实现在线聊天(包含所有代码)
文章介绍了如何使用WebSocket、Node.js和JavaScript实现在线聊天功能,包括完整的前端和后端代码示例。
18 0
|
4月前
|
JavaScript 前端开发
js变量的作用域、作用域链、数据类型和转换应用案例
【4月更文挑战第27天】JavaScript 中变量有全局和局部作用域,全局变量在所有地方可访问,局部变量只限其定义的代码块。作用域链允许变量在当前块未定义时向上搜索父级作用域。语言支持多种数据类型,如字符串、数字、布尔值,可通过 `typeof` 检查类型。转换数据类型用 `parseInt` 或 `parseFloat`,将字符串转为数值。
37 1