JavaScript代码整洁之道

7次阅读

共计 6662 个字符,预计需要花费 17 分钟才能阅读完成。

JavaScript 代码整洁之道
整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低 bug 几率。
原文 clean-code-javascript,这里总结摘录出个人觉得有帮助的地方,也加入了一些自己的理解(有些文字我感觉保留原文更好,所以直接 copy 了),其他还有很多点没有列出来,感兴趣可以去看原文。另外这不是强制的代码规范,就像原文中说的,These are guidelines and nothing more。
1. 用命名的变量代替数组下标
// bad
const address = “One Infinite Loop, Cupertino 95014”;
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
// 下标 1,2 不易于理解
address.match(cityZipCodeRegex)[1],
address.match(cityZipCodeRegex)[2]
);
// good
const address = “One Infinite Loop, Cupertino 95014”;
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
// 使用数组解构更好的命名变量
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
2. 函数的参数最好 <= 2 个,尽量避免 3 个。
如果有很多参数就利用 object 传递,并使用解构。
注意 object array 这些引用类型的值是 mutable 的。
3. 一个函数只做一件事。
好处在于 compose, test, and reason about。
4. 不要自行扩展原型
如果想扩展原型,可以先继承再添加方法,防止污染。
// bad
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};
// good
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
5. 用多态来代替条件语句
// bad
if (type === ‘text’) {
// do something
} else if (type === ‘select’) {
// do something else
}
我个人写这种代码的一种常用方式是:
const control = {
text: {
mapper() {},
restore(){},
name: ‘this is a text field’,
},
select: {
mapper() {},
restore(){},
name: ‘this is a select field’,
}
}

control[type].mapper();
实际上就是多态(polymorphism),也可以考虑用 class 的方式,大概这样:
class Field {

}

class TextField extends Field {
mapper(){}
restore(){}
name = ‘this is a text field’;
}

class SelectField extends Field {
mapper(){}
restore(){}
name = ‘this is a select field’;
}
6. 使用 getter 和 setter 函数。
// bad
function makeBankAccount() {
// …

return {
balance: 0
// …
};
}

const account = makeBankAccount();
account.balance = 100;
// good
function makeBankAccount() {
// this one is private
let balance = 0;

// a “getter”, made public via the returned object below
function getBalance() {
return balance;
}

// a “setter”, made public via the returned object below
function setBalance(amount) {
// … validate before updating the balance
balance = amount;
}

return {
// …
getBalance,
setBalance
};
}

const account = makeBankAccount();
account.setBalance(100);
你可以在 getter 和 setter 里面做很多事情而不需要修改每一个.balance 的地方。
7. Prefer composition over inheritance
尽量用组合来代替继承,什么情况下用继承:

Your inheritance represents an “is-a” relationship and not a “has-a” relationship (Human->Animal vs. User->UserDetails).
You can reuse code from the base classes (Humans can move like all animals).
You want to make global changes to derived classes by changing a base class. (Change the caloric expenditure of all animals when they move).

8. SOLID

Single Responsibility Principle 单一职责原则
There should never be more than one reason for a class to change,一个类被改变的原因数量应该尽可能降低。如果一个类中功能太多,当你修改其中一点时会无法估量任何引用该类的模块所受到的影响。
Open/Closed Principle 开放封闭原则
用户可以在不修改内部实现的前提下自行扩展功能。例如有一个 Http 模块,内部会根据环境判断用哪个 adaptor。如果用户要添加 adaptor 就必须修改 Http 模块。
// bad
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = “ajaxAdapter”;
}
}

class NodeAdapter extends Adapter {
constructor() {
super();
this.name = “nodeAdapter”;
}
}

class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}

fetch(url) {
if (this.adapter.name === “ajaxAdapter”) {
return makeAjaxCall(url).then(response => {
// transform response and return
});
} else if (this.adapter.name === “nodeAdapter”) {
return makeHttpCall(url).then(response => {
// transform response and return
});
}
}
}

function makeAjaxCall(url) {
// request and return promise
}

function makeHttpCall(url) {
// request and return promise
}
// good
class AjaxAdapter extends Adapter {
constructor() {
super();
this.name = “ajaxAdapter”;
}

request(url) {
// request and return promise
}
}

class NodeAdapter extends Adapter {
constructor() {
super();
this.name = “nodeAdapter”;
}

request(url) {
// request and return promise
}
}

class HttpRequester {
constructor(adapter) {
this.adapter = adapter;
}

fetch(url) {
return this.adapter.request(url).then(response => {
// transform response and return
});
}
}
Liskov Substitution Principle 里式替换原则
父类和子类应该可以被交换使用而不会出错。
// bad
class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}

setColor(color) {
// …
}

render(area) {
// …
}

setWidth(width) {
this.width = width;
}

setHeight(height) {
this.height = height;
}

getArea() {
return this.width * this.height;
}
}

class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}

setHeight(height) {
this.width = height;
this.height = height;
}
}

function renderLargeRectangles(rectangles) {
rectangles.forEach(rectangle => {
rectangle.setWidth(4);
rectangle.setHeight(5);
const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
rectangle.render(area);
});
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);
上面的 Rectangle 不能直接替换 Square,因为会导致计算面积错误,考虑将计算面积的方法抽象出来:
class Shape {
setColor(color) {
// …
}

render(area) {
// …
}
}

class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}

getArea() {
return this.width * this.height;
}
}

class Square extends Shape {
constructor(length) {
super();
this.length = length;
}

getArea() {
return this.length * this.length;
}
}

function renderLargeShapes(shapes) {
shapes.forEach(shape => {
const area = shape.getArea();
shape.render(area);
});
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
Interface Segregation Principle 接口隔离原则
Clients should not be forced to depend upon interfaces that they do not use。举例来说,一个功能模块需要设计必须传的参数和可选参数,不应该强迫用户使用可选参数。
Dependency Inversion Principle 依赖注入原则
原文:

High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend on abstractions.

// bad
class InventoryRequester {
constructor() {
this.REQ_METHODS = [“HTTP”];
}

requestItem(item) {
// …
}
}

class InventoryTracker {
constructor(items) {
this.items = items;

// BAD: We have created a dependency on a specific request implementation.
// We should just have requestItems depend on a request method: `request`
this.requester = new InventoryRequester();
}

requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}

const inventoryTracker = new InventoryTracker([“apples”, “bananas”]);
inventoryTracker.requestItems();
上面例子在于,InventoryTracker 内部实例化了 InventoryRequester,也就意味着 high-level 的模块需要知道 low-level 模块的细节(比如实例化 InventoryRequester 需要知道它的构造参数等,或者说需要 import 该模块,造成耦合)。
// good
class InventoryTracker {
constructor(items, requester) {
this.items = items;
this.requester = requester;
}

requestItems() {
this.items.forEach(item => {
this.requester.requestItem(item);
});
}
}

class InventoryRequesterV1 {
constructor() {
this.REQ_METHODS = [“HTTP”];
}

requestItem(item) {
// …
}
}

class InventoryRequesterV2 {
constructor() {
this.REQ_METHODS = [“WS”];
}

requestItem(item) {
// …
}
}

// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
[“apples”, “bananas”],
new InventoryRequesterV2()
);
inventoryTracker.requestItems();
直接传入 low-level 的实例而不需要考虑它是如何被实例化的,high-level 只需要依赖抽象的接口就可以完成对子模块的调用。
9. 注释
Comments are an apology, not a requirement. Good code mostly documents itself. 好的代码是自解释的。
参考:clean-code-javascript

正文完
 0