前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 风格指南 [每日前端夜话(0x1C)]

JavaScript 风格指南 [每日前端夜话(0x1C)]

作者头像
疯狂的技术宅
发布2019-03-27 11:08:28
7220
发布2019-03-27 11:08:28
举报
文章被收录于专栏:京程一灯京程一灯

每日前端夜话0x1C

每日前端夜话,陪你聊前端。

每天晚上18:00准时推送。

正文共:4708 字 很多 代码

预计阅读时间: 36 分钟

原文链接:https://github.com/ryanmcdermott/clean-code-javascript

Original Repository: ryanmcdermott/clean-code-javascript

JavaScript 风格指南

目录

  1. 介绍
  2. 变量
  3. 函数
  4. 对象和数据结构
  5. 测试
  6. 并发
  7. 错误处理
  8. 格式化
  9. 注释

介绍

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

本文是对其的翻译。

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

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

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

变量

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

反例:

代码语言:javascript
复制
var yyyymmdstr=moment().format('YYYY/MM/DD');

正例:

代码语言:javascript
复制
var yearMonthDay=moment().format('YYYY/MM/DD');

使用 ES6 的 const 定义常量

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

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

反例:

代码语言:javascript
复制
var FIRST_US_PRESIDENT="George Washington";

正例:

代码语言:javascript
复制
const FIRST_US_PRESIDENT="George Washington";

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

反例:

代码语言:javascript
复制
getUserInfo();
getClientData();
getCustomerRecord();

正例:

代码语言:javascript
复制
getUser();

使用易于检索名称

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

反例:

代码语言:javascript
复制
// 525600 是什么?
for(vari=0; i<525600; i++) {
 runCronJob();
}

正例:

代码语言:javascript
复制
// Declare them as capitalized `var` globals.
var MINUTES_IN_A_YEAR=525600;
for(vari=0; i<MINUTES_IN_A_YEAR; i++) {
 runCronJob();
}

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

反例:

代码语言:javascript
复制
constcityStateRegex=/^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);

正例:

代码语言:javascript
复制
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);

不要绕太多的弯子

显式优于隐式。

反例:

代码语言:javascript
复制
varlocations=['Austin', 'New York', 'San Francisco'];
locations.forEach((l) =>{
 doStuff();
 doSomeOtherStuff();
 ...
 ...
 ...
 // l是什么?
 dispatch(l);
});

正例:

代码语言:javascript
复制
varlocations=['Austin', 'New York', 'San Francisco'];
locations.forEach((location) =>{
 doStuff();
 doSomeOtherStuff();
 ...
 ...
 ...
 dispatch(location);
});

避免重复的描述

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

反例:

代码语言:javascript
复制
varCar={
 carMake: 'Honda',
 carModel: 'Accord',
 carColor: 'Blue'
};

functionpaintCar(car) {
 car.carColor='Red';
}

正例:

代码语言:javascript
复制
varCar={
 make: 'Honda',
 model: 'Accord',
 color: 'Blue'
};

functionpaintCar(car) {
 car.color='Red';
}

避免无意义的条件判断

反例:

代码语言:javascript
复制
functioncreateMicrobrewery(name) {
 var breweryName;
 if(name) {
   breweryName=name;
} else{
   breweryName='Hipster Brew Co.';
}
}

正例:

代码语言:javascript
复制
functioncreateMicrobrewery(name) {
 var breweryName=name||'Hipster Brew Co.'
}

函数

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

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

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

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

反例:

代码语言:javascript
复制
functioncreateMenu(title, body, buttonText, cancellable) {
 ...
}

正例:

代码语言:javascript
复制
varmenuConfig={
 title: 'Foo',
 body: 'Bar',
 buttonText: 'Baz',
 cancellable: true
}

functioncreateMenu(menuConfig) {
 ...
}

函数功能的单一性

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

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

反例:

代码语言:javascript
复制
functionemailClients(clients) {
 clients.forEach(client=>{
   letclientRecord=database.lookup(client);
   if(clientRecord.isActive()) {
     email(client);
  }
});
}

正例:

代码语言:javascript
复制
functionemailClients(clients) {
 clients.forEach(client=>{
   emailClientIfNeeded(client);
});
}

functionemailClientIfNeeded(client) {
 if(isClientActive(client)) {
   email(client);
}
}

functionisClientActive(client) {
 letclientRecord=database.lookup(client);
 returnclientRecord.isActive();
}

函数名应明确表明其功能

反例:

代码语言:javascript
复制
functiondateAdd(date, month) {
 // ...
}

letdate=newDate();

// 很难理解dateAdd(date, 1)是什么意思
dateAdd(date, 1);

正例:

代码语言:javascript
复制
functiondateAddMonth(date, month) {
 // ...
}

letdate=newDate();
dateAddMonth(date, 1);

函数应该只做一层抽象

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

反例:

代码语言:javascript
复制
functionparseBetterJSAlternative(code) {
 letREGEXES=[
   // ...
];

 letstatements=code.split(' ');
 lettokens;
 REGEXES.forEach((REGEX) =>{
   statements.forEach((statement) =>{
     // ...
  })
});

 letast;
 tokens.forEach((token) =>{
   // lex...
});

 ast.forEach((node) =>{
   // parse...
})
}

正例:

代码语言:javascript
复制
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 弱类型的特点使得函数拥有更强的普适性。好好利用这一优点吧。

反例:

代码语言:javascript
复制
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);
});
}

正例:

代码语言:javascript
复制
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);
});
}

采用默认参数精简代码

反例:

代码语言:javascript
复制
functionwriteForumComment(subject, body) {
 subject=subject||'No Subject';
 body=body||'No text';
}

正例:

代码语言:javascript
复制
functionwriteForumComment(subject='No subject', body='No text') {
 ...
}

使用 Object.assign 设置默认对象

反例:

代码语言:javascript
复制
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);

正例:

代码语言:javascript
复制
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)作为函数参数

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

反例:

代码语言:javascript
复制
functioncreateFile(name, temp) {
 if(temp) {
   fs.create('./temp/'+name);
} else{
   fs.create(name);
}
}

正例:

代码语言:javascript
复制
functioncreateTempFile(name) {
 fs.create('./temp/'+name);
}

----------


functioncreateFile(name) {
 fs.create(name);
}

避免副作用

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

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

反例:

代码语言:javascript
复制
// 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'];

正例:

代码语言:javascript
复制
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 做简单的扩展显然是一个更棒的选择。

反例:

代码语言:javascript
复制
Array.prototype.diff=function(comparisonArray) {
 varvalues=[];
 varhash={};

 for(variofcomparisonArray) {
   hash[i] =true;
}

 for(variofthis) {
   if(!hash[i]) {
     values.push(i);
  }
}

 returnvalues;
}

正例:

代码语言:javascript
复制
classSuperArrayextendsArray{
 constructor(...args) {
   super(...args);
}

 diff(comparisonArray) {
   varvalues=[];
   varhash={};

   for(variofcomparisonArray) {
     hash[i] =true;
  }

   for(variofthis) {
     if(!hash[i]) {
       values.push(i);
    }
  }

   returnvalues;
}
}

采用函数式编程

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

反例:

代码语言:javascript
复制
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;
}

正例:

代码语言:javascript
复制
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);

封装判断条件

反例:

代码语言:javascript
复制
if(fsm.state==='fetching'&&isEmpty(listNode)) {
 /// ...
}

正例:

代码语言:javascript
复制
functionshouldShowSpinner(fsm, listNode) {
 returnfsm.state==='fetching'&&isEmpty(listNode);
}

if(shouldShowSpinner(fsmInstance, listNodeInstance)) {
 // ...
}

避免“否定情况”的判断

反例:

代码语言:javascript
复制
functionisDOMNodeNotPresent(node) {
 // ...
}

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

正例:

代码语言:javascript
复制
functionisDOMNodePresent(node) {
 // ...
}

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

避免条件判断

这看起来似乎不太可能。

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

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

反例:

代码语言:javascript
复制
classAirplane{
 //...
 getCruisingAltitude() {
   switch(this.type) {
     case'777':
       returngetMaxAltitude() -getPassengerCount();
     case'Air Force One':
       returngetMaxAltitude();
     case'Cessna':
       returngetMaxAltitude() -getFuelExpenditure();
  }
}
}

正例:

代码语言:javascript
复制
classAirplane{
 //...
}

classBoeing777extendsAirplane{
 //...
 getCruisingAltitude() {
   returngetMaxAltitude() -getPassengerCount();
}
}

classAirForceOneextendsAirplane{
 //...
 getCruisingAltitude() {
   returngetMaxAltitude();
}
}

classCessnaextendsAirplane{
 //...
 getCruisingAltitude() {
   returngetMaxAltitude() -getFuelExpenditure();
}
}

避免类型判断(part 1)

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

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

反例:

代码语言:javascript
复制
functiontravelToTexas(vehicle) {
 if(vehicleinstanceofBicycle) {
   vehicle.peddle(this.currentLocation, newLocation('texas'));
} elseif(vehicleinstanceofCar) {
   vehicle.drive(this.currentLocation, newLocation('texas'));
}
}

正例:

代码语言:javascript
复制
functiontravelToTexas(vehicle) {
 vehicle.move(this.currentLocation, newLocation('texas'));
}

避免类型判断(part 2)

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

反例:

代码语言:javascript
复制
functioncombine(val1, val2) {
 if(typeofval1=="number"&&typeofval2=="number"||
     typeofval1=="string"&&typeofval2=="string") {
   returnval1+val2;
} else{
   thrownewError('Must be of type String or Number');
}
}

正例:

代码语言:javascript
复制
functioncombine(val1, val2) {
 returnval1+val2;
}

避免过度优化

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

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

反例:

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

正例:

代码语言:javascript
复制
for(vari=0; i<list.length; i++) {
 // ...
}

删除无效的代码

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

反例:

代码语言:javascript
复制
functionoldRequestModule(url) {
 // ...
}

functionnewRequestModule(url) {
 // ...
}

varreq=newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

正例:

代码语言:javascript
复制
functionnewRequestModule(url) {
 // ...
}

varreq=newRequestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

对象和数据结构

使用 getters 和 setters

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

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

  1. 当需要对获取的对象属性执行额外操作时。
  2. 执行 set 时可以增加规则对要变量的合法性进行判断。
  3. 封装了内部逻辑。
  4. 在存取时可以方便的增加日志和错误处理。
  5. 继承该类时可以重载默认行为。
  6. 从服务器获取数据时可以进行懒加载。

反例:

代码语言:javascript
复制
classBankAccount{
 constructor() {
  this.balance=1000;
}
}

letbankAccount=newBankAccount();

// Buy shoes...
bankAccount.balance=bankAccount.balance-100;

正例:

代码语言:javascript
复制
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);

让对象拥有私有成员

可以通过闭包完成

反例:

代码语言:javascript
复制
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 Doe
deleteemployee.name;
console.log('Employee name: '+employee.getName()); // Employee name: undefined

正例:

代码语言:javascript
复制
varEmployee=(function() {
 functionEmployee(name) {
   this.getName=function() {
     returnname;
  };
}

 returnEmployee;
}());

varemployee=newEmployee('John Doe');
console.log('Employee name: '+employee.getName()); // Employee name: John Doe
deleteemployee.name;
console.log('Employee name: '+employee.getName()); // Employee name: John Doe

单一职责原则 (SRP)

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

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

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

反例:

代码语言:javascript
复制
classUserSettings{
 constructor(user) {
   this.user=user;
}

 changeSettings(settings) {
   if(this.verifyCredentials(user)) {
     // ...
  }
}

 verifyCredentials(user) {
   // ...
}
}

正例:

代码语言:javascript
复制
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 文件源码手动对其进行修改。

反例:

代码语言:javascript
复制
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) {
   // ...
}

}

正例:

代码语言:javascript
复制
classAjaxRequester{
 constructor() {
   this.HTTP_METHODS=['POST', 'PUT', 'GET'];
}

 get(url) {
   // ...
}

 addHTTPMethod(method) {
   this.HTTP_METHODS.push(method);
}
}

利斯科夫替代原则 (LSP)

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

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

反例:

代码语言:javascript
复制
classRectangle{
 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);

正例:

代码语言:javascript
复制
classShape{
 constructor() {}

 setColor(color) {
   // ...
}

 render(area) {
   // ...
}
}

classRectangleextendsShape{
 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 中,当一个类需要许多参数设置才能生成一个对象时,或许大多时候不需要设置这么多的参数。此时减少对配置参数数量的需求是有益的。

反例:

代码语言:javascript
复制
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.
 // ...
});

正例:

代码语言:javascript
复制
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. 抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口。

反例:

代码语言:javascript
复制
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();

正例:

代码语言:javascript
复制
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。

反例:

代码语言:javascript
复制
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() {};

正例:

代码语言:javascript
复制
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,能够方便的将类需要执行的多个方法链接起来。

反例:

代码语言:javascript
复制
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('
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京程一灯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JavaScript 风格指南
    • 目录
      • 介绍
        • 变量
          • 使用有意义,可读性好的变量名
          • 使用 ES6 的 const 定义常量
          • 对功能类似的变量名采用统一的命名风格
          • 使用易于检索名称
          • 使用说明变量(即有意义的变量名)
          • 不要绕太多的弯子
          • 避免重复的描述
          • 避免无意义的条件判断
        • 函数
          • 函数参数 (理想情况下应不超过 2 个)
          • 函数功能的单一性
          • 函数名应明确表明其功能
          • 函数应该只做一层抽象
          • 移除重复的代码
          • 采用默认参数精简代码
          • 使用 Object.assign 设置默认对象
          • 不要使用标记(Flag)作为函数参数
          • 避免副作用
          • 不要写全局函数
          • 采用函数式编程
          • 封装判断条件
          • 避免“否定情况”的判断
          • 避免条件判断
          • 避免类型判断(part 1)
          • 避免类型判断(part 2)
          • 避免过度优化
          • 删除无效的代码
        • 对象和数据结构
          • 使用 getters 和 setters
          • 让对象拥有私有成员
          • 单一职责原则 (SRP)
          • 开/闭原则 (OCP)
          • 利斯科夫替代原则 (LSP)
          • 接口隔离原则 (ISP)
          • 依赖反转原则 (DIP)
          • 使用 ES6 的 classes 而不是 ES5 的 Function
          • 使用方法链
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档