# ä»£ç æ´æ´ç JavaScript ## ç®å½ 1. [ç®ä»](#ç®ä») 2. [åé](#åé) 3. [彿°](#彿°) 4. [å¯¹è±¡åæ°æ®ç»æ](#å¯¹è±¡åæ°æ®ç»æ) 5. [ç±»](#ç±») 6. [SOLID](#solid) 7. [æµè¯](#æµè¯) 8. [å¹¶å](#å¹¶å) 9. [é误å¤ç](#é误å¤ç) 10. [æ ¼å¼å](#æ ¼å¼å) 11. [注é](#注é) 12. [Translation](#translation) ## ç®ä»  å°æºèª Robert C. Martin ç [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) ç软件工ç¨ååéé å° JavaScript ã è¿ä¸æ¯ä¸ä¸ªä»£ç 飿 ¼æåï¼ å®æ¯ä¸ä¸ªä½¿ç¨ JavaScript æ¥ç产 å¯è¯»çï¼ å¯éç¨çï¼ ä»¥åå¯éæç软件çæåã è¿éçæ¯ä¸é¡¹ååé½ä¸æ¯å¿ é¡»éµå®çï¼ çè³åªææ´å°çè½å¤è¢«å¹¿æ³è®¤å¯ã è¿äºä» ä» æ¯æåèå·²ï¼ ä½æ¯å´æ¯ *Clean Code* ä½è å¤å¹´ç»éªçç»æ¶ã æä»¬ç软件工ç¨è¡ä¸åªæççç 50 å¹´ï¼ ä¾ç¶æå¾å¤è¦æä»¬å»å¦ä¹ ã å½è½¯ä»¶æ¶æä¸å»ºçæ¶æä¸æ ·å¤èæ¶ï¼ ä¹è®¸æä»¬å°ä¼æç¡¬æ§çè§åå»éµå®ã èç°å¨ï¼ 让è¿äºæååä¸ºä½ åä½ çå¢éç产ç JavaScript 代ç ç è´¨éçæ åã è¿æä¸ä»¶äºï¼ ç¥éè¿äºæåå¹¶ä¸è½é©¬ä¸è®©ä½ æä¸ºä¸ä¸ªæ´å åºè²ç软件å¼åè ï¼ å¹¶ä¸ä½¿ç¨å®ä»¬å·¥ä½å¤å¹´ä¹å¹¶ 䏿å³çä½ ä¸åä¼ç¯éè¯¯ã æ¯ä¸æ®µä»£ç æå¼å§é½æ¯èç¨¿ï¼ åæ¹¿ç²å䏿 ·è¢«æé ææç»çå½¢æã æå彿们 åææ¡£ä»¬ä¸èµ·å®¡æ¥ä»£ç æ¶æ¸ é¤é£äºä¸å®åä¹å¤, ä¸è¦å 为æåéè¦æ¹åçè稿代ç èèªè´£ï¼ èæ¯å¯¹é£äºä»£ ç 䏿ã ## **åé** ### ä½¿ç¨ææä¹å¹¶ä¸å¯è¯»çåéåç§° **ä¸å¥½çï¼** ```javascript const yyyymmdstr = moment().format('YYYY/MM/DD'); ``` **好çï¼** ```javascript const currentDate = moment().format('YYYY/MM/DD'); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 为ç¸åç±»åçåé使ç¨ç¸åçè¯æ± **ä¸å¥½çï¼** ```javascript getUserInfo(); getClientData(); getCustomerRecord(); ``` **好çï¼** ```javascript getUser(); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 使ç¨å¯æç´¢çåç§° æä»¬è¦é 读çä»£ç æ¯è¦åç代ç å¤å¾å¤ï¼ æä»¥æä»¬ååºç代ç çå¯è¯»æ§åå¯æç´¢æ§æ¯å¾éè¦çã ä½¿ç¨æ²¡æ æä¹çåéåå°ä¼å¯¼è´æä»¬çç¨åºé¾äºçè§£ï¼ å°ä¼ä¼¤å®³æä»¬ç读è ï¼ æä»¥è¯·ä½¿ç¨å¯æç´¢çåéåã 类似 [buddy.js](https://github.com/danielstjules/buddy.js) å [ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) çå·¥å ·å¯ä»¥å¸®å©æä»¬æ¾å°æªå½åç常éã **ä¸å¥½çï¼** ```javascript // è¹ï¼ 86400000 æ¯ä»ä¹é¬¼ï¼ setTimeout(blastOff, 86400000); ``` **好çï¼** ```javascript // å°å®ä»¬å£°æä¸ºå ¨å±å¸¸é `const` ã const MILLISECONDS_IN_A_DAY = 86400000; setTimeout(blastOff, MILLISECONDS_IN_A_DAY); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 使ç¨è§£éæ§çåé **ä¸å¥½çï¼** ```javascript const address = 'One Infinite Loop, Cupertino 95014'; const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]); ``` **好çï¼** ```javascript const address = 'One Infinite Loop, Cupertino 95014'; const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; const [, city, zipCode] = address.match(cityZipCodeRegex) || []; saveCityZipCode(city, zipCode); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å å¿çæ å° æ¾ç¤ºæ¯é弿´å¥½ **ä¸å¥½çï¼** ```javascript const locations = ['Austin', 'New York', 'San Francisco']; locations.forEach((l) => { doStuff(); doSomeOtherStuff(); // ... // ... // ... // ççï¼ `l` æ¯å¥ï¼ dispatch(l); }); ``` **好çï¼** ```javascript const locations = ['Austin', 'New York', 'San Francisco']; locations.forEach((location) => { doStuff(); doSomeOtherStuff(); // ... // ... // ... dispatch(location); }); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 䏿·»å ä¸å¿ è¦çä¸ä¸æ å¦æä½ çç±»å/å¯¹è±¡åææä¹ï¼ ä¸è¦å¨åéåä¸åéå¤ã **ä¸å¥½çï¼** ```javascript const Car = { carMake: 'Honda', carModel: 'Accord', carColor: 'Blue' }; function paintCar(car) { car.carColor = 'Red'; } ``` **好çï¼** ```javascript const Car = { make: 'Honda', model: 'Accord', color: 'Blue' }; function paintCar(car) { car.color = 'Red'; } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 使ç¨é»è®¤åéæ¿ä»£çè·¯è¿ç®ææ¡ä»¶ **ä¸å¥½çï¼** ```javascript function createMicrobrewery(name) { const breweryName = name || 'Hipster Brew Co.'; // ... } ``` **好çï¼** ```javascript function createMicrobrewery(breweryName = 'Hipster Brew Co.') { // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **彿°** ### 彿°åæ° (ä¸¤ä¸ªä»¥ä¸æçæ³) éå¶å½æ°åæ°çä¸ªæ°æ¯é常éè¦çï¼ å ä¸ºè¿æ ·å°ä½¿ä½ ç彿°å®¹æè¿è¡æµè¯ã 䏿¦è¶ è¿ä¸ä¸ªåæ°å°ä¼å¯¼è´ç» åçç¸ï¼ å ä¸ºä½ ä¸å¾ä¸ç¼å大éé对æ¯ä¸ªåæ°çæµè¯ç¨ä¾ã 没æåæ°æ¯æçæ³çï¼ ä¸ä¸ªæè ä¸¤ä¸ªåæ°ä¹æ¯å¯ä»¥çï¼ ä¸ä¸ªåæ°åºè¯¥é¿å ï¼ è¶ è¿ä¸ä¸ªåºè¯¥è¢«éæã éå¸¸ï¼ å¦æä½ æä¸ä¸ªè¶ è¿ä¸¤ä¸ªå½æ°çåæ°ï¼ é£å°±æå³çä½ ç彿°å°è¯å太å¤çäºæ ã 妿䏿¯ï¼ 夿°æ åµä¸ä¸ä¸ª æ´é«çº§å¯¹è±¡å¯è½ä¼æ»¡è¶³éæ±ã ç±äº JavaScript å 许æä»¬ä¸å®ä¹ç±»å/模æ¿å°±å¯ä»¥åå»ºå¯¹è±¡ï¼ å½ä½ åç°ä½ èªå·±éè¦å¤§éçåæ°æ¶ï¼ ä½ å¯ä»¥ä½¿ç¨ä¸ä¸ªå¯¹è±¡ã **ä¸å¥½çï¼** ```javascript function createMenu(title, body, buttonText, cancellable) { // ... } ``` **好çï¼** ```javascript const menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }; function createMenu(config) { // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 彿°åºå½åªåä¸ä»¶äºæ è¿æ¯è½¯ä»¶å·¥ç¨ä¸æéè¦ç䏿¡è§åï¼ å½å½æ°éè¦åæ´å¤çäºæ æ¶ï¼ å®ä»¬å°ä¼æ´é¾è¿è¡ç¼åã æµè¯åæ¨çã å½ä½ è½å°ä¸ä¸ªå½æ°é离å°åªæä¸ä¸ªå¨ä½ï¼ ä»ä»¬å°è½å¤è¢«å®¹æçè¿è¡éæå¹¶ä¸ä½ ç代ç å°ä¼æ´å®¹æé 读ã å¦ æä½ ä¸¥æ ¼éµå®æ¬æåä¸çè¿ä¸æ¡ï¼ ä½ å°ä¼é¢å äºè®¸å¤å¼åè ã **ä¸å¥½çï¼** ```javascript function emailClients(clients) { clients.forEach((client) => { const clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); } ``` **好çï¼** ```javascript function emailClients(clients) { clients .filter(isClientActive) .forEach(email); } function isClientActive(client) { const clientRecord = database.lookup(client); return clientRecord.isActive(); } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 彿°åç§°åºè¯¥è¯´æå®è¦åä»ä¹ **ä¸å¥½çï¼** ```javascript function addToDate(date, month) { // ... } const date = new Date(); // å¾é¾ä»å½æ°åçåºå äºä»ä¹ addToDate(date, 1); ``` **好çï¼** ```javascript function addMonthToDate(month, date) { // ... } const date = new Date(); addMonthToDate(1, date); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 彿°åºè¯¥åªæä¸ä¸ªæ½è±¡çº§å« å½å¨ä½ ç彿°ä¸æå¤äºä¸ä¸ªæ½è±¡çº§å«æ¶ï¼ ä½ ç彿°é常åäºå¤ªå¤äºæ ã æå彿°å°ä¼æåéç¨æ§åæµè¯æ§ã **ä¸å¥½çï¼** ```javascript function parseBetterJSAlternative(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { // ... }); }); const ast = []; tokens.forEach((token) => { // lex... }); ast.forEach((node) => { // parse... }); } ``` **好çï¼** ```javascript function tokenize(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { tokens.push( /* ... */ ); }); }); return tokens; } function lexer(tokens) { const ast = []; tokens.forEach((token) => { ast.push( /* ... */ ); }); return ast; } function parseBetterJSAlternative(code) { const tokens = tokenize(code); const ast = lexer(tokens); ast.forEach((node) => { // parse... }); } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ç§»é¤åä½ä»£ç ç«å°½ä½ çå ¨åå»é¿å åä½ä»£ç ã åä½ä»£ç æ¯ä¸å¥½çï¼ å ä¸ºå®æå³çå½ä½ éè¦ä¿®æ¹ä¸äºé»è¾æ¶ä¼æå¤ä¸ªå°æ¹ éè¦ä¿®æ¹ã æ³è±¡ä¸ä¸ä½ å¨ç»è¥ä¸å®¶é¤é¦ï¼ ä½ éè¦è®°å½ææçåºå西红æ¿ï¼ æ´è±ï¼ 大èï¼ åç§é¦æççã å¦æä½ æå¤ 个记å½åè¡¨ï¼ å½ä½ ç¨è¥¿çº¢æ¿åä¸éèæ¶ä½ å¾æ´æ°å¤ä¸ªå表ã å¦æä½ åªæä¸ä¸ªåè¡¨ï¼ å°±åªæä¸ä¸ªå°æ¹éè¦æ´ æ°ï¼ ä½ æåä½ä»£ç é常æ¯å ä¸ºä½ æä¸¤ä¸ªæå¤ä¸ªç¨å¾®ä¸åçä¸è¥¿ï¼ å®ä»¬å ±äº«å¤§é¨åï¼ ä½æ¯å®ä»¬çä¸åä¹å¤è¿«ä½¿ä½ 使 ç¨ä¸¤ä¸ªææ´å¤ç¬ç«ç彿°æ¥å¤ç大é¨åç¸åçä¸è¥¿ã ç§»é¤åä½ä»£ç æå³çå建ä¸ä¸ªå¯ä»¥å¤çè¿äºä¸åä¹å¤ç æ½è±¡ç彿°/模å/ç±»ã 让è¿ä¸ªæ½è±¡æ£ç¡®æ¯å ³é®çï¼ è¿æ¯ä¸ºä»ä¹è¦ä½ éµå¾ª *Classes* é£ä¸ç« ç SOLID çåå ã ä¸å¥½çæ½è±¡æ¯å ä½ä»£ç æ´å·®ï¼ æä»¥è¦è°¨æ è¡äºã æ¢ç¶å·²ç»è¿ä¹è¯´äºï¼ å¦æä½ è½å¤ååºä¸ä¸ªå¥½çæ½è±¡ï¼ æå»åã ä¸è¦éå¤ ä½ èªå·±ï¼ å¦åä½ ä¼åç°å½ä½ è¦ä¿®æ¹ä¸ä¸ªä¸è¥¿æ¶æ¶å»éè¦ä¿®æ¹å¤ä¸ªå°æ¹ã **ä¸å¥½çï¼** ```javascript function showDeveloperList(developers) { developers.forEach((developer) => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink }; render(data); }); } function showManagerList(managers) { managers.forEach((manager) => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio }; render(data); }); } ``` **好çï¼** ```javascript function showList(employees) { employees.forEach((employee) => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); let portfolio = employee.getGithubLink(); if (employee.type === 'manager') { portfolio = employee.getMBAProjects(); } const data = { expectedSalary, experience, portfolio }; render(data); }); } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä½¿ç¨ Object.assign 设置é»è®¤å¯¹è±¡ **ä¸å¥½çï¼** ```javascript const menuConfig = { title: null, body: 'Bar', buttonText: null, cancellable: true }; function createMenu(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 const menuConfig = { title: 'Order', // User did not include 'body' key buttonText: 'Send', cancellable: true }; function createMenu(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); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¸è¦ä½¿ç¨æ è®°ä½åä¸ºå½æ°åæ° æ è®°ä½æ¯åè¯ä½ çç¨æ·è¿ä¸ªå½æ°åäºä¸åªä¸ä»¶äºæ ã 彿°åºè¯¥åªåä¸ä»¶äºæ ã å¦æä½ ç彿°å 为ä¸ä¸ªå¸å°å¼ åºç°ä¸åç代ç è·¯å¾ï¼ 请æåå®ä»¬ã **ä¸å¥½çï¼** ```javascript function createFile(name, temp) { if (temp) { fs.create(`./temp/${name}`); } else { fs.create(name); } } ``` **好çï¼** ```javascript function createFile(name) { fs.create(name); } function createTempFile(name) { createFile(`./temp/${name}`); } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å å¯ä½ç¨ 妿ä¸ä¸ªå½æ°åäºé¤æ¥åä¸ä¸ªå¼ç¶åè¿åä¸ä¸ªå¼æå¤ä¸ªå¼ä¹å¤çä»»ä½äºæ ï¼ å®å°ä¼äº§çå¯ä½ç¨ï¼ å®å¯è½æ¯ åå ¥ä¸ä¸ªæä»¶ï¼ ä¿®æ¹ä¸ä¸ªå ¨å±åéï¼ æè æå¤çæä½ ææçé±è¿æ¥å°ä¸ä¸ªéç人é£éã ç°å¨å¨ä½ çç¨åºä¸ç¡®å®å¶å°éè¦å¯ä½ç¨ï¼ å°±åä¸é¢ç代ç ï¼ ä½ ä¹è®¸éè¦åå ¥å°ä¸ä¸ªæä»¶ï¼ ä½ éè¦åçæ¯é ä¸åä½ è¦åçäºæ ï¼ ä¸è¦è®©å¤ä¸ªå½æ°æè ç±»åå ¥ä¸ä¸ªç¹å®çæä»¶ï¼ ç¨ä¸ä¸ªæå¡æ¥å®ç°å®ï¼ ä¸ä¸ªå¹¶ä¸åªæä¸ 个ã éç¹æ¯é¿å è¿äºå¸¸è§çæç¯çéè¯¯ï¼ æ¯å¦å¨å¯¹è±¡ä¹é´å ±äº«ç¶æèä¸ä½¿ç¨ä»»ä½ç»æï¼ 使ç¨ä»»ä½å°æ¹é½å¯ä»¥åå ¥ çå¯åçæ°æ®ç±»åï¼ æ²¡æéä¸å导è´å¯ä½ç¨ã å¦æä½ è½åå°è¿äºï¼ é£ä¹ä½ å°ä¼æ¯å ¶å®çç å大忴å 幸ç¦ã **ä¸å¥½çï¼** ```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. // 妿æä»¬æå¦ä¸ä¸ªå½æ°ä½¿ç¨è¿ä¸ª name ï¼ ç°å¨å®åºè¯¥æ¯ä¸ä¸ªæ°ç»ï¼ è¿å¯è½ä¼åºç°é误ã let name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott']; ``` **好çï¼** ```javascript function splitIntoFirstAndLastName(name) { return name.split(' '); } const name = 'Ryan McDermott'; const newName = splitIntoFirstAndLastName(name); console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott']; ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¸è¦åå ¥å ¨å±å½æ° 污æå ¨å±å¨ JavaScript 䏿¯ä¸ä¸ªä¸å¥½çåæ³ï¼ å ä¸ºä½ å¯è½ä¼åå¦å¤ä¸ä¸ªç±»åºå²çªï¼ ä½ ç API çç¨æ· å¯è½ä¸å¤èªæï¼ ç´å°ä»ä»¬å¾å°å¨ç产ç¯å¢å¾å°ä¸ä¸ªå¼å¸¸ã 让æä»¬æ¥èèè¿æ ·ä¸ä¸ªä¾åï¼ åè®¾ä½ è¦æ©å± JavaScript ç åç `Array` ï¼ æ·»å ä¸ä¸ªå¯ä»¥æ¾ç¤ºä¸¤ä¸ªæ°ç»çä¸åä¹å¤ç `diff` æ¹æ³ï¼ ä½ å¯ä»¥å¨ `Array.prototype` ä¸åä¸ä¸ªæ°çæ¹æ³ï¼ 使¯å®å¯è½ä¼åå°è¯åç¸åäºæ çå ¶å®ç±»åºåçå²çªã 妿æ å¦å¤ä¸ä¸ªç±»åºä» ä» ä½¿ç¨ `diff` æ¹æ³æ¥æ¥æ¾æ°ç»ç第ä¸ä¸ªå ç´ åæåä¸ä¸ªå ç´ ä¹é´çä¸åä¹å¤å¢ï¼ è¿å°±æ¯ 为ä»ä¹ä½¿ç¨ ES2015/ES6 çç±»æ¯ä¸ä¸ªæ´å¥½çåæ³çåå ï¼ åªè¦ç®åçæ©å±å ¨å±ç `Array` å³å¯ã **ä¸å¥½çï¼** ```javascript Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); }; ``` **好çï¼** ```javascript class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 彿°å¼ç¼ç¨ä¼äºæä»¤å¼ç¼ç¨ JavaScript 䏿¯ Haskell é£ç§æ¹å¼ç彿°å¼è¯è¨ï¼ 使¯å®æå®ç彿°å¼é£æ ¼ã 彿°å¼è¯è¨æ´å ç®æ´ 并䏿´å®¹æè¿è¡æµè¯ï¼ å½ä½ å¯ä»¥ä½¿ç¨å½æ°å¼ç¼ç¨é£æ ¼æ¶è¯·å°½æ 使ç¨ã **ä¸å¥½çï¼** ```javascript const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; let totalOutput = 0; for (let i = 0; i < programmerOutput.length; i++) { totalOutput += programmerOutput[i].linesOfCode; } ``` **好çï¼** ```javascript const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; const totalOutput = programmerOutput .map((programmer) => programmer.linesOfCode) .reduce((acc, linesOfCode) => acc + linesOfCode, 0); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### å°è£ æ¡ä»¶è¯å¥ **ä¸å¥½çï¼** ```javascript if (fsm.state === 'fetching' && isEmpty(listNode)) { // ... } ``` **好çï¼** ```javascript function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode); } if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å è´é¢æ¡ä»¶ **ä¸å¥½çï¼** ```javascript function isDOMNodeNotPresent(node) { // ... } if (!isDOMNodeNotPresent(node)) { // ... } ``` **好çï¼** ```javascript function isDOMNodePresent(node) { // ... } if (isDOMNodePresent(node)) { // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å æ¡ä»¶è¯å¥ è¿çèµ·æ¥ä¼¼ä¹æ¯ä¸ä¸ªä¸å¯è½çä»»å¡ã ç¬¬ä¸æ¬¡å¬å°è¿ä¸ªæ¶ï¼ 夿°äººä¼è¯´ï¼ âæ²¡æ `if` è¯å¥è¿è½æææå¹² å¥å¢âï¼ çæ¡æ¯å¤æ°æ åµä¸ä½ å¯ä»¥ä½¿ç¨å¤ææ¥å®æåæ ·çä»»å¡ã 第äºä¸ªé®é¢éå¸¸æ¯ â好äºï¼ é£ä¹å徿£ï¼ 使¯æä¸ºä»ä¹æ³è¦é£æ ·åå¢âï¼ çæ¡æ¯æä»¬å¦å°çä¸ä¸æ¡ä»£ç æ´æ´ä¹éççå¿µï¼ ä¸ä¸ªå½æ°åºå½åªåä¸ä»¶äºæ ã å½ä½ æä½¿ç¨ `if` è¯å¥çç±»/彿°æ¯ï¼ ä½ å¨åè¯ä½ çç¨æ·ä½ ç彿°åäºä¸æ¢ä¸ä»¶äºæ ã è®°ä½ï¼ åªåä¸ä»¶ äºæ ã **ä¸å¥½çï¼** ```javascript class Airplane { // ... getCruisingAltitude() { switch (this.type) { case '777': return this.getMaxAltitude() - this.getPassengerCount(); case 'Air Force One': return this.getMaxAltitude(); case 'Cessna': return this.getMaxAltitude() - this.getFuelExpenditure(); } } } ``` **好çï¼** ```javascript class Airplane { // ... } class Boeing777 extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getPassengerCount(); } } class AirForceOne extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude(); } } class Cessna extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getFuelExpenditure(); } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å ç±»åæ£æ¥ (part 1) JavaScript æ¯æ ç±»åçï¼ è¿æå³çä½ ç彿°è½æ¥åä»»ä½ç±»åçåæ°ã 使¯ææ¶åä¼è¢«è¿ç§èªç±å¬ä¼¤ï¼ äºæ¯åå°è¯å¨ä½ ç彿°ä¸åç±»åæ£æ¥ã æå¾å¤ç§æ¹å¼æ¥é¿å è¿ä¸ªï¼ 第ä¸ä¸ªè¦èèçæ¯ä¸è´ç API ã **ä¸å¥½çï¼** ```javascript function travelToTexas(vehicle) { if (vehicle instanceof Bicycle) { vehicle.peddle(this.currentLocation, new Location('texas')); } else if (vehicle instanceof Car) { vehicle.drive(this.currentLocation, new Location('texas')); } } ``` **好çï¼** ```javascript function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas')); } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å ç±»åæ£æ¥ (part 2) å¦æä½ ä½¿ç¨åå§çåç¬¦ä¸²ã æ´æ°åæ°ç»ï¼ å¹¶ä¸ä½ ä¸è½ä½¿ç¨å¤æï¼ 使¯ä½ ä¾ç¶æè§å°æç±»åæ£æ¥çéè¦ï¼ ä½ åºè¯¥èèä½¿ç¨ TypeScript ã 宿¯ä¸ä¸ªå¸¸è§ JavaScript çä¼ç§çæ¿ä»£åï¼ å 为å®å¨æ åç JavaScript è¯æ³ä¹ä¸ä¸ºä½ æä¾éæç±»åã å¯¹å¸¸è§ JavaScript åäººå·¥ç±»åæ£æ¥çé®é¢æ¯éè¦å¤§éçåè¯æ¥ä»¿é ç±»åå® å ¨èä¸ç¼ºå¤±å¯è¯»æ§ã ä¿æä½ ç JavaScript ç®æ´ï¼ ç¼åè¯å¥½çæµè¯ï¼ å¹¶æè¯å¥½ç代ç 审é ï¼ å¦åä½¿ç¨ TypeScript ï¼å°±åæè¯´çï¼ å®æ¯ä¸ä¸ªä¼å¤§çæ¿ä»£åï¼æ¥å®æè¿äºã **ä¸å¥½çï¼** ```javascript function combine(val1, val2) { if (typeof val1 === 'number' && typeof val2 === 'number' || typeof val1 === 'string' && typeof val2 === 'string') { return val1 + val2; } throw new Error('Must be of type String or Number'); } ``` **好çï¼** ```javascript function combine(val1, val2) { return val1 + val2; } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¸è¦è¿åº¦ä¼å ç°ä»£åæµè§å¨è¿è¡æ¶å¨å¹åå大éçä¼åï¼ å¨å¤§å¤æ°çæ¶é´ï¼ åä¼åå°±æ¯å¨æµªè´¹ä½ çæ¶é´ã [è¿äºæ¯å¥½ç èµæº](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers)ï¼ ç¨æ¥ æ¥çé£äºå°æ¹éè¦ä¼åã 为è¿äºèä¼åï¼ ç´å°ä»ä»¬è¢«ä¿®æ£ã **ä¸å¥½çï¼** ```javascript // On old browsers, each iteration with uncached `list.length` would be costly // because of `list.length` recomputation. In modern browsers, this is optimized. // 卿§çæµè§å¨ä¸ï¼ æ¯æ¬¡å¾ªç¯ `list.length` 齿²¡æè¢«ç¼åï¼ ä¼å¯¼è´ä¸å¿ è¦çå¼éï¼ å 为è¦éæ°è®¡ // ç® `list.length` ã å¨ç°ä»£åæµè§å¨ä¸ï¼ è¿ä¸ªå·²ç»è¢«ä¼åäºã for (let i = 0, len = list.length; i < len; i++) { // ... } ``` **好çï¼** ```javascript for (let i = 0; i < list.length; i++) { // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ç§»é¤åµå°¸ä»£ç 嵿»ä»£ç ååä½ä»£ç åæ ·ç³ç³ã 没æçç±å¨ä»£ç åºä¸ä¿åå®ã 妿å®ä¸ä¼è¢«è°ç¨ï¼ å°±å æå®ã å½ä½ éè¦ å®æ¶ï¼ å®ä¾ç¶ä¿åå¨çæ¬åå²è®°å½ä¸ã **ä¸å¥½çï¼** ```javascript function oldRequestModule(url) { // ... } function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` **好çï¼** ```javascript function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **å¯¹è±¡åæ°æ®ç»æ** ### ä½¿ç¨ getters å setters JavaScript æ²¡ææ¥å£æç±»åï¼ æä»¥åæè¿ä¸ªæ¨¡å¼æ¯é常å°é¾çï¼ å 为æä»¬æ²¡æ `public` å `private` å ³é®åã æ£å ä¸ºå¦æ¤ï¼ ä½¿ç¨ getters å setters æ¥è®¿é®å¯¹è±¡ä¸çæ°æ®æ¯ç®åçå¨ä¸ä¸ªå¯¹è±¡ä¸æ¥æ¾å±æ§ è¦å¥½å¾å¤ã â为ä»ä¹ï¼â ä½ å¯è½ä¼é®ï¼ 好å§ï¼ åå 请çä¸é¢çåè¡¨ï¼ * å½ä½ æ³å¨è·åä¸ä¸ªå¯¹è±¡å±æ§çèååæ´å¤çäºæ æ¶ï¼ ä½ ä¸éè¦å¨ä»£ç åºä¸æ¥æ¾åä¿®æ¹æ¯ä¸å¤è®¿é®ï¼ * ä½¿ç¨ `set` å¯ä»¥è®©æ·»å éªè¯åå¾å®¹æï¼ * å°è£ å é¨å®ç°ï¼ * ä½¿ç¨ getting å setting æ¶ï¼ å®¹ææ·»å æ¥å¿åé误å¤çï¼ * ç»§æ¿è¿ä¸ªç±»ï¼ ä½ å¯ä»¥éåé»è®¤åè½ï¼ * ä½ å¯ä»¥å»¶è¿å 载对象ç屿§ï¼ æ¯å¦è¯´ä»æå¡å¨è·åã **ä¸å¥½çï¼** ```javascript class BankAccount { constructor() { this.balance = 1000; } } const bankAccount = new BankAccount(); // Buy shoes... bankAccount.balance -= 100; ``` **好çï¼** ```javascript class BankAccount { constructor(balance = 1000) { this._balance = balance; } // It doesn't have to be prefixed with `get` or `set` to be a getter/setter set balance(amount) { if (verifyIfAmountCanBeSetted(amount)) { this._balance = amount; } } get balance() { return this._balance; } verifyIfAmountCanBeSetted(val) { // ... } } const bankAccount = new BankAccount(); // Buy shoes... bankAccount.balance -= shoesPrice; // Get balance let balance = bankAccount.balance; ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### è®©å¯¹è±¡æ¥æç§ææå è¿ä¸ªå¯ä»¥éè¿éå æ¥å®ç°ï¼é对 ES5 ææ´ä½ï¼ã **ä¸å¥½çï¼** ```javascript const Employee = function(name) { this.name = name; }; Employee.prototype.getName = function getName() { return this.name; }; const employee = new Employee('John Doe'); console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe delete employee.name; console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined ``` **好çï¼** ```javascript const Employee = function (name) { this.getName = function getName() { return name; }; }; const employee = new Employee('John Doe'); console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe delete employee.name; console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **ç±»** ### ES2015/ES6 ç±»ä¼å ä¸ ES5 çº¯å½æ° å¾é¾ä¸ºç»å ¸ç ES5 ç±»å建å¯è¯»ççç»§æ¿ã æé åæ¹æ³å®ä¹ã å¦æä½ éè¦ç»§æ¿ï¼å¹¶ä¸æå°å¥æªä¸ºå¥ä½ ä¸é è¦ï¼ï¼ åä¼å ç¨ ES2015/ES6çç±»ã ä¸è¿ï¼ çå°ç彿°ä¼å äºç±»ï¼ ç´å°ä½ åç°ä½ éè¦æ´å¤§å¹¶ä¸æ´å¤æç 对象ã **ä¸å¥½çï¼** ```javascript const Animal = function(age) { if (!(this instanceof Animal)) { throw new Error('Instantiate Animal with `new`'); } this.age = age; }; Animal.prototype.move = function move() {}; const Mammal = function(age, furColor) { if (!(this instanceof Mammal)) { throw new Error('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 liveBirth() {}; const Human = function(age, furColor, languageSpoken) { if (!(this instanceof Human)) { throw new Error('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 speak() {}; ``` **好çï¼** ```javascript class Animal { constructor(age) { this.age = age; } move() { /* ... */ } } class Mammal extends Animal { constructor(age, furColor) { super(age); this.furColor = furColor; } liveBirth() { /* ... */ } } class Human extends Mammal { constructor(age, furColor, languageSpoken) { super(age, furColor); this.languageSpoken = languageSpoken; } speak() { /* ... */ } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä½¿ç¨æ¹æ³é¾ è¿ä¸ªæ¨¡å¼å¨ JavaScript 䏿¯é常æç¨çï¼ å¹¶ä¸ä½ å¯ä»¥å¨è®¸å¤ç±»åºæ¯å¦ jQuery å Lodash ä¸è§å°ã å®ä½¿ä½ ç代ç åå¾å¯æè¡¨ç°åï¼ å¹¶åå°å°å¦ã å 为è¿ä¸ªåå ï¼ æè¯´ï¼ ä½¿ç¨æ¹æ³é¾ç¶ååççä½ ç代ç ä¼åå¾å¤ä¹ç®æ´ã å¨ä½ çç±»ï¼æ¹æ³ä¸ï¼ ç®åç卿¯ä¸ªæ¹æ³çæåè¿å `this` ï¼ ç¶åä½ å°±è½æè¿ä¸ªç±»ç å ¶å®æ¹æ³é¾å¨ä¸èµ·ã **ä¸å¥½çï¼** ```javascript class Car { constructor() { this.make = 'Honda'; this.model = 'Accord'; this.color = 'white'; } setMake(make) { this.make = make; } setModel(model) { this.model = model; } setColor(color) { this.color = color; } save() { console.log(this.make, this.model, this.color); } } const car = new Car(); car.setColor('pink'); car.setMake('Ford'); car.setModel('F-150'); car.save(); ``` **好çï¼** ```javascript class Car { constructor() { this.make = 'Honda'; this.model = 'Accord'; this.color = 'white'; } setMake(make) { this.make = make; // NOTE: Returning this for chaining return this; } setModel(model) { this.model = model; // NOTE: Returning this for chaining return this; } setColor(color) { this.color = color; // NOTE: Returning this for chaining return this; } save() { console.log(this.make, this.model, this.color); // NOTE: Returning this for chaining return this; } } const car = new Car() .setColor('pink') .setMake('Ford') .setModel('F-150') .save(); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ç»åä¼å äºç»§æ¿ æ£å¦[*设计模å¼å人帮*](https://en.wikipedia.org/wiki/Design_Patterns)æè¿°ï¼ 妿å¯è½ï¼ ä½ åºè¯¥ä¼å 使ç¨ç»åè䏿¯ç»§æ¿ã æè®¸å¤å¥½ççç±å»ä½¿ç¨ç»§æ¿ï¼ ä¹æè®¸å¤å¥½ççç±å»ä½¿ç¨ç»åãè¿ä¸ªæ ¼è¨ çéç¹æ¯ï¼ å¦æä½ æ¬è½çè§ç¹æ¯ç»§æ¿ï¼ é£ä¹è¯·æ³ä¸ä¸ç»åè½å¦æ´å¥½çä¸ºä½ çé®é¢å»ºæ¨¡ã å¾å¤æ åµä¸å®çç å¯ä»¥ã é£ä¹ä½ ä¹è®¸ä¼è¿æ ·æ³ï¼ âæä»ä¹æ¶åæ¹ä½¿ç¨ç»§æ¿ï¼â è¿åå³äºä½ æä¸çé®é¢ï¼ ä¸è¿è¿å¿æä¸ä¸ªåæ ·çå表说 æä»ä¹æ¶åç»§æ¿æ¯ç»åæ´å¥½ç¨ï¼ 1. ä½ çç»§æ¿è¡¨ç¤º"æ¯ä¸ä¸ª"çå ³ç³»è䏿¯"æä¸ä¸ª"çå ³ç³»ï¼äººç±»->å¨ç© vs ç¨æ·->ç¨æ·è¯¦æ ï¼ï¼ 2. ä½ å¯ä»¥éç¨æ¥èªåºç±»ç代ç ï¼äººå¯ä»¥åææå¨ç©ä¸æ ·è¡å¨ï¼ï¼ 3. ä½ æ³éè¿åºç±»å¯¹åç±»è¿è¡å ¨å±çä¿®æ¹ï¼æ¹åææå¨ç©è¡å¨æ¶ççéæ¶èï¼ï¼ **ä¸å¥½çï¼** ```javascript class Employee { constructor(name, email) { this.name = name; this.email = email; } // ... } // ä¸å¥½æ¯å 为éåâæâç¨çæ°æ®ï¼ EmployeeTaxData 䏿¯ä¸ä¸ª Employee ç±»åã class EmployeeTaxData extends Employee { constructor(ssn, salary) { super(); this.ssn = ssn; this.salary = salary; } // ... } ``` **好çï¼** ```javascript class EmployeeTaxData { constructor(ssn, salary) { this.ssn = ssn; this.salary = salary; } // ... } class Employee { constructor(name, email) { this.name = name; this.email = email; } setTaxData(ssn, salary) { this.taxData = new EmployeeTaxData(ssn, salary); } // ... } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **SOLID** ### åä¸èè´£åå (SRP) æ£å¦ä»£ç æ´æ´ä¹éæè¿°ï¼ âæ°¸è¿ä¸è¦æè¶ è¿ä¸ä¸ªçç±æ¥ä¿®æ¹ä¸ä¸ªç±»âã ç»ä¸ä¸ªç±»å¡æ»¡è®¸å¤åè½ï¼ å°±åä½ å¨èª çä¸åªè½å¸¦ä¸ä¸ªè¡æç®±ä¸æ ·ï¼ è¿æ ·åçé®é¢ä½ çç±»ä¸ä¼æçæ³çå èæ§ï¼ å°ä¼æå¤ªå¤ççç±æ¥å¯¹å®è¿è¡ä¿®æ¹ã æå°åéè¦ä¿®æ¹ä¸ä¸ªç±»çæ¬¡æ°æ¶å¾éè¦çï¼ å ä¸ºå¦æä¸ä¸ªç±»æ¥æå¤ªå¤çåè½ï¼ 䏿¦ä½ ä¿®æ¹å®çä¸å°é¨åï¼ å°ä¼å¾é¾å¼æ¸ æ¥ä¼å¯¹ä»£ç åºä¸çå ¶å®æ¨¡åé æä»ä¹å½±åã **ä¸å¥½çï¼** ```javascript class UserSettings { constructor(user) { this.user = user; } changeSettings(settings) { if (this.verifyCredentials()) { // ... } } verifyCredentials() { // ... } } ``` **好çï¼** ```javascript class UserAuth { constructor(user) { this.user = user; } verifyCredentials() { // ... } } class UserSettings { constructor(user) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings) { if (this.auth.verifyCredentials()) { // ... } } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### å¼éåå (OCP) Bertrand Meyer 说è¿ï¼ â软件å®ä½ (ç±»ï¼ æ¨¡åï¼ å½æ°ç) åºè¯¥ä¸ºæ©å±å¼æ¾ï¼ 使¯ä¸ºä¿®æ¹å ³éãâ è¿ æ¯ä»ä¹ææå¢ï¼ è¿ä¸ªåååºæ¬ä¸è¯´æäºä½ åºè¯¥å è®¸ç¨æ·æ·»å åè½èä¸å¿ ä¿®æ¹ç°æç代ç ã **ä¸å¥½çï¼** ```javascript 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 === 'httpNodeAdapter') { return makeHttpCall(url).then((response) => { // transform response and return }); } } } function makeAjaxCall(url) { // request and return promise } function makeHttpCall(url) { // request and return promise } ``` **好çï¼** ```javascript 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 }); } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 鿰代æ¢åå (LSP) è¿æ¯é对ä¸ä¸ªé常ç®åçéé¢çä¸ä¸ªæææå¾ï¼ å®çæ£å¼å®ä¹æ¯ï¼ â妿 S æ¯ T çä¸ä¸ªåç±»åï¼ é£ä¹ç±» å为 T ç对象å¯ä»¥è¢«ç±»å为 S çå¯¹è±¡æ¿æ¢ï¼ä¾å¦ï¼ ç±»å为 S ç对象å¯ä½ä¸ºç±»å为 T çæ¿ä»£åï¼å¿ä¸é è¦ä¿®æ¹ç®æ ç¨åºçæææ§è´¨ ï¼æ£ç¡®æ§ã 任塿§è¡æ§çï¼ãâ è¿çè³æ¯ä¸ªææçå®ä¹ã æå¥½çè§£éæ¯ï¼ å¦æä½ åä¸ä¸ªåºç±»åä¸ä¸ªåç±»ï¼ é£ä¸ªåºç±»ååç±»å¯ä»¥äºæ¢èä¸ä¼äº§ç䏿£ç¡®çç»æã è¿å¯ è½è¿ææäºçæï¼ 让æä»¬æ¥çä¸ä¸è¿ä¸ªç»å ¸çæ£æ¹å½¢ä¸ç©å½¢çä¾åã 仿°å¦ä¸è¯´ï¼ ä¸ä¸ªæ£æ¹å½¢æ¯ä¸ä¸ªç©å½¢ï¼ 使¯ä½ ç¨ "is-a" çå ³ç³»ç¨ç»§æ¿æ¥å®ç°ï¼ ä½ å°å¾å¿«éå°éº»ç¦ã **ä¸å¥½çï¼** ```javascript 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: Will return 25 for Square. Should be 20. rectangle.render(area); }); } const rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles(rectangles); ``` **好çï¼** ```javascript 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); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### æ¥å£é离åå (ISP) JavaScript æ²¡ææ¥å£ï¼ æä»¥è¿ä¸ªåå䏿³å ¶å®è¯è¨é£ä¹ä¸¥æ ¼ã ä¸è¿ï¼ å¯¹äº JavaScript è¿ç§ç¼ºå°ç±» åçè¯è¨æ¥è¯´ï¼ å®ä¾ç¶æ¯éè¦å¹¶ä¸ææä¹çã æ¥å£é离ååè¯´çæ¯ â客æ·ç«¯ä¸åºè¯¥å¼ºå¶ä¾èµä»ä»¬ä¸éè¦çæ¥å£ãâ å¨ JavaScript è¿ç§å¼±ç±»åè¯è¨ä¸ï¼ æ¥å£æ¯éå¼çå¥çº¦ã å¨ JavaScript ä¸è½æ¯è¾å¥½ç说æè¿ä¸ªååçæ¯ä¸ä¸ªç±»éè¦ä¸ä¸ªå·¨å¤§çé 置对象ã ä¸éè¦å®¢æ·ç«¯å»è®¾ç½®å¤§ éçéé¡¹æ¯æççï¼ å ä¸ºå¤æ°æ åµä¸ä»ä»¬ä¸éè¦å ¨é¨ç设置ã 让å®ä»¬åæå¯éçæå©äºé²æ¢åºç°ä¸ä¸ªâèæ¥ å£âã **ä¸å¥½çï¼** ```javascript class DOMTraverser { constructor(settings) { this.settings = settings; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.animationModule.setup(); } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), animationModule() {} // Most of the time, we won't need to animate when traversing. // ... }); ``` **好çï¼** ```javascript class DOMTraverser { 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() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), options: { animationModule() {} } }); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¾èµå转åå (DIP) è¿ä¸ªååéè¿°äºä¸¤ä¸ªéè¦çäºæ ï¼ 1. é«çº§æ¨¡åä¸åºè¯¥ä¾èµäºä½çº§æ¨¡åï¼ ä¸¤è é½åºè¯¥ä¾èµä¸æ½è±¡ï¼ 2. æ½è±¡ä¸åºå½ä¾èµäºå ·ä½å®ç°ï¼ å ·ä½å®ç°åºå½ä¾èµäºæ½è±¡ã è¿ä¸ªä¸å¼å§ä¼å¾é¾çè§£ï¼ ä½æ¯å¦æä½ 使ç¨è¿ Angular.js ï¼ ä½ åºè¯¥å·²ç»çå°è¿éè¿ä¾èµæ³¨å ¥æ¥å®ç°çè¿ ä¸ªååï¼ è½ç¶ä»ä»¬ä¸æ¯ç¸åçæ¦å¿µï¼ ä¾èµå转åå让é«çº§æ¨¡åè¿ç¦»ä½çº§æ¨¡åçç»èååå»ºï¼ å¯ä»¥éè¿ DI æ¥å®ç°ã è¿æ ·åç巨大ç夿¯é使¨¡åé´çè¦åã è¦åæ¯ä¸ä¸ªé常ç³ç³çå¼å模å¼ï¼ å 为ä¼å¯¼è´ä»£ç é¾äº éæã å¦ä¸æè¿°ï¼ JavaScript æ²¡ææ¥å£ï¼ æä»¥è¢«ä¾èµçæ½è±¡æ¯éå¼å¥çº¦ã ä¹å°±æ¯è¯´ï¼ ä¸ä¸ªå¯¹è±¡/ç±»çæ¹æ³å 屿§ç´æ¥æ´é²ç»å¦å¤ä¸ä¸ªå¯¹è±¡/ç±»ã å¨ä¸é¢çä¾åä¸ï¼ ä»»ä½ä¸ä¸ª Request 模åçéå¼å¥çº¦ `InventoryTracker` å°æä¸ä¸ª `requestItems` æ¹æ³ã **ä¸å¥½çï¼** ```javascript class InventoryRequester { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... } } class InventoryTracker { constructor(items) { this.items = items; // ä¸å¥½çï¼ æä»¬å·²ç»å建äºä¸ä¸ªå¯¹è¯·æ±çå ·ä½å®ç°çä¾èµï¼ æä»¬åªæä¸ä¸ª requestItems æ¹æ³ä¾ // èµä¸ä¸ªè¯·æ±æ¹æ³ 'request' this.requester = new InventoryRequester(); } requestItems() { this.items.forEach((item) => { this.requester.requestItem(item); }); } } const inventoryTracker = new InventoryTracker(['apples', 'bananas']); inventoryTracker.requestItems(); ``` **好çï¼** ```javascript 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) { // ... } } // éè¿å¤é¨å建ä¾èµé¡¹å¹¶å°å®ä»¬æ³¨å ¥ï¼ æä»¬å¯ä»¥è½»æ¾çç¨ä¸ä¸ªå´æ°çä½¿ç¨ WebSockets çè¯·æ±æ¨¡åè¿è¡ // æ¿æ¢ã const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); inventoryTracker.requestItems(); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **æµè¯** æµè¯æ¯å叿´å éè¦ã å¦æä½ æ²¡ææµè¯æè æµè¯ä¸å¤å åï¼ æ¯æ¬¡å叿¶ä½ å°±ä¸è½ç¡®è®¤æ²¡æç ´åä»»ä½äºæ ã æµè¯çéç±ä½ çå¢éå³å®ï¼ 使¯æ¥æ 100% çè¦çç(å æ¬ææçè¯å¥å忝)æ¯ä½ 为ä»ä¹è½è¾¾å°é«åº¦èªä¿¡ åå å¿çå¹³éã è¿æå³çéè¦ä¸ä¸ªé¢å¤çä¼å¤§çæµè¯æ¡æ¶ï¼ ä¹éè¦ä¸ä¸ªå¥½ç[è¦ççå·¥å ·](http://gotwarlost.github.io/istanbul/)ã æ²¡æçç±ä¸åæµè¯ã è¿éæ[大éçä¼ç§ç JS æµè¯æ¡æ¶](http://jstherightway.org/#testing-tools)ï¼ éä¸ä¸ªéåä½ çå¢éçå³å¯ã å½ä¸ºå¢ééæ©äºæµè¯æ¡æ¶ä¹åï¼ æ¥ä¸æ¥çç®æ æ¯ä¸ºçäº§çæ¯ä¸ä¸ªæ°çåè½ï¼æ¨¡ åç¼åæµè¯ã å¦æä½ å¾åäºæµè¯é©±å¨å¼å(TDD)ï¼ é£å°±å¤ªæ£äºï¼ 使¯è¦ç¹æ¯ç¡®è®¤ä½ å¨ä¸çº¿ä»»ä½åè½æè é æä¸ä¸ªç°æåè½ä¹åï¼ è¾¾å°äºéè¦çç®æ è¦ççã ### ä¸ä¸ªæµè¯ä¸ä¸ªæ¦å¿µ **ä¸å¥½çï¼** ```javascript const assert = require('assert'); describe('MakeMomentJSGreatAgain', () => { it('handles date boundaries', () => { let date; date = new MakeMomentJSGreatAgain('1/1/2015'); date.addDays(30); date.shouldEqual('1/31/2015'); date = new MakeMomentJSGreatAgain('2/1/2016'); date.addDays(28); assert.equal('02/29/2016', date); date = new MakeMomentJSGreatAgain('2/1/2015'); date.addDays(28); assert.equal('03/01/2015', date); }); }); ``` **好çï¼** ```javascript const assert = require('assert'); describe('MakeMomentJSGreatAgain', () => { it('handles 30-day months', () => { const date = new MakeMomentJSGreatAgain('1/1/2015'); date.addDays(30); date.shouldEqual('1/31/2015'); }); it('handles leap year', () => { const date = new MakeMomentJSGreatAgain('2/1/2016'); date.addDays(28); assert.equal('02/29/2016', date); }); it('handles non-leap year', () => { const date = new MakeMomentJSGreatAgain('2/1/2015'); date.addDays(28); assert.equal('03/01/2015', date); }); }); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **å¹¶å** ### ä½¿ç¨ Promises, ä¸è¦ä½¿ç¨åè° åè°ä¸å¤ç®æ´ï¼ å 为ä»ä»¬ä¼äº§çè¿å¤çåµå¥ã å¨ ES2015/ES6 ä¸ï¼ Promises å·²ç»æ¯å ç½®çå ¨å±ç±»å äºï¼ä½¿ç¨å®ä»¬å§ï¼ **ä¸å¥½çï¼** ```javascript require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => { if (requestErr) { console.error(requestErr); } else { require('fs').writeFile('article.html', response.body, (writeErr) => { if (writeErr) { console.error(writeErr); } else { console.log('File written'); } }); } }); ``` **好çï¼** ```javascript require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') .then((response) => { return require('fs-promise').writeFile('article.html', response); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); }); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### Async/Await æ¯ Promises æ´å ç®æ´ Promises æ¯åè°çä¸ä¸ªéå¸¸ç®æ´çæ¿ä»£åï¼ ä½æ¯ ES2017/ES8 带æ¥ç async å await æä¾äºä¸ä¸ª æ´å ç®æ´çè§£å³æ¹æ¡ã ä½ éè¦çåªæ¯ä¸ä¸ªåç¼ä¸º `async` å ³é®åç彿°ï¼ æ¥ä¸æ¥å°±å¯ä»¥ä¸éè¦ `then` 彿°é¾æ¥ç¼åé»è¾äºã å¦æä½ è½ä½¿ç¨ ES2017/ES8 çé«çº§åè½çè¯ï¼ ä»å¤©å°±ä½¿ç¨å®å§ï¼ **ä¸å¥½çï¼** ```javascript require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') .then((response) => { return require('fs-promise').writeFile('article.html', response); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); }); ``` **好çï¼** ```javascript async function getCleanCodeArticle() { try { const response = await require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); await require('fs-promise').writeFile('article.html', response); console.log('File written'); } catch(err) { console.error(err); } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **é误å¤ç** æåºé误æ¯ä¸ä»¶å¥½äºæ ï¼ ä»ä»¬æå³çå½ä½ çç¨åºæéæ¶è¿è¡æ¶å¯ä»¥æåç¡®è®¤ï¼ å¹¶ä¸éè¿åæ¢æ§è¡å½åå æ ä¸ç彿°æ¥è®©ä½ ç¥éï¼ ç»æå½åè¿ç¨ï¼å¨ Node ä¸ï¼ï¼ 卿§å¶å°ä¸ç¨ä¸ä¸ªå æ è·è¸ªæç¤ºä½ ã ### ä¸è¦å¿½ç¥ææå°çé误 对ææå°çé误ä¸åä»»ä½å¤çä¸è½ç»ä½ ä¿®å¤é误æè ååºé误çè½åã åæ§å¶å°è®°å½é误 (`console.log`) ä¹ä¸æä¹å¥½ï¼ å 为å¾å¾ä¼ä¸¢å¤±å¨æµ·éçæ§å¶å°è¾åºä¸ã å¦æä½ æä»»æä¸æ®µä»£ç ç¨ `try/catch` å è£ é£å°± æå³çä½ æ³å°è¿éå¯è½ä¼éï¼ å æ¤ä½ åºè¯¥æä¸ªä¿®å¤è®¡åï¼ æè å½é误åçæ¶æä¸ä¸ªä»£ç è·¯å¾ã **ä¸å¥½çï¼** ```javascript try { functionThatMightThrow(); } catch (error) { console.log(error); } ``` **好çï¼** ```javascript try { functionThatMightThrow(); } catch (error) { // One option (more noisy than console.log): console.error(error); // Another option: notifyUserOfError(error); // Another option: reportErrorToService(error); // OR do all three! } ``` ### ä¸è¦å¿½ç¥è¢«æç»ç promise ä¸ä½ ä¸åºå¿½ç¥æ¥èª `try/catch` çé误çåå ç¸åã **ä¸å¥½çï¼** ```javascript getdata() .then((data) => { functionThatMightThrow(data); }) .catch((error) => { console.log(error); }); ``` **好çï¼** ```javascript getdata() .then((data) => { functionThatMightThrow(data); }) .catch((error) => { // One option (more noisy than console.log): console.error(error); // Another option: notifyUserOfError(error); // Another option: reportErrorToService(error); // OR do all three! }); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **æ ¼å¼å** æ ¼å¼åæ¯ä¸»è§çã å°±åå ¶å®è§å䏿 ·ï¼ 没æå¿ é¡»è®©ä½ éµå®ç硬æ§è§åã éç¹æ¯ä¸è¦å ä¸ºæ ¼å¼å»äºè®ºï¼ è¿ éæ[大éçå·¥å ·](http://standardjs.com/rules.html)æ¥èªå¨æ ¼å¼åï¼ ä½¿ç¨å ¶ä¸çä¸ä¸ªå³å¯ï¼ å 为å为工ç¨å¸å»äºè®ºæ ¼å¼åå°±æ¯å¨æµªè´¹æ¶é´åéé±ã é对èªå¨æ ¼å¼åå·¥å ·ä¸è½æ¶µççé®é¢ï¼ç¼©è¿ã å¶è¡¨ç¬¦è¿æ¯ç©ºæ ¼ã åå¼å·è¿æ¯åå¼å·çï¼ï¼ è¿éæä¸äºæåã ### 使ç¨ä¸è´ç大å°å JavaScript æ¯æ ç±»åçï¼ æä»¥å¤§å°ååè¯ä½ å ³äºä½ çåéã 彿°ççå¾å¤äºæ ã è¿äºè§åæ¯ä¸»è§çï¼ æä»¥ä½ çå¢éå¯ä»¥éæ©ä»ä»¬æ³è¦çã éç¹æ¯ï¼ ä¸ç®¡ä½ 们鿩äºä»ä¹ï¼ è¦ä¿æä¸è´ã **ä¸å¥½çï¼** ```javascript const DAYS_IN_WEEK = 7; const daysInMonth = 30; const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; function eraseDatabase() {} function restore_database() {} class animal {} class Alpaca {} ``` **好çï¼** ```javascript const DAYS_IN_WEEK = 7; const DAYS_IN_MONTH = 30; const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; const artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; function eraseDatabase() {} function restoreDatabase() {} class Animal {} class Alpaca {} ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### 彿°çè°ç¨æ¹ä¸è¢«è°ç¨æ¹åºè¯¥é è¿ å¦æä¸ä¸ªå½æ°è°ç¨å¦ä¸ä¸ªï¼ åå¨ä»£ç ä¸è¿ä¸¤ä¸ªå½æ°çç«ç´ä½ç½®åºè¯¥é è¿ã çæ³æ åµä¸ï¼ä¿æè¢«è°ç¨å½æ°å¨è¢« è°ç¨å½æ°çæ£ä¸æ¹ã æä»¬å¾åäºä»ä¸å°ä¸é 读代ç ï¼ å°±å读ä¸ç« æ¥çº¸ã ç±äºè¿ä¸ªåå ï¼ ä¿æä½ ç代ç å¯ ä»¥æç §è¿ç§æ¹å¼é 读ã **ä¸å¥½çï¼** ```javascript class PerformanceReview { constructor(employee) { this.employee = employee; } lookupPeers() { return db.lookup(this.employee, 'peers'); } lookupManager() { return db.lookup(this.employee, 'manager'); } getPeerReviews() { const peers = this.lookupPeers(); // ... } perfReview() { this.getPeerReviews(); this.getManagerReview(); this.getSelfReview(); } getManagerReview() { const manager = this.lookupManager(); } getSelfReview() { // ... } } const review = new PerformanceReview(user); review.perfReview(); ``` **好çï¼** ```javascript class PerformanceReview { constructor(employee) { this.employee = employee; } perfReview() { this.getPeerReviews(); this.getManagerReview(); this.getSelfReview(); } getPeerReviews() { const peers = this.lookupPeers(); // ... } lookupPeers() { return db.lookup(this.employee, 'peers'); } getManagerReview() { const manager = this.lookupManager(); } lookupManager() { return db.lookup(this.employee, 'manager'); } getSelfReview() { // ... } } const review = new PerformanceReview(employee); review.perfReview(); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## **注é** ### ä» ä» å¯¹å å«å¤æä¸å¡é»è¾çä¸è¥¿è¿è¡æ³¨é æ³¨éæ¯ä»£ç çè¾©è§£ï¼ ä¸æ¯è¦æ±ã 夿°æ åµä¸ï¼ 好ç代ç å°±æ¯ææ¡£ã **ä¸å¥½çï¼** ```javascript function hashIt(data) { // The hash let hash = 0; // Length of string const length = data.length; // Loop through every character in data for (let i = 0; i < length; i++) { // Get character code. const char = data.charCodeAt(i); // Make the hash hash = ((hash << 5) - hash) + char; // Convert to 32-bit integer hash &= hash; } } ``` **好çï¼** ```javascript function hashIt(data) { let hash = 0; const length = data.length; for (let i = 0; i < length; i++) { const char = data.charCodeAt(i); hash = ((hash << 5) - hash) + char; // Convert to 32-bit integer hash &= hash; } } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¸è¦å¨ä»£ç åºä¸ä¿å注éæç代ç å 为æçæ¬æ§å¶ï¼ ææ§ç代ç çå¨åå²è®°å½å³å¯ã **ä¸å¥½çï¼** ```javascript doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff(); ``` **好çï¼** ```javascript doStuff(); ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### ä¸è¦ææ¥å¿å¼ç注é è®°ä½ï¼ 使ç¨çæ¬æ§å¶ï¼ ä¸éè¦åµå°¸ä»£ç ï¼ æ³¨éæç代ç ï¼ å°¤å ¶æ¯æ¥å¿å¼ç注éã ä½¿ç¨ `git log` æ¥ è·ååå²è®°å½ã **ä¸å¥½çï¼** ```javascript /** * 2016-12-20: Removed monads, didn't understand them (RM) * 2016-10-01: Improved using special monads (JP) * 2016-02-03: Removed type-checking (LI) * 2015-03-14: Added combine with type-checking (JR) */ function combine(a, b) { return a + b; } ``` **好çï¼** ```javascript function combine(a, b) { return a + b; } ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ### é¿å å ä½ç¬¦ å®ä»¬ä» ä» æ·»å äºå¹²æ°ã è®©å½æ°ååéåç§°ä¸åéç缩è¿åæ ¼å¼åä¸ºä½ çä»£ç æä¾è§è§ç»æã **ä¸å¥½çï¼** ```javascript //////////////////////////////////////////////////////////////////////////////// // Scope Model Instantiation //////////////////////////////////////////////////////////////////////////////// $scope.model = { menu: 'foo', nav: 'bar' }; //////////////////////////////////////////////////////////////////////////////// // Action setup //////////////////////////////////////////////////////////////////////////////// const actions = function() { // ... }; ``` **好çï¼** ```javascript $scope.model = { menu: 'foo', nav: 'bar' }; const actions = function() { // ... }; ``` **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)** ## Translation This is also available in other languages: -  **Brazilian Portuguese**: [fesnt/clean-code-javascript](https://github.com/fesnt/clean-code-javascript) -  **Chinese**: - [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) - [beginor/clean-code-js](https://github.com/beginor/clean-code-javascript/blob/master/README-zh-CN.md) -  **German**: [marcbruederlin/clean-code-javascript](https://github.com/marcbruederlin/clean-code-javascript) -  **Korean**: [qkraudghgh/clean-code-javascript-ko](https://github.com/qkraudghgh/clean-code-javascript-ko) -  **Russian**: - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/) - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript) -  **Vietnamese**: [hienvd/clean-code-javascript/](https://github.com/hienvd/clean-code-javascript/) **[⬠è¿åé¡¶é¨](#ä»£ç æ´æ´ç-javascript)**