引言
Clean Code JavaScript
是一个开源项目,该项目旨在帮助 JavaScript 开发者编写可读性强、可重用且易于重构的代码。它不仅仅是一个风格指南,而是一个指导开发者如何编写高质量 JavaScript 代码的实用指南。在实际开发的过程中不必严格遵循其中每条细责,有时候少遵循一些反而会更好。视情况而定即可。本文则是挑选一些细则进行记录。
变量
使用可读性强的变量名或者方法名
js
//反例
var a=10;
//正例
var age=10;
使用说明变量
arduino
// 1.不需要过于压缩代码,可使用说明变量过渡逻辑,增强可读性。
显式优于隐式
js
let locations=['临安','萧山','淳安']
// 反例
locations.forEach(el=>{
console.log(`location:${el}`)
})
// 正例 写location 比 el 可读性更好
locations.forEach(location=>{
console.log(`location:${location}`)
})
避免无意义的判断
js
// 反例
var breweryName;
if (name) {
breweryName = name;
} else {
breweryName = 'defaultValue';
}
// 正例
var breweryName = name || 'defaultValue'
函数
限制函数参数 (理想状态下不超过2个);
应该避免三个以上参数的函数,通常情况下参数超过2个意味着函数功能过于复杂需要对函数功能进行拆分优化; 当确实有多个参数的时候考虑封装为一个对象;
js
//反例
function createMenu(title, body, buttonText, cancellable) {
...
}
//正例
var menuConfig = {
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
}
function createMenu(menuConfig) {
...
}
函数应该只做一层抽象
通过分层抽象,让代码更符合人类线性思维习惯,降低认知负担;避免高层描述和具体细节逻辑混合在一起 提升代码的可读性可维护性,简而言之就是"做什么"和"怎么做"的细节逻辑分开。
js
// 反例
async function handleSubmit(formData) {
// 验证字段
if (!formData.email.includes("@")) {
alert("Invalid email");
return;
}
// 发送请求
const res = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(formData),
});
// 处理响应
const result = await res.json();
if (result.success) {
window.location.href = "/success";
}
}
//正例
async function handleSubmit(formData) {
if (!isFormValid(formData)) return; // 高层
const result = await submitForm(formData); // 高层
if (result.success) redirectToSuccess(); // 高层
}
// // 底层函数
function isFormValid(formData) {
if (!formData.email.includes("@")) {
alert("Invalid email");
return false;
}
return true;
}
async function submitForm(formData) {
const res = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(formData),
});
return res.json();
}
使用 Object.assign 设置默认对象
js
let shop={
addrres:'地址',
openNingTime:null,
closingTime:null,
phone:'1578656589'
}
//反例
function creatShop(config){
config.title = config.addrres||'',
config.openNingTime = config.openNingTime || '10:00'
config.closingTime = config.openNingTime || '22:00'
config.phone=config.phone||''
}
creatShop(shop)
//正例
function creatShop(config) {
config = Object.assign({
addrres: '',
openNingTime: '10:00',
closingTime: '22:00',
phone: ''
}, config)
creatShop(shop)
避免副作用
// 当函数产生了除了接受一个值或者返回一个值之外的行为时成为该函数产生的副作用。
js
// 反例
var name='Ryan McDermott'
function splitName(){
name=name.split(' ')
}
// 正例
function splitName(name){
return name.split( ' ')
}
var name='Ryan McDermott'
var newName =splitName(name);
js
采用函数式编程
其核心思想是通过纯函数、不可变数据和高阶函数等机制构建程序逻辑。与面向对象编程(OOP)不同,函数式编程更关注"如何描述问题"而非"如何操作步骤",从而提升代码的可维护性和可测试性。
js
数组每个元素进行平方操作,命令式编程与函数式如下:
// 反例
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
array[i] = Math.pow(array[i], 2)
}
// 正例
array.map(num=>Math.pow(num,2))
避免"否定情况"的判断
js
//反例
if(!show){
}
// 正例
if(show){
}
正向判断的优点
1.可读性 正向条件通常比反向条件更直观。
2.减少错误 避免在编写否定条件时容易不小心颠倒逻辑。
3.简化维护正向逻辑更容易理解与维护。
避免复杂类型判断
js是弱类型语言,意味着函数可以接受任何类型参数。
js
function travelToTex(vehicle){
if (vehicle instanceof Bicycle) {
vehicle.peddle(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'));
}
避免多态类型判断
js
//反例
function combine(val1, val2) {
if (typeof val1 == "number" && typeof val2 == "number" ||
typeof val1 == "string" && typeof val2 == "string") {
return val1 + val2;
} else {
throw new Error('Must be of type String or Number');
}
}
//对于多态类型判断建议使用TypeScript对类型增强校验。
对象和数据结构
使用 getters 和 setters
使用getter和setter对对象的访问进行控制,确保数据的安全性;s6之后可通过定义class类,字面量结合object.defineProperty方法, 使用proxy对象。
js
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
// Getter
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
// Setter
set fullName(value) {
if (typeof value !== 'string') {
throw new TypeError('Full name must be a string');
}
const parts = value.split(' ');
this._firstName = parts[0];
this._lastName = parts[1];
}
}
const person = new Person('John', 'Doe');
console.log(person.fullName); // John Doe
person.fullName = 'Jane Smith';
console.log(person.fullName); // Jane Smith
const person = {
_firstName: 'John',
_lastName: 'Doe'
};
Object.defineProperty(person, 'fullName', {
get: function() {
return `${this._firstName} ${this._lastName}`;
},
set: function(value) {
if (typeof value !== 'string') {
throw new TypeError('Full name must be a string');
}
const parts = value.split(' ');
this._firstName = parts[0];
this._lastName = parts[1];
}
});
console.log(person.fullName); // John Doe
person.fullName = 'Jane Smith'; // Jane Smith
console.log(person.fullName); // Jane Smith
const person = {
_firstName: 'John',
_lastName: 'Doe'
};
const handler = {
get(target, prop) {
if (prop === 'fullName') {
return `${target._firstName} ${target._lastName}`;
} else {
return Reflect.get(target, prop); // 默认行为,返回属性值或undefined
}
},
set(target, prop, value) {
if (prop === 'fullName') {
if (typeof value !== 'string') {
throw new TypeError('Full name must be a string');
}
const parts = value.split(' ');
target._firstName = parts[0];
target._lastName = parts[1];
return true; // 表示成功设置值
} else {
return Reflect.set(target, prop, value); // 默认行为,设置属性值或返回false表示失败
}
}
};
const proxyPerson = new Proxy(person, handler);
console.log(proxyPerson.fullName); // John Doe
proxyPerson.fullName = 'Jane Smith'; // Jane Smith
console.log(proxyPerson.fullName); // Jane Smith
让对象拥有私有成员
js
class Person {
#id; // 私有字段
#name; // 私有字段
static #counter = 0; // 静态私有字段
constructor(name) {
this.#name = name; // 使用私有字段
this.#id = ++Person.#counter; // 使用静态私有字段
}
getName() {
return this.#name; // 访问私有字段
}
}
const person1 = new Person('Alice');
console.log(person1.getName()); // Alice
console.log(person1.#id); // TypeError: Illegal access to private field '#id' of class 'Person' due to encapsulation