Original Repository: ryanmcdermott/clean-code-javascript

JavaScript 风格指南


目录


1.介绍

2.变量

3.函数

4.对象和数据结构

5.类

6.测试

7.并发

8.错误处理

9.格式化

10.注释

介绍


作者根据 Robert C. Martin 《代码整洁之道》总结了适用于 JavaScript 的软件工程原则《Clean Code JavaScript》。

本文是对其的翻译。

不必严格遵守本文的所有原则,有时少遵守一些效果可能会更好,具体应根据实际情况决定。这是根据《代码整洁之道》作者多年经验整理的代码优化建议,但也仅仅只是一份建议。

软件工程已经发展了 50 多年,至今仍在不断前进。现在,把这些原则当作试金石,尝试将他们作为团队代码质量考核的标准之一吧。

最后你需要知道的是,这些东西不会让你立刻变成一个优秀的工程师,长期奉行他们也并不意味着你能够高枕无忧不再犯错。千里之行,始于足下。我们需要时常和同行们进行代码评审,不断优化自己的代码。不要惧怕改善代码质量所需付出的努力,加油。

变量


使用有意义,可读性好的变量名

反例:

var yyyymmdstr=moment().format('YYYY/MM/DD');

正例:

var yearMonthDay=moment().format('YYYY/MM/DD');

使用 ES6 的 const 定义常量

反例中使用"var"定义的"常量"是可变的。

在声明一个常量时,该常量在整个程序中都应该是不可变的。

反例:

var FIRST_US_PRESIDENT="George Washington";

正例:

const FIRST_US_PRESIDENT="George Washington";

对功能类似的变量名采用统一的命名风格

反例:

getUserInfo();getClientData();getCustomerRecord();

正例:

getUser();

使用易于检索名称

我们需要阅读的代码远比自己写的要多,使代码拥有良好的可读性且易于检索非常重要。阅读变量名晦涩难懂的代码对读者来说是一种相当糟糕的体验。让你的变量名易于检索。

反例:

// 525600 是什么?for(vari=0; i<525600; i++) { runCronJob();}

正例:

// Declare them as capitalized `var` globals.var MINUTES_IN_A_YEAR=525600;for(vari=0; i<MINUTES_IN_A_YEAR; i++) { runCronJob();}

使用说明变量(即有意义的变量名)

反例:

constcityStateRegex=/^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);

正例:

constADDRESS='One Infinite Loop, Cupertino 95014';var cityStateRegex=/^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;var match=ADDRESS.match(cityStateRegex)var city=match[1];varstate=match[2];saveCityState(city, state);

不要绕太多的弯子

显式优于隐式。

反例:

