JavaScript代码整洁之道整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低bug几率。原文clean-code-javascript,这里总结摘录出个人觉得有帮助的地方,也加入了一些自己的理解(有些文字我感觉保留原文更好,所以直接copy了),其他还有很多点没有列出来,感兴趣可以去看原文。另外这不是强制的代码规范,就像原文中说的,These are guidelines and nothing more。1. 用命名的变量代替数组下标// badconst address = “One Infinite Loop, Cupertino 95014”;const cityZipCodeRegex = /^[^,\]+[,\\s]+(.+?)\s*(\d{5})?$/;saveCityZipCode( // 下标1,2不易于理解 address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);// goodconst 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. 不要自行扩展原型如果想扩展原型,可以先继承再添加方法,防止污染。// badArray.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem));};// goodclass SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); }}5. 用多态来代替条件语句// badif (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函数。// badfunction makeBankAccount() { // … return { balance: 0 // … };}const account = makeBankAccount();account.balance = 100;// goodfunction 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. SOLIDSingle Responsibility Principle 单一职责原则There should never be more than one reason for a class to change,一个类被改变的原因数量应该尽可能降低。如果一个类中功能太多,当你修改其中一点时会无法估量任何引用该类的模块所受到的影响。Open/Closed Principle 开放封闭原则用户可以在不修改内部实现的前提下自行扩展功能。例如有一个Http模块,内部会根据环境判断用哪个adaptor。如果用户要添加adaptor就必须修改Http模块。// badclass 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}// goodclass 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 里式替换原则父类和子类应该可以被交换使用而不会出错。// badclass 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.// badclass 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该模块,造成耦合)。// goodclass 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