varlocations=['Austin', 'New York', 'San Francisco'];locations.forEach((l) =>{ doStuff(); doSomeOtherStuff(); ... ... ... // l是什么? dispatch(l);});

正例:

varlocations=['Austin', 'New York', 'San Francisco'];locations.forEach((location) =>{ doStuff(); doSomeOtherStuff(); ... ... ... dispatch(location);});

避免重复的描述

当类/对象名已经有意义时,对其变量进行命名不需要再次重复。

反例:

varCar={ carMake: 'Honda', carModel: 'Accord', carColor: 'Blue'};functionpaintCar(car) { car.carColor='Red';}

正例:

varCar={ make: 'Honda', model: 'Accord', color: 'Blue'};functionpaintCar(car) { car.color='Red';}

避免无意义的条件判断

反例:

functioncreateMicrobrewery(name) { var breweryName; if(name) {   breweryName=name;} else{   breweryName='Hipster Brew Co.';}}

正例:

functioncreateMicrobrewery(name) { var breweryName=name||'Hipster Brew Co.'}

函数


函数参数 (理想情况下应不超过 2 个)

限制函数参数数量很有必要,这么做使得在测试函数时更加轻松。过多的参数将导致难以采用有效的测试用例对函数的各个参数进行测试。

应避免三个以上参数的函数。通常情况下,参数超过两个意味着函数功能过于复杂,这时需要重新优化你的函数。当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。

JS 定义对象非常方便,当需要多个参数时,可以使用一个对象进行替代。

反例:

functioncreateMenu(title, body, buttonText, cancellable) { ...}

正例:

varmenuConfig={ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true}functioncreateMenu(menuConfig) { ...}

函数功能的单一性

这是软件功能中最重要的原则之一。

功能不单一的函数将导致难以重构、测试和理解。功能单一的函数易于重构,并使代码更加干净。

反例:

functionemailClients(clients) { clients.forEach(client=>{   letclientRecord=database.lookup(client);   if(clientRecord.isActive()) {     email(client);  }});}

正例:

functionemailClients(clients) { clients.forEach(client=>{   emailClientIfNeeded(client);});}functionemailClientIfNeeded(client) { if(isClientActive(client)) {   email(client);}}functionisClientActive(client) { letclientRecord=database.lookup(client); returnclientRecord.isActive();}

函数名应明确表明其功能

反例:

functiondateAdd(date, month) { // ...}letdate=newDate();// 很难理解dateAdd(date, 1)是什么意思dateAdd(date, 1);

正例:

functiondateAddMonth(date, month) { // ...}letdate=newDate();dateAddMonth(date, 1);

函数应该只做一层抽象

当函数的需要的抽象多于一层时通常意味着函数功能过于复杂,需将其进行分解以提高其可重用性和可测试性。

反例:

functionparseBetterJSAlternative(code) { letREGEXES=[   // ...]; letstatements=code.split(' '); lettokens; REGEXES.forEach((REGEX) =>{   statements.forEach((statement) =>{     // ...  })}); letast; tokens.forEach((token) =>{   // lex...}); ast.forEach((node) =>{   // parse...})}

正例:

functiontokenize(code) { letREGEXES=[   // ...]; letstatements=code.split(' '); lettokens; REGEXES.forEach((REGEX) =>{   statements.forEach((statement) =>{     // ...  })}); returntokens;}functionlexer(tokens) { letast; tokens.forEach((token) =>{   // lex...}); returnast;}functionparseBetterJSAlternative(code) { lettokens=tokenize(code); letast=lexer(tokens); ast.forEach((node) =>{   // parse...})}

移除重复的代码

永远、永远、永远不要在任何循环下有重复的代码。

这种做法毫无意义且潜在危险极大。重复的代码意味着逻辑变化时需要对不止一处进行修改。JS 弱类型的特点使得函数拥有更强的普适性。好好利用这一优点吧。

反例:

functionshowDeveloperList(developers) { developers.forEach(developer=>{   varexpectedSalary=developer.calculateExpectedSalary();   varexperience=developer.getExperience();   vargithubLink=developer.getGithubLink();   vardata={     expectedSalary: expectedSalary,     experience: experience,     githubLink: githubLink  };   render(data);});}functionshowManagerList(managers) { managers.forEach(manager=>{   varexpectedSalary=manager.calculateExpectedSalary();   varexperience=manager.getExperience();   varportfolio=manager.getMBAProjects();   vardata={     expectedSalary: expectedSalary,     experience: experience,     portfolio: portfolio  };   render(data);});}

正例:

functionshowList(employees) { employees.forEach(employee=>{   varexpectedSalary=employee.calculateExpectedSalary();   varexperience=employee.getExperience();   varportfolio;   if(employee.type==='manager') {     portfolio=employee.getMBAProjects();  } else{     portfolio=employee.getGithubLink();  }   vardata={     expectedSalary: expectedSalary,     experience: experience,     portfolio: portfolio  };   render(data);});}

采用默认参数精简代码

反例:

functionwriteForumComment(subject, body) { subject=subject||'No Subject'; body=body||'No text';}

正例:

functionwriteForumComment(subject='No subject', body='No text') { ...}

使用 Object.assign 设置默认对象

反例:

varmenuConfig={ title: null, body: 'Bar', buttonText: null, cancellable: true}functioncreateMenu(config) { config.title=config.title||'Foo' config.body=config.body||'Bar' config.buttonText=config.buttonText||'Baz' config.cancellable=config.cancellable===undefined?config.cancellable: true;}createMenu(menuConfig);

正例:

varmenuConfig={ title: 'Order', // User did not include 'body' key buttonText: 'Send', cancellable: true}functioncreateMenu(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);

不要使用标记(Flag)作为函数参数

这通常意味着函数的功能的单一性已经被破坏。此时应考虑对函数进行再次划分。

反例:

functioncreateFile(name, temp) { if(temp) {   fs.create('./temp/'+name);} else{   fs.create(name);}}

正例:

functioncreateTempFile(name) { fs.create('./temp/'+name);}----------functioncreateFile(name) { fs.create(name);}

避免副作用

当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。

程序在某些情况下确实需要副作用这一行为,如先前例子中的写文件。这时应该将这些功能集中在一起,不要用多个函数/类修改某个文件。用且只用一个 service 完成这一需求。

反例:

// Global variable referenced by following function.// If we had another function that used this name, now it'd be an array and it could break it.varname='Ryan McDermott';functionsplitIntoFirstAndLastName() { name=name.split(' ');}splitIntoFirstAndLastName();console.log(name); // ['Ryan', 'McDermott'];

正例:

functionsplitIntoFirstAndLastName(name) { returnname.split(' ');}varname='Ryan McDermott'varnewName=splitIntoFirstAndLastName(name);console.log(name); // 'Ryan McDermott';console.log(newName); // ['Ryan', 'McDermott'];

不要写全局函数

在 JS 中污染全局是一个非常不好的实践,这么做可能和其他库起冲突,且调用你的 API 的用户在实际环境中得到一个 exception 前对这一情况是一无所知的。

想象以下例子:如果你想扩展 JS 中的 Array,为其添加一个 diff 函数显示两个数组间的差异,此时应如何去做?你可以将 diff 写入 Array.prototype,但这么做会和其他有类似需求的库造成冲突。如果另一个库对 diff 的需求为比较一个数组中首尾元素间的差异呢?

使用 ES6 中的 class 对全局的 Array 做简单的扩展显然是一个更棒的选择。

反例:

Array.prototype.diff=function(comparisonArray) { varvalues=[]; varhash={}; for(variofcomparisonArray) {   hash[i] =true;} for(variofthis) {   if(!hash[i]) {     values.push(i);  }} returnvalues;}

正例:

classSuperArrayextendsArray{ constructor(...args) {   super(...args);} diff(comparisonArray) {   varvalues=[];   varhash={};   for(variofcomparisonArray) {     hash[i] =true;  }   for(variofthis) {     if(!hash[i]) {       values.push(i);    }  }   returnvalues;}}

采用函数式编程

函数式的编程具有更干净且便于测试的特点。尽可能的使用这种风格吧。

反例:

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

正例:

constprogrammerOutput=[{   name: 'Uncle Bobby',   linesOfCode: 500}, {   name: 'Suzie Q',   linesOfCode: 1500}, {   name: 'Jimmy Gosling',   linesOfCode: 150}, {   name: 'Gracie Hopper',   linesOfCode: 1000}];vartotalOutput=programmerOutput.map((programmer) =>programmer.linesOfCode).reduce((acc, linesOfCode) =>acc+linesOfCode, 0);

封装判断条件

反例:

if(fsm.state==='fetching'&&isEmpty(listNode)) { /// ...}

正例:

functionshouldShowSpinner(fsm, listNode) { returnfsm.state==='fetching'&&isEmpty(listNode);}if(shouldShowSpinner(fsmInstance, listNodeInstance)) { // ...}

避免“否定情况”的判断

反例:

functionisDOMNodeNotPresent(node) { // ...}if(!isDOMNodeNotPresent(node)) { // ...}

正例:

functionisDOMNodePresent(node) { // ...}if(isDOMNodePresent(node)) { // ...}

避免条件判断

这看起来似乎不太可能。

大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”许多情况下通过使用多态(polymorphism)可以达到同样的目的。

第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。

反例:

classAirplane{ //... getCruisingAltitude() {   switch(this.type) {     case'777':       returngetMaxAltitude() -getPassengerCount();     case'Air Force One':       returngetMaxAltitude();     case'Cessna':       returngetMaxAltitude() -getFuelExpenditure();  }}}

正例:

classAirplane{ //...}classBoeing777extendsAirplane{ //... getCruisingAltitude() {   returngetMaxAltitude() -getPassengerCount();}}classAirForceOneextendsAirplane{ //... getCruisingAltitude() {   returngetMaxAltitude();}}classCessnaextendsAirplane{ //... getCruisingAltitude() {   returngetMaxAltitude() -getFuelExpenditure();}}

避免类型判断(part 1)

JS 是弱类型语言,这意味着函数可接受任意类型的参数。

有时这会对你带来麻烦,你会对参数做一些类型判断。有许多方法可以避免这些情况。

反例:

functiontravelToTexas(vehicle) { if(vehicleinstanceofBicycle) {   vehicle.peddle(this.currentLocation, newLocation('texas'));} elseif(vehicleinstanceofCar) {   vehicle.drive(this.currentLocation, newLocation('texas'));}}

正例:

functiontravelToTexas(vehicle) { vehicle.move(this.currentLocation, newLocation('texas'));}

避免类型判断(part 2)

如果需处理的数据为字符串,整型,数组等类型,无法使用多态并仍有必要对其进行类型检测时,可以考虑使用 TypeScript。

反例:

functioncombine(val1, val2) { if(typeofval1=="number"&&typeofval2=="number"||     typeofval1=="string"&&typeofval2=="string") {   returnval1+val2;} else{   thrownewError('Must be of type String or Number');}}

正例:

functioncombine(val1, val2) { returnval1+val2;}

避免过度优化

现代的浏览器在运行时会对代码自动进行优化。有时人为对代码进行优化可能是在浪费时间。

这里可以找到许多真正需要优化的地方

反例:

// 这里使用变量len是因为在老式浏览器中,// 直接使用正例中的方式会导致每次循环均重复计算list.length的值,// 而在现代浏览器中会自动完成优化,这一行为是没有必要的for(vari=0, len=list.length; i<len; i++) { // ...}

正例:

for(vari=0; i<list.length; i++) { // ...}

删除无效的代码

不再被调用的代码应及时删除。

反例:

functionoldRequestModule(url) { // ...}functionnewRequestModule(url) { // ...}varreq=newRequestModule;inventoryTracker('apples', req, 'www.inventory-awesome.io');

正例:

functionnewRequestModule(url) { // ...}varreq=newRequestModule;inventoryTracker('apples', req, 'www.inventory-awesome.io');

对象和数据结构


使用 getters 和 setters

JS 没有接口或类型,因此实现这一模式是很困难的,因为我们并没有类似 public 和 private 的关键词。

然而,使用 getters 和 setters 获取对象的数据远比直接使用点操作符具有优势。为什么呢?

1.当需要对获取的对象属性执行额外操作时。

2.执行 set 时可以增加规则对要变量的合法性进行判断。

3.封装了内部逻辑。

4.在存取时可以方便的增加日志和错误处理。

5.继承该类时可以重载默认行为。

6.从服务器获取数据时可以进行懒加载。

反例:

classBankAccount{ constructor() {  this.balance=1000;}}letbankAccount=newBankAccount();// Buy shoes...bankAccount.balance=bankAccount.balance-100;

正例:

classBankAccount{ constructor() {  this.balance=1000;} // It doesn't have to be prefixed with `get` or `set` to be a getter/setter withdraw(amount) {if(verifyAmountCanBeDeducted(amount)) { this.balance-=amount;}}}letbankAccount=newBankAccount();// Buy shoes...bankAccount.withdraw(100);

让对象拥有私有成员

可以通过闭包完成

反例:

varEmployee=function(name) { this.name=name;}Employee.prototype.getName=function() { returnthis.name;}varemployee=newEmployee('John Doe');console.log('Employee name: '+employee.getName()); // Employee name: John Doedeleteemployee.name;console.log('Employee name: '+employee.getName()); // Employee name: undefined

正例:

varEmployee=(function() { functionEmployee(name) {   this.getName=function() {     returnname;  };} returnEmployee;}());varemployee=newEmployee('John Doe');console.log('Employee name: '+employee.getName()); // Employee name: John Doedeleteemployee.name;console.log('Employee name: '+employee.getName()); // Employee name: John Doe


单一职责原则 (SRP)

如《代码整洁之道》一书中所述,“修改一个类的理由不应该超过一个”。

将多个功能塞进一个类的想法很诱人,但这将导致你的类无法达到概念上的内聚,并经常不得不进行修改。

最小化对一个类需要修改的次数是非常有必要的。如果一个类具有太多太杂的功能,当你对其中一小部分进行修改时,将很难想象到这一修够对代码库中依赖该类的其他模块会带来什么样的影响。

反例:

classUserSettings{ constructor(user) {   this.user=user;} changeSettings(settings) {   if(this.verifyCredentials(user)) {     // ...  }} verifyCredentials(user) {   // ...}}

正例:

classUserAuth{ constructor(user) {   this.user=user;} verifyCredentials() {   // ...}}classUserSettings{ constructor(user) {   this.user=user;   this.auth=newUserAuth(user)} changeSettings(settings) {   if(this.auth.verifyCredentials()) {     // ...  }}}

开/闭原则 (OCP)

“代码实体(类,模块,函数等)应该易于扩展,难于修改。”

这一原则指的是我们应允许用户方便的扩展我们代码模块的功能,而不需要打开 js 文件源码手动对其进行修改。

反例:

classAjaxRequester{ constructor() {   // What if we wanted another HTTP Method, like DELETE? We would have to   // open this file up and modify this and put it in manually.   this.HTTP_METHODS=['POST', 'PUT', 'GET'];} get(url) {   // ...}}

正例:

classAjaxRequester{ constructor() {   this.HTTP_METHODS=['POST', 'PUT', 'GET'];} get(url) {   // ...} addHTTPMethod(method) {   this.HTTP_METHODS.push(method);}}

利斯科夫替代原则 (LSP)

“子类对象应该能够替换其超类对象被使用”。

也就是说,如果有一个父类和一个子类,当采用子类替换父类时不应该产生错误的结果。

反例:

cla***ectangle{ constructor() {   this.width=0;   this.height=0;} setColor(color) {   // ...} render(area) {   // ...} setWidth(width) {   this.width=width;} setHeight(height) {   this.height=height;} getArea() {   returnthis.width*this.height;}}classSquareextendsRectangle{ constructor() {   super();} setWidth(width) {   this.width=width;   this.height=width;} setHeight(height) {   this.width=height;   this.height=height;}}functionrenderLargeRectangles(rectangles) { rectangles.forEach((rectangle) =>{   rectangle.setWidth(4);   rectangle.setHeight(5);   letarea=rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20.   rectangle.render(area);})}letrectangles=[newRectangle(), newRectangle(), newSquare()];renderLargeRectangles(rectangles);

正例:

classShape{ constructor() {} setColor(color) {   // ...} render(area) {   // ...}}cla***ectangleextendsShape{ constructor() {   super();   this.width=0;   this.height=0;} setWidth(width) {   this.width=width;} setHeight(height) {   this.height=height;} getArea() {   returnthis.width*this.height;}}classSquareextendsShape{ constructor() {   super();   this.length=0;} setLength(length) {   this.length=length;} getArea() {   returnthis.length*this.length;}}functionrenderLargeShapes(shapes) { shapes.forEach((shape) =>{   switch(shape.constructor.name) {     case'Square':       shape.setLength(5);     case'Rectangle':       shape.setWidth(4);       shape.setHeight(5);  }   letarea=shape.getArea();   shape.render(area);})}letshapes=[newRectangle(), newRectangle(), newSquare()];renderLargeShapes(shapes);

接口隔离原则 (ISP)

“客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。”

在 JS 中,当一个类需要许多参数设置才能生成一个对象时,或许大多时候不需要设置这么多的参数。此时减少对配置参数数量的需求是有益的。

反例:

classDOMTraverser{ constructor(settings) {   this.settings=settings;   this.setup();} setup() {   this.rootNode=this.settings.rootNode;   this.animationModule.setup();} traverse() {   // ...}}let$=newDOMTraverser({ rootNode: document.getElementsByTagName('body'), animationModule: function() {} // Most of the time, we won't need to animate when traversing. // ...});

正例:

classDOMTraverser{ constructor(settings) {   this.settings=settings;   this.options=settings.options;   this.setup();} setup() {   this.rootNode=this.settings.rootNode;   this.setupOptions();} setupOptions() {   if(this.options.animationModule) {     // ...  }} traverse() {   // ...}}let$=newDOMTraverser({ rootNode: document.getElementsByTagName('body'), options: {   animationModule: function() {}}});

依赖反转原则 (DIP)

该原则有两个核心点:

1.高层模块不应该依赖于低层模块。他们都应该依赖于抽象接口。

2.抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口。

反例:

classInventoryTracker{ 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=newInventoryRequester();} requestItems() {   this.items.forEach((item) =>{     this.requester.requestItem(item);  });}}classInventoryRequester{ constructor() {   this.REQ_METHODS=['HTTP'];} requestItem(item) {   // ...}}letinventoryTracker=newInventoryTracker(['apples', 'bananas']);inventoryTracker.requestItems();

正例:

classInventoryTracker{ constructor(items, requester) {   this.items=items;   this.requester=requester;} requestItems() {   this.items.forEach((item) =>{     this.requester.requestItem(item);  });}}classInventoryRequesterV1{ constructor() {   this.REQ_METHODS=['HTTP'];} requestItem(item) {   // ...}}classInventoryRequesterV2{ 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.letinventoryTracker=newInventoryTracker(['apples', 'bananas'], newInventoryRequesterV2());inventoryTracker.requestItems();

使用 ES6 的 classes 而不是 ES5 的 Function

典型的 ES5 的类(function)在继承、构造和方法定义方面可读性较差。

当需要继承时,优先选用 classes。

但是,当在需要更大更复杂的对象时,最好优先选择更小的 function 而非 classes。

反例:

varAnimal=function(age) {   if(!(thisinstanceofAnimal)) {       thrownewError("Instantiate Animal with `new`");  }   this.age=age;};Animal.prototype.move=function() {};varMammal=function(age, furColor) {   if(!(thisinstanceofMammal)) {       thrownewError("Instantiate Mammal with `new`");  }   Animal.call(this, age);   this.furColor=furColor;};Mammal.prototype=Object.create(Animal.prototype);Mammal.prototype.constructor=Mammal;Mammal.prototype.liveBirth=function() {};varHuman=function(age, furColor, languageSpoken) {   if(!(thisinstanceofHuman)) {       thrownewError("Instantiate Human with `new`");  }   Mammal.call(this, age, furColor);   this.languageSpoken=languageSpoken;};Human.prototype=Object.create(Mammal.prototype);Human.prototype.constructor=Human;Human.prototype.speak=function() {};

正例:

classAnimal{   constructor(age) {       this.age=age;  }   move() {}}classMammalextendsAnimal{   constructor(age, furColor) {       super(age);       this.furColor=furColor;  }   liveBirth() {}}classHumanextendsMammal{   constructor(age, furColor, languageSpoken) {       super(age, furColor);       this.languageSpoken=languageSpoken;  }   speak() {}}

使用方法链

这里我们的理解与《代码整洁之道》的建议有些不同。

有争论说方法链不够干净且违反了德米特法则,也许这是对的,但这种方法在 JS 及许多库(如 JQuery)中显得非常实用。

因此,我认为在 JS 中使用方法链是非常合适的。在 class 的函数中返回 this,能够方便的将类需要执行的多个方法链接起来。

反例:

classCar{ constructor() {   this.make='Honda';   this.model='Accord';   this.color='white';} setMake(make) {   this.name=name;} setModel(model) {   this.model=model;} setColor(color) {   this.color=color;} save() {   console.log(this.make, this.model, this.color);}}letcar=newCar();car.setColor('pink');car.setMake('
©著作权归作者所有:来自51CTO博客作者mb5ff980b461ced的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 利用query()与eval()优化pandas代码
  2. 异步函数中的异常处理及测试方法 [每日前端夜话(0x18)]
  3. 高效的10个Pandas函数,你都用过吗?
  4. DBSCAN密度聚类算法(理论+图解+python代码)
  5. JavaScript的工作原理:V8引擎内部机制及优化代码的5个技巧
  6. jupyter lab最强代码提示插件来了
  7. 20 行代码:Serverless 架构下用 Python 轻松搞定图像分类和预测
  8. 8个超好用的Python内置函数,提升效率必备!
  9. pandas_profiling :教你一行代码生成数据分析报告

随机推荐

  1. 在docker上编译openjdk8
  2. 教您使用java爬虫gecco抓取JD全部商品信
  3. C#/Java 调用WSDL接口及方法
  4. 数据结构:关于重建二叉树的三种思路
  5. 记录一次LinkError排错:
  6. 201521123112《Java程序设计》第5周学习
  7. 定位JVM中占CPU较高的堆栈
  8. 约汗——基于Android的大学生找伙伴约运
  9. 【JavaWeb-6】HttpServletResponse的字符
  10. javascript 滑动验证 仿淘宝滑动验证