Photo by Pankaj Patel on Unsplash
Airbnb JavaScript Style Guide()
แนวทางที่สมเหตุสมผลส่วนใหญ่สำหรับ JavaScript
คู่มือสไตล์อื่นๆ
แปลจาก Airbnb JavaScript Style Guide
ประเภทข้อมูล (Types)
- 1.1 พื้นฐาน (Primitives): คุณสามารถเข้าถึงประเภทพื้นฐานได้โดยตรง
stringnumberbooleannullundefined
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
- 1.2 ซับซ้อน (Complex): คุณสามารถเข้าถึงประเภทซับซ้อนได้โดยการอ้างอิง (reference)
objectarrayfunction
const foo = [1, 2];
const bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
การอ้างอิง (References)
- 2.1 ใช้
constสำหรับการอ้างอิงทั้งหมดของคุณ หลีกเลี่ยงvareslint:prefer-const,no-const-assign
ทำไม? เพื่อให้แน่ใจว่าคุณไม่สามารถกำหนดค่าการอ้างอิงใหม่ได้ ซึ่งอาจนำไปสู่ข้อผิดพลาดและทำให้โค้ดเข้าใจยาก
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;
- 2.2 หากคุณต้องกำหนดค่าการอ้างอิงใหม่ ให้ใช้
letแทนvareslint:no-varjscs:disallowVar
ทำไม?
letมีขอบเขตระดับบล็อก (block-scoped) แทนที่จะเป็นระดับฟังก์ชัน (function-scoped) เหมือนvar
// bad
var count = 1;
if (true) {
count += 1;
}
// good, use the let.
let count = 1;
if (true) {
count += 1;
}
- 2.3 โปรดทราบว่าทั้ง
letและconstมีขอบเขตระดับบล็อก
// const และ let มีอยู่เฉพาะในบล็อกที่กำหนดไว้เท่านั้น
{
let a = 1;
const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
ออบเจกต์ (Objects)
- 3.1 ใช้รูปแบบ literal สำหรับการสร้างออบเจกต์ eslint rules:
no-new-object.
// bad
const item = new Object();
// good
const item = {};
- 3.2 อย่าใช้ คำสงวน (reserved words) เป็นคีย์ มันจะไม่ทำงานใน IE8 ข้อมูลเพิ่มเติม ไม่เป็นไรหากจะใช้ในโมดูล ES6 และโค้ดฝั่งเซิร์ฟเวอร์ jscs:
disallowIdentifierNames
// bad
const superman = {
default: { clark: 'kent' },
private: true,
};
// good
const superman = {
defaults: { clark: 'kent' },
hidden: true,
};
- 3.3 ใช้คำพ้องความหมายที่อ่านง่ายแทนคำสงวน jscs:
disallowIdentifierNames
// bad
const superman = {
class: 'alien',
};
// bad
const superman = {
klass: 'alien',
};
// good
const superman = {
type: 'alien',
};
- 3.4 ใช้ชื่อคุณสมบัติที่คำนวณได้ (computed property names) เมื่อสร้างออบเจกต์ที่มีชื่อคุณสมบัติแบบไดนามิก
ทำไม? เพราะช่วยให้คุณกำหนดคุณสมบัติทั้งหมดของออบเจกต์ได้ในที่เดียว
function getKey(k) {
return `a key named ${k}`;
}
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
- 3.5 ใช้รูปแบบย่อของเมธอดในออบเจกต์ eslint:
object-shorthandjscs:requireEnhancedObjectLiterals
// bad
const atom = {
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
},
};
- 3.6 ใช้รูปแบบย่อของคุณสมบัติ eslint:
object-shorthandjscs:requireEnhancedObjectLiterals
ทำไม? เพราะสั้นกระชับและสื่อความหมาย
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
- 3.7 จัดกลุ่มคุณสมบัติแบบย่อไว้ที่ส่วนต้นของการประกาศออบเจกต์
ทำไม? เพื่อให้ง่ายต่อการบอกว่าคุณสมบัติใดใช้รูปแบบย่อ
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};
- 3.8 ใส่เครื่องหมายคำพูดเฉพาะคุณสมบัติที่เป็นตัวระบุที่ไม่ถูกต้อง (invalid identifiers) eslint:
quote-propsjscs:disallowQuotedKeysInObjects
ทำไม? โดยทั่วไปเราถือว่าอ่านง่ายกว่าในเชิงอัตนัย มันช่วยในการเน้น syntax และยังง่ายต่อการปรับให้เหมาะสมโดย JS engines หลายตัว
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
อาร์เรย์ (Arrays)
- 4.1 ใช้รูปแบบ literal สำหรับการสร้างอาร์เรย์ eslint:
no-array-constructor
// bad
const items = new Array();
// good
const items = [];
- 4.2 ใช้ Array#push แทนการกำหนดค่าโดยตรงเพื่อเพิ่มรายการลงในอาร์เรย์
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
- 4.3 ใช้การกระจายอาร์เรย์ (array spreads)
...เพื่อคัดลอกอาร์เรย์
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
- 4.4 ในการแปลงวัตถุคล้ายอาร์เรย์ (array-like object) เป็นอาร์เรย์ ให้ใช้ Array#from
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
- 4.5 ใช้คำสั่ง return ใน callback ของเมธอดในอาร์เรย์ สามารถละเว้น return ได้หากเนื้อหาของฟังก์ชันประกอบด้วยคำสั่งเดียวตาม 8.2 eslint:
array-callback-return
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map(x => x + 1);
// bad
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = memo.concat(item);
});
// good
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = flatten;
return flatten;
});
// bad
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
} else {
return false;
}
});
// good
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
}
return false;
});
การแยกโครงสร้าง (Destructuring)
- 5.1 ใช้การแยกโครงสร้างออบเจกต์เมื่อเข้าถึงและใช้คุณสมบัติหลายรายการของออบเจกต์ jscs:
requireObjectDestructuring
ทำไม? การแยกโครงสร้างช่วยให้คุณไม่ต้องสร้างการอ้างอิงชั่วคราวสำหรับคุณสมบัติเหล่านั้น
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
- 5.2 ใช้การแยกโครงสร้างอาร์เรย์ jscs:
requireArrayDestructuring
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
- 5.3 ใช้การแยกโครงสร้างออบเจกต์สำหรับการส่งคืนหลายค่า ไม่ใช่การแยกโครงสร้างอาร์เรย์
ทำไม? คุณสามารถเพิ่มคุณสมบัติใหม่หรือเปลี่ยนลำดับของสิ่งต่างๆ ได้โดยไม่ทำให้จุดที่เรียกใช้เสียหาย
// bad
function processInput(input) {
// ปาฏิหาริย์เกิดขึ้นที่นี่
return [left, right, top, bottom];
}
// ผู้เรียกต้องนึกถึงลำดับของข้อมูลที่ส่งคืน
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// ปาฏิหาริย์เกิดขึ้นที่นี่
return { left, right, top, bottom };
}
// ผู้เรียกเลือกเฉพาะข้อมูลที่ต้องการ
const { left, right } = processInput(input);
สตริง (Strings)
- 6.1 ใช้ single quotes
''สำหรับสตริง eslint:quotesjscs:validateQuoteMarks
// bad
const name = "Capt. Janeway";
// good
const name = 'Capt. Janeway';
- 6.2 สตริงที่ยาวเกิน 100 ตัวอักษรไม่ควรเขียนข้ามหลายบรรทัดโดยใช้วิธีการเชื่อมสตริง
- 6.3 หมายเหตุ: หากใช้มากเกินไป การเชื่อมต่อสตริงยาวๆ อาจส่งผลต่อประสิทธิภาพ jsPerf & Discussion
// bad
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
// good
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
- 6.4 เมื่อสร้างสตริงด้วยโปรแกรม ให้ใช้ template strings แทนการเชื่อมต่อ eslint:
prefer-templatetemplate-curly-spacingjscs:requireTemplateStrings
ทำไม? Template strings ให้ไวยากรณ์ที่อ่านง่าย กระชับ พร้อมการขึ้นบรรทัดใหม่ที่เหมาะสมและคุณสมบัติการแทรกสตริง (string interpolation)
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
- 6.5 ห้ามใช้
eval()บนสตริง เพราะมันเปิดช่องโหว่มากเกินไป
ฟังก์ชัน (Functions)
- 7.1 ใช้การประกาศฟังก์ชัน (function declarations) แทน function expressions jscs:
requireFunctionDeclarations
ทำไม? การประกาศฟังก์ชันมีชื่อ ดังนั้นจึงระบุได้ง่ายกว่าใน stack traces นอกจากนี้ การประกาศฟังก์ชันจะถูก hoist (ยกขึ้นไปไว้บนสุด) ในขณะที่ function expressions ไม่เป็นเช่นนั้น กฎนี้ทำให้ Arrow Functions สามารถแทนที่ function expressions ได้อย่างสมบูรณ์
// bad
const foo = function () {
};
// good
function foo() {
}
- 7.2 IIFE: eslint:
wrap-iifejscs:requireParenthesesAroundIIFE
ทำไม? IIFE เป็นหน่วยเดียว - การห่อทั้งฟังก์ชันและการเรียกใช้ในวงเล็บทำให้ชัดเจน โปรดทราบว่าในโลกของโมดูล คุณมักจะไม่ต้องใช้ IIFE อีกต่อไป
// immediately-invoked function expression (IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());
-
7.3 ห้ามประกาศฟังก์ชันในบล็อกที่ไม่ใช่ฟังก์ชัน (if, while ฯลฯ) ให้กำหนดฟังก์ชันให้กับตัวแปรแทน เบราว์เซอร์จะอนุญาตให้คุณทำเช่นนั้น แต่ทั้งหมดตีความแตกต่างกัน eslint:
no-loop-func -
7.4 หมายเหตุ: ECMA-262 กำหนด
blockเป็นรายการของคำสั่ง (statements) การประกาศฟังก์ชันไม่ใช่คำสั่ง อ่านหมายเหตุของ ECMA-262 เกี่ยวกับปัญหานี้
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
- 7.5 ห้ามตั้งชื่อพารามิเตอร์ว่า
argumentsสิ่งนี้จะมีความสำคัญเหนือกว่าออบเจกต์argumentsที่มอบให้กับทุกขอบเขตฟังก์ชัน
// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}
- 7.6 ห้ามใช้
argumentsเลือกใช้ไวยากรณ์ rest...แทนprefer-rest-params
ทำไม?
...ชัดเจนว่าคุณต้องการดึงอาร์กิวเมนต์ใดออกมา นอกจากนี้ rest arguments เป็น Array จริง ๆ ไม่ใช่เพียงแค่ Array-like เหมือนarguments
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
- 7.7 ใช้ไวยากรณ์พารามิเตอร์เริ่มต้น (default parameter) แทนการเปลี่ยนแปลงอาร์กิวเมนต์ของฟังก์ชัน
// really bad
function handleThings(opts) {
// No! We shouldn't mutate function arguments.
// Double bad: if opts is false it will be set to an object which may
// be what you want but it can cause subtle bugs.
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
- 7.8 หลีกเลี่ยงผลข้างเคียง (side effects) ด้วยพารามิเตอร์เริ่มต้น
ทำไม? ทำให้สับสนในการทำความเข้าใจ
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
- 7.9 วางพารามิเตอร์เริ่มต้นไว้ท้ายสุดเสมอ
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
- 7.10 ห้ามใช้ Function constructor เพื่อสร้างฟังก์ชันใหม่
ทำไม? การสร้างฟังก์ชันด้วยวิธีนี้จะประเมินสตริงคล้ายกับ eval() ซึ่งเปิดช่องโหว่
// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');
- 7.11 การเว้นวรรคใน function signature
ทำไม? ความสม่ำเสมอเป็นสิ่งที่ดี และคุณไม่ควรต้องเพิ่มหรือลบช่องว่างเมื่อเพิ่มหรือลบชื่อ
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
- 7.12 ห้ามเปลี่ยนแปลง (mutate) พารามิเตอร์ eslint:
no-param-reassign
ทำไม? การจัดการออบเจกต์ที่ส่งเข้ามาเป็นพารามิเตอร์อาจทำให้เกิดผลข้างเคียงของตัวแปรที่ไม่พึงประสงค์ในผู้เรียก (caller) ดั้งเดิม
// bad
function f1(obj) {
obj.key = 1;
};
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};
- 7.13 ห้ามกำหนดค่าใหม่ (reassign) ให้กับพารามิเตอร์ eslint:
no-param-reassign
ทำไม? การกำหนดค่าใหม่ให้กับพารามิเตอร์อาจนำไปสู่พฤติกรรมที่ไม่คาดคิด โดยเฉพาะเมื่อเข้าถึงออบเจกต์
argumentsนอกจากนี้ยังอาจทำให้เกิดปัญหาการปรับให้เหมาะสม โดยเฉพาะใน V8
```javascript
// bad
function f1(a) {
a = 1;
}
function f2(a) {
if (!a) { a = 1; }
}
// good
function f3(a) {
const b = a || 1;
}
function f4(a = 1) {
}
```
Arrow Functions
- 8.1 เมื่อคุณต้องใช้ function expressions (เช่นเมื่อส่งผ่านฟังก์ชันที่ไม่ระบุชื่อ) ให้ใช้รูปแบบ arrow function eslint:
prefer-arrow-callback,arrow-spacingjscs:requireArrowFunctions
ทำไม? มันสร้างเวอร์ชันของฟังก์ชันที่ทำงานในบริบทของ
thisซึ่งมักจะเป็นสิ่งที่คุณต้องการ และเป็นไวยากรณ์ที่กระชับกว่า
ทำไมไม่? หากคุณมีฟังก์ชันที่ค่อนข้างซับซ้อน คุณอาจย้ายตรรกะนั้นออกไปเป็นการประกาศฟังก์ชันของตัวเอง
// bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
- 8.2 หากเนื้อหาของฟังก์ชันประกอบด้วยคำสั่งเดียวที่ส่งคืนนิพจน์โดยไม่มีผลข้างเคียง ให้ละเว้นปีกกาและใช้การ return โดยนัย (implicit return) มิฉะนั้น ให้เก็บปีกกาไว้และใช้คำสั่ง
returneslint:arrow-parens,arrow-body-stylejscs:disallowParenthesesAroundArrowParam,requireShorthandArrowFunctions
ทำไม? Syntactic sugar (ความสะดวกทางไวยากรณ์) มันอ่านง่ายเมื่อฟังก์ชันหลายตัวเชื่อมต่อกัน
ทำไมไม่? หากคุณวางแผนที่จะส่งคืนออบเจกต์
// bad
[1, 2, 3].map(number => {
const nextNumber = number + 1;
`A string containing the ${nextNumber}.`;
});
// good
[1, 2, 3].map(number => `A string containing the ${number}.`);
// good
[1, 2, 3].map((number) => {
const nextNumber = number + 1;
return `A string containing the ${nextNumber}.`;
});
- 8.3 ในกรณีที่นิพจน์ครอบคลุมหลายบรรทัด ให้ห่อไว้ในวงเล็บเพื่อให้อ่านง่ายขึ้น
ทำไม? มันแสดงให้เห็นชัดเจนว่าฟังก์ชันเริ่มต้นและสิ้นสุดที่ใด
// bad
[1, 2, 3].map(number => 'As time went by, the string containing the ' +
`${number} became much longer. So we needed to break it over multiple ` +
'lines.'
);
// good
[1, 2, 3].map(number => (
`As time went by, the string containing the ${number} became much ` +
'longer. So we needed to break it over multiple lines.'
));
- 8.4 หากฟังก์ชันของคุณรับอาร์กิวเมนต์เดียวและไม่ใช้ปีกกา ให้ละเว้นวงเล็บ มิฉะนั้น ให้ใส่วงเล็บรอบอาร์กิวเมนต์เสมอ eslint:
arrow-parensjscs:disallowParenthesesAroundArrowParam
ทำไม? ลดความยุ่งเหยิงทางสายตา
// bad
[1, 2, 3].map((x) => x * x);
// good
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].map(number => (
`A long string with the ${number}. It’s so long that we’ve broken it ` +
'over multiple lines!'
));
// bad
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
- 8.5 หลีกเลี่ยงรูปแบบ arrow function ที่สับสน (
=>) กับตัวดำเนินการเปรียบเทียบ (<=,>=) eslint:no-confusing-arrow
// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = item => { return item.height > 256 ? item.largeSize : item.smallSize; }
คอนสตรัคเตอร์ (Constructors)
- 9.1 ใช้
classเสมอ หลีกเลี่ยงการจัดการprototypeโดยตรง
ทำไม? ไวยากรณ์
classกระชับกว่าและเข้าใจได้ง่ายกว่า
// bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function () {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
// good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
- 9.2 ใช้
extendsสำหรับการสืบทอด (inheritance)
ทำไม? เป็นวิธีที่มีมาให้ในตัวเพื่อสืบทอดฟังก์ชันการทำงานจาก prototype โดยไม่ทำให้
instanceofเสียหาย
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
return this._queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
- 9.3 เมธอดสามารถคืนค่า
thisเพื่อช่วยในการเชื่อมต่อเมธอด (method chaining)
// bad
Jedi.prototype.jump = function () {
this.jumping = true;
return true;
};
Jedi.prototype.setHeight = function (height) {
this.height = height;
};
const luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20); // => undefined
// good
class Jedi {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
const luke = new Jedi();
luke.jump()
.setHeight(20);
- 9.4 ไม่เป็นไรที่จะเขียนเมธอด toString() ขึ้นเอง เพียงแค่ต้องแน่ใจว่ามันทำงานได้สำเร็จและไม่มีผลข้างเคียง
class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
- 9.5 คลาสมี constructor เริ่มต้นหากไม่ได้ระบุไว้ constructor ที่ว่างเปล่าหรือที่เพียงแค่มอบหมายให้กับคลาสแม่นั้นไม่จำเป็น
no-useless-constructor
// bad
class Jedi {
constructor() {}
getName() {
return this.name;
}
}
// bad
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}
// good
class Rey extends Jedi {
constructor(...args) {
super(...args);
this.name = 'Rey';
}
}
โมดูล (Modules)
- 10.1 ใช้โมดูล (
import/export) เสมอ แทนระบบโมดูลที่ไม่ได้มาตรฐาน คุณสามารถแปลง (transpile) ไปยังระบบโมดูลที่คุณต้องการได้เสมอ
ทำไม? โมดูลคืออนาคต มาเริ่มใช้อนาคตกันเถอะ
// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;
// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;
// best
import { es6 } from './AirbnbStyleGuide';
export default es6;
- 10.2 อย่าใช้ wildcard imports
ทำไม? สิ่งนี้ทำให้มั่นใจได้ว่าคุณมี default export เพียงอันเดียว
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
- 10.3 และอย่า export โดยตรงจากการ import
ทำไม? แม้ว่าการเขียนบรรทัดเดียวจะกระชับ แต่การมีวิธีที่ชัดเจนในการ import และ export สิ่งต่างๆ ทำให้มีความสม่ำเสมอ
// bad
// filename es6.js
export { es6 as default } from './airbnbStyleGuide';
// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
Iterators และ Generators
- 11.1 อย่าใช้ iterators ควรเลือกใช้ higher-order functions ของ JavaScript เช่น
map()และreduce()แทนลูปอย่างfor-ofeslint:no-iterator
ทำไม? นี่เป็นการบังคับใช้กฎ immutable ของเรา การจัดการกับ pure functions ที่ส่งคืนค่านั้นง่ายต่อการเข้าใจมากกว่าผลข้างเคียง
eslint rules: no-iterator.
const numbers = [1, 2, 3, 4, 5];
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good
let sum = 0;
numbers.forEach(num => sum += num);
sum === 15;
// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
- 11.2 อย่าใช้ generators ในตอนนี้
ทำไม? มันยังแปลงเป็น ES5 ได้ไม่ดีนัก
คุณสมบัติ (Properties)
- 12.1 ใช้ dot notation เมื่อเข้าถึงคุณสมบัติ eslint:
dot-notationjscs:requireDotNotation
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
- 12.2 ใช้ bracket notation
[]เมื่อเข้าถึงคุณสมบัติด้วยตัวแปร
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
ตัวแปร (Variables)
- 13.1 ใช้
constหรือletเสมอเพื่อประกาศตัวแปร การไม่ทำเช่นนั้นจะส่งผลให้เกิดตัวแปรแบบ Global เราต้องการหลีกเลี่ยงการสร้างมลพิษให้กับ Global namespace กัปตันแพลนเน็ตเตือนเราเรื่องนั้นแล้ว
// bad
superPower = new SuperPower();
// good
const superPower = new SuperPower();
- 13.2 ใช้
constหรือletหนึ่งรายการต่อการประกาศตัวแปร eslint:one-varjscs:disallowMultipleVarDecl
ทำไม? วิธีนี้ทำให้ง่ายต่อการเพิ่มการประกาศตัวแปรใหม่ และคุณไม่ต้องกังวลเกี่ยวกับการสลับ
;เป็น,หรือทำให้เกิดความแตกต่างของเครื่องหมายวรรคตอนเท่านั้น
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad
// (compare to above, notice the error)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
- 13.3 จัดกลุ่ม
constทั้งหมดของคุณ แล้วจึงจัดกลุ่มletทั้งหมด
ทำไม? สิ่งนี้เป็นประโยชน์เมื่อในภายหลังคุณอาจต้องกำหนดตัวแปรโดยขึ้นอยู่กับตัวแปรที่กำหนดไว้ก่อนหน้านี้
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
- 13.4 กำหนดตัวแปรในตำแหน่งที่คุณต้องการ แต่ให้วางไว้ในตำแหน่งที่เหมาะสม
ทำไม?
letและconstเป็น block scoped ไม่ใช่ function scoped
// bad - unnecessary function call
function checkName(hasName) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// good
function checkName(hasName) {
if (hasName === 'test') {
return false;
}
const name = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
การ Hoisting
- 14.1 การประกาศ
varจะถูก hoist ไปที่ด้านบนสุดของขอบเขต แต่การกำหนดค่าจะไม่ถูก hoist ส่วนการประกาศconstและletได้รับแนวคิดใหม่ที่เรียกว่า Temporal Dead Zones (TDZ) เป็นสิ่งสำคัญที่ต้องรู้ว่าทำไม typeof ถึงไม่ปลอดภัยอีกต่อไป
// we know this wouldn't work (assuming there
// is no notDefined global variable)
function example() {
console.log(notDefined); // => throws a ReferenceError
}
// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// The interpreter is hoisting the variable
// declaration to the top of the scope,
// which means our example could be rewritten as:
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// using const and let
function example() {
console.log(declaredButNotAssigned); // => throws a ReferenceError
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
const declaredButNotAssigned = true;
}
- 14.2 Anonymous function expressions จะ hoist ชื่อตัวแปรของมัน แต่จะไม่ hoist การกำหนดค่าฟังก์ชัน
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('anonymous function expression');
};
}
- 14.3 Named function expressions จะ hoist ชื่อตัวแปร ไม่ใช่ชื่อฟังก์ชันหรือเนื้อหาของฟังก์ชัน
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('Flying');
};
}
// the same is true when the function name
// is the same as the variable name.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
}
}
- 14.4 Function declarations จะ hoist ชื่อและเนื้อหาของฟังก์ชัน
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
- สำหรับข้อมูลเพิ่มเติมโปรดดู JavaScript Scoping & Hoisting โดย Ben Cherry
ตัวดำเนินการเปรียบเทียบและความเท่าเทียม (Comparison Operators & Equality)
-
15.2 คำสั่งเงื่อนไขเช่น
ifจะประเมินนิพจน์โดยใช้การบีบบังคับ (coercion) ด้วย abstract methodToBooleanและปฏิบัติตามกฎง่ายๆ เหล่านี้เสมอ:
- Objects ประเมินเป็น true
- Undefined ประเมินเป็น false
- Null ประเมินเป็น false
- Booleans ประเมินเป็น ค่าของ boolean นั้น
- Numbers ประเมินเป็น false หากเป็น +0, -0 หรือ NaN, มิฉะนั้น true
- Strings ประเมินเป็น false หากเป็นสตริงว่าง
'', มิฉะนั้น true
if ([0] && []) {
// true
// an array (even an empty one) is an object, objects will evaluate to true
}
- 15.3 ใช้ทางลัด
// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
- 15.4 สำหรับข้อมูลเพิ่มเติมดู Truth Equality and JavaScript โดย Angus Croll
- 15.5 ใช้วงเล็บปีกกาเพื่อสร้างบล็อกใน
caseและdefaultที่มีการประกาศ lexical (เช่นlet,const,function, และclass)
ทำไม? การประกาศ Lexical จะมองเห็นได้ในบล็อก
switchทั้งหมด แต่จะเริ่มต้นเมื่อมีการกำหนดค่าเท่านั้น ซึ่งจะเกิดขึ้นเมื่อถึงcaseของมันเท่านั้น สิ่งนี้ทำให้เกิดปัญหาเมื่อหลายcaseพยายามกำหนดสิ่งเดียวกัน
eslint rules: no-case-declarations.
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {}
break;
default:
class C {}
}
// good
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 3: {
function f() {}
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
- 15.7 Ternaries ไม่ควรซ้อนกันและโดยทั่วไปควรเป็นนิพจน์บรรทัดเดียว
eslint rules: no-nested-ternary.
// bad
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;
// better
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// best
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
- 15.8 หลีกเลี่ยง ternary statements ที่ไม่จำเป็น
eslint rules: no-unneeded-ternary.
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
บล็อก (Blocks)
- 16.1 ใช้วงเล็บปีกกากับบล็อกหลายบรรทัดทั้งหมด
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function foo() { return false; }
// good
function bar() {
return false;
}
- 16.2 หากคุณใช้บล็อกหลายบรรทัดที่มี
ifและelseให้วางelseในบรรทัดเดียวกับวงเล็บปิดของบล็อกifeslint:brace-stylejscs:disallowNewlineBeforeBlockStatements
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
คำสั่งควบคุม (Control Statements)
- 17.1 ในกรณีที่คำสั่งควบคุมของคุณ (
if,whileฯลฯ) ยาวเกินไปหรือเกินความยาวบรรทัดสูงสุด แต่ละเงื่อนไข (ที่จัดกลุ่ม) สามารถใส่ไว้ในบรรทัดใหม่ได้ ตัวดำเนินการทางตรรกะควรเริ่มต้นบรรทัด
ทำไม? การกำหนดให้ตัวดำเนินการอยู่ที่จุดเริ่มต้นของบรรทัดช่วยให้ตัวดำเนินการจัดแนวและเป็นไปตามรูปแบบที่คล้ายกับการเชื่อมต่อเมธอด (method chaining) สิ่งนี้ยังช่วยปรับปรุงความสามารถในการอ่านโดยทำให้ตรรกะที่ซับซ้อนง่ายต่อการเข้าใจด้วยสายตา
// bad
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
thing1();
}
// bad
if (foo === 123 &&
bar === 'abc') {
thing1();
}
// bad
if (foo === 123
&& bar === 'abc') {
thing1();
}
// bad
if (
foo === 123 &&
bar === 'abc'
) {
thing1();
}
// good
if (
foo === 123
&& bar === 'abc'
) {
thing1();
}
// good
if (
(foo === 123 || bar === 'abc')
&& doesItLookGoodWhenItBecomesThatLong()
&& isThisReallyHappening()
) {
thing1();
}
// good
if (foo === 123 && bar === 'abc') {
thing1();
}
- 17.2 อย่าใช้ selection operators แทนที่ control statements
// bad
!isRunning && startRunning();
// good
if (!isRunning) {
startRunning();
}
คอมเมนต์ (Comments)
- 18.1 ใช้
/** ... */สำหรับคอมเมนต์หลายบรรทัด รวมคำอธิบาย ระบุประเภทและค่าสำหรับพารามิเตอร์ทั้งหมดและค่าที่ส่งคืน
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// ...stuff...
return element;
}
- 18.2 ใช้
//สำหรับคอมเมนต์บรรทัดเดียว วางคอมเมนต์บรรทัดเดียวไว้บนบรรทัดใหม่เหนือหัวข้อของคอมเมนต์ ใส่บรรทัดว่างก่อนคอมเมนต์เว้นแต่จะอยู่บรรทัดแรกของบล็อก
// bad
const active = true; // is current tab
// good
// is current tab
const active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
// also good
function getType() {
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
-
18.3 การขึ้นต้นคอมเมนต์ของคุณด้วย
FIXMEหรือTODOจะช่วยให้นักพัฒนาคนอื่นเข้าใจได้อย่างรวดเร็วว่าคุณกำลังชี้ให้เห็นปัญหาที่ต้องกลับมาดูใหม่ หรือคุณกำลังแนะนำวิธีแก้ปัญหาที่จำเป็นต้องดำเนินการ แตกต่างจากคอมเมนต์ทั่วไปเพราะสามารถดำเนินการต่อได้ การกระทำคือFIXME: -- need to figure this outหรือTODO: -- need to implement -
18.4 ใช้
// FIXME:เพื่อจดบันทึกปัญหา
class Calculator extends Abacus {
constructor() {
super();
// FIXME: shouldn't use a global here
total = 0;
}
}
- 18.5 ใช้
// TODO:เพื่อจดบันทึกวิธีแก้ปัญหา
class Calculator extends Abacus {
constructor() {
super();
// TODO: total should be configurable by an options param
this.total = 0;
}
}
การเว้นวรรค (Whitespace)
- 19.1 ใช้ soft tabs (ตัวอักษร space) ตั้งค่าเป็น 2 spaces eslint:
indentjscs:validateIndentation
// bad
function foo() {
∙∙∙∙const name;
}
// bad
function bar() {
∙const name;
}
// good
function baz() {
∙∙const name;
}
- 19.2 วาง 1 space ก่อนปีกกาเปิด eslint:
space-before-blocksjscs:requireSpaceBeforeBlockStatements
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});
- 19.3 วาง 1 space ก่อนวงเล็บเปิดใน control statements (
if,whileฯลฯ) ไม่ต้องเว้นวรรคระหว่างรายการอาร์กิวเมนต์และชื่อฟังก์ชันในการเรียกและประกาศฟังก์ชัน eslint:space-after-keywords,space-before-keywordsjscs:requireSpaceAfterKeywords
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}
- 19.4 แยกตัวดำเนินการด้วย spaces eslint:
space-infix-opsjscs:requireSpaceBeforeBinaryOperators,requireSpaceAfterBinaryOperators
// bad
const x=y+5;
// good
const x = y + 5;
- 19.5 จบไฟล์ด้วยอักขระขึ้นบรรทัดใหม่หนึ่งตัว
// bad
(function (global) {
// ...stuff...
})(this);
// bad
(function (global) {
// ...stuff...
})(this);↵
↵
// good
(function (global) {
// ...stuff...
})(this);↵
- 19.6 ใช้การย่อหน้าเมื่อสร้าง method chains ที่ยาว (มากกว่า 2 method chains) ใช้จุดนำหน้า ซึ่งเน้นว่าเป็นบรรทัดของการเรียกเมธอด ไม่ใช่คำสั่งใหม่ eslint:
newline-per-chained-callno-whitespace-before-property
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// bad
$('#items').
find('.selected').
highlight().
end().
find('.open').
updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
// bad
const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true)
.attr('width', (radius + margin) * 2).append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
// good
const leds = stage.selectAll('.led')
.data(data)
.enter().append('svg:svg')
.classed('led', true)
.attr('width', (radius + margin) * 2)
.append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
// good
const leds = stage.selectAll('.led').data(data);
- 19.7 เว้นบรรทัดว่างหลังจากบล็อกและก่อนคำสั่งถัดไป jscs:
requirePaddingNewLinesAfterBlocks
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
// bad
const obj = {
foo() {
},
bar() {
},
};
return obj;
// good
const obj = {
foo() {
},
bar() {
},
};
return obj;
// bad
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// good
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
- 19.8 อย่าเติมบรรทัดว่างในบล็อกของคุณ (pad blocks) eslint:
padded-blocksjscs:disallowPaddingNewlinesInBlocks
// bad
function bar() {
console.log(foo);
}
// also bad
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}
// good
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
- 19.9 อย่าเพิ่มช่องว่างภายในวงเล็บ (parentheses) eslint:
space-in-parensjscs:disallowSpacesInsideParentheses
// bad
function bar( foo ) {
return foo;
}
// good
function bar(foo) {
return foo;
}
// bad
if ( foo ) {
console.log(foo);
}
// good
if (foo) {
console.log(foo);
}
- 19.10 อย่าเพิ่มช่องว่างภายในวงเล็บเหลี่ยม (brackets) eslint:
array-bracket-spacingjscs:disallowSpacesInsideArrayBrackets
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
- 19.11 เพิ่มช่องว่างภายในวงเล็บปีกกา (curly braces) eslint:
object-curly-spacingjscs: [disallowSpacesInsideObjectBrackets](http://jscs.info/rule/
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
- 19.12 หลีกเลี่ยงบรรทัดที่ยาวเกิน 100 ตัวอักษร (รวมช่องว่าง) eslint:
max-lenjscs:maximumLineLength
ทำไม? ช่วยให้มั่นใจได้ถึงความสามารถในการอ่านและการบำรุงรักษา
// bad
const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!';
// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
// good
const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. ' +
'Whatever wizard constrains a helpful ally. The counterpart ascends!';
// good
$.ajax({
method: 'POST',
url: 'https://airbnb.com/',
data: { name: 'John' },
})
.done(() => console.log('Congratulations!'))
.fail(() => console.log('You have failed this city.'));
คอมมา (Commas)
- 20.1 คอมมานำหน้า (Leading commas): ไม่ eslint:
comma-stylejscs:requireCommaBeforeLineBreak
// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
// bad
const hero = {
firstName: 'Ada'
, lastName: 'Lovelace'
, birthYear: 1815
, superPower: 'computers'
};
// good
const hero = {
firstName: 'Ada',
lastName: 'Lovelace',
birthYear: 1815,
superPower: 'computers',
};
- 20.2 คอมมาต่อท้ายเพิ่มเติม (Additional trailing comma): ใช่ eslint:
comma-danglejscs:requireTrailingComma
ทำไม? สิ่งนี้ทำให้ git diff สะอาดตาขึ้น นอกจากนี้ transpilers เช่น Babel จะลบคอมมาต่อท้ายเพิ่มเติมในโค้ดที่ transpile แล้ว ซึ่งหมายความว่าคุณไม่ต้องกังวลเกี่ยวกับ ปัญหา trailing comma ในเบราว์เซอร์รุ่นเก่า
// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb graph', 'modern nursing']
};
// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
const heroes = [
'Batman',
'Superman'
];
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
อัฒภาค (Semicolons)
- 21.1 ใช่ eslint:
semijscs:requireSemicolons
// bad
(function () {
const name = 'Skywalker'
return name
})()
// good
(() => {
const name = 'Skywalker';
return name;
}());
// good (guards against the function becoming an argument when two files with IIFEs are concatenated)
;(() => {
const name = 'Skywalker';
return name;
}());
การแปลงประเภทและการบีบบังคับ (Type Casting & Coercion)
// => this.reviewScore = 9;
// bad
const totalScore = this.reviewScore + '';
// good
const totalScore = String(this.reviewScore);
- 22.3 ตัวเลข: ใช้
Numberสำหรับการ type casting และparseIntเสมอกับ radix สำหรับการแยกวิเคราะห์สตริง eslint:radix
const inputValue = '4';
// bad
const val = new Number(inputValue);
// bad
const val = +inputValue;
// bad
const val = inputValue >> 0;
// bad
const val = parseInt(inputValue);
// good
const val = Number(inputValue);
// good
const val = parseInt(inputValue, 10);
- 22.4 หากคุณกำลังทำอะไรที่แปลกๆ ด้วยเหตุผลบางอย่างและ
parseIntเป็นคอขวดและจำเป็นต้องใช้ Bitshift ด้วย เหตุผลด้านประสิทธิภาพ ให้อธิบายว่าทำไมและคุณกำลังทำอะไร
// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
const val = inputValue >> 0;
- 22.5 หมายเหตุ: ระวังเมื่อใช้การดำเนินการ bitshift ตัวเลขจะแสดงเป็น ค่า 64-bit แต่การดำเนินการ bitshift จะส่งคืน integer 32-bit เสมอ (แหล่งที่มา) Bitshift อาจนำไปสู่พฤติกรรมที่ไม่คาดคิดสำหรับค่า integer ที่ใหญ่กว่า 32 bits การอภิปราย integer 32-bit ที่ใหญ่ที่สุดที่มีเครื่องหมายคือ 2,147,483,647:
2147483647 >> 0 //=> 2147483647
2147483648 >> 0 //=> -2147483648
2147483649 >> 0 //=> -2147483647
- 22.6 Booleans:
const age = 0;
// bad
const hasAge = new Boolean(age);
// good
const hasAge = Boolean(age);
// good
const hasAge = !!age;
ข้อตกลงการตั้งชื่อ (Naming Conventions)
- 23.1 หลีกเลี่ยงชื่อตัวอักษรเดียว ให้ตั้งชื่อที่สื่อความหมาย
// bad
function q() {
// ...stuff...
}
// good
function query() {
// ..stuff..
}
- 23.2 ใช้ camelCase เมื่อตั้งชื่อออบเจกต์ ฟังก์ชัน และ instances eslint:
camelcasejscs:requireCamelCaseOrUpperCaseIdentifiers
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
- 23.3 ใช้ PascalCase เมื่อตั้งชื่อ constructors หรือ classes เท่านั้น eslint:
new-capjscs:requireCapitalizedConstructors
// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});
- 23.4 ใช้ขีดล่างนำหน้า
_เมื่อตั้งชื่อคุณสมบัติส่วนตัว (private properties) eslint:no-underscore-danglejscs:disallowDanglingUnderscores
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
// good
this._firstName = 'Panda';
- 23.5 อย่าบันทึกการอ้างอิงถึง
thisให้ใช้ arrow functions หรือ Function#bind jscs:disallowNodeTypes
// bad
function foo() {
const self = this;
return function () {
console.log(self);
};
}
// bad
function foo() {
const that = this;
return function () {
console.log(that);
};
}
// good
function foo() {
return () => {
console.log(this);
};
}
- 23.6 หากไฟล์ของคุณ export class เดียว ชื่อไฟล์ของคุณควรเป็นชื่อเดียวกับ class นั้นทุกประการ
// file contents
class CheckBox {
// ...
}
export default CheckBox;
// in some other file
// bad
import CheckBox from './checkBox';
// bad
import CheckBox from './check_box';
// good
import CheckBox from './CheckBox';
- 23.7 ใช้ camelCase เมื่อคุณ export default function ชื่อไฟล์ของคุณควรเหมือนกับชื่อฟังก์ชัน
function makeStyleGuide() {
}
export default makeStyleGuide;
- 23.8 ใช้ PascalCase เมื่อคุณ export singleton / function library / bare object
const AirbnbStyleGuide = {
es6: {
}
};
export default AirbnbStyleGuide;
Accessors
- 24.1 ฟังก์ชัน Accessor สำหรับคุณสมบัติไม่จำเป็น
- 24.2 อย่าใช้ JavaScript getters/setters เพราะมันทำให้เกิดผลข้างเคียงที่ไม่คาดคิดและยากต่อการทดสอบ บำรุงรักษา และทำความเข้าใจ แทนที่จะทำเช่นนั้น หากคุณสร้างฟังก์ชัน accessor ให้ใช้ getVal() และ setVal(‘hello’)
// bad
dragon.age();
// good
dragon.getAge();
// bad
dragon.age(25);
// good
dragon.setAge(25);
- 24.3 หากคุณสมบัติเป็น boolean ให้ใช้
isVal()หรือhasVal()
// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
}
- 24.4 ไม่เป็นไรที่จะสร้างฟังก์ชัน get() และ set() แต่ต้องสม่ำเสมอ
class Jedi {
constructor(options = {}) {
const lightsaber = options.lightsaber || 'blue';
this.set('lightsaber', lightsaber);
}
set(key, val) {
this[key] = val;
}
get(key) {
return this[key];
}
}
เหตุการณ์ (Events)
- 25.1 เมื่อแนบข้อมูล payloads ไปกับเหตุการณ์ (ไม่ว่าจะเป็น DOM events หรือสิ่งที่เฉพาะเจาะจงกว่าเช่น Backbone events) ให้ส่ง hash แทนที่จะเป็นค่าดิบ สิ่งนี้ช่วยให้ผู้สนับสนุนคนต่อไปสามารถเพิ่มข้อมูลไปยัง event payload ได้โดยไม่ต้องค้นหาและอัปเดตทุก handler สำหรับเหตุการณ์นั้น ตัวอย่างเช่น แทนที่จะเป็น:
// bad
$(this).trigger('listingUpdated', listing.id);
...
$(this).on('listingUpdated', (e, listingId) => {
// do something with listingId
});
ควรใช้:
// good
$(this).trigger('listingUpdated', { listingId: listing.id });
...
$(this).on('listingUpdated', (e, data) => {
// do something with data.listingId
});
jQuery
- 26.1 นำหน้าตัวแปรออบเจกต์ jQuery ด้วย
$jscs:requireDollarBeforejQueryAssignment
// bad
const sidebar = $('.sidebar');
// good
const $sidebar = $('.sidebar');
// good
const $sidebarBtn = $('.sidebar-btn');
- 26.2 แคชการค้นหา jQuery (jQuery lookups)
// bad
function setSidebar() {
$('.sidebar').hide();
// ...stuff...
$('.sidebar').css({
'background-color': 'pink'
});
}
// good
function setSidebar() {
const $sidebar = $('.sidebar');
$sidebar.hide();
// ...stuff...
$sidebar.css({
'background-color': 'pink'
});
}
- 26.3 สำหรับ DOM queries ให้ใช้ Cascading
$('.sidebar ul')หรือ parent > child$('.sidebar > ul'). jsPerf - 26.4 ใช้
findกับ scope ของการค้นหาออบเจกต์ jQuery
// bad
$('ul', '.sidebar').hide();
// bad
$('.sidebar').find('ul').hide();
// good
$('.sidebar ul').hide();
// good
$('.sidebar > ul').hide();
// good
$sidebar.find('ul').hide();
ความเข้ากันได้ของ ECMAScript 5 (ECMAScript 5 Compatibility)
- 27.1 อ้างถึง ตารางความเข้ากันได้ ของ ES5 ของ Kangax
สไตล์ ECMAScript 6 (ECMAScript 6 Styles)
- 28.1 นี่คือคอลเล็กชันของลิงก์ไปยังฟีเจอร์ ES6 ต่างๆ
- Arrow Functions
- Classes
- Object Shorthand
- Object Concise
- Computed Object Properties
- Template Strings
- Destructuring
- Default Parameters
- Rest
- Array Spreads
- Let and Const
- Iterators and Generators
- Modules
ไลบรารีมาตรฐาน (Standard Library)
Standard Library มี utilities บางอย่างที่เสียแต่ยังคงอยู่ด้วยเหตุผลดั้งเดิม
- 29.1 ใช้
Number.isNaNแทนisNaN
ทำไม?
isNaNบีบบังคับค่าที่ไม่ใช่ตัวเลขให้เป็นตัวเลข โดยคืนค่า true สำหรับทุกสิ่งที่บีบบังคับเป็น NaN หากต้องการพฤติกรรมนี้ ให้ทำให้ชัดเจน
// bad
isNaN('1.2'); // false
isNaN('1.2.3'); // true
// good
Number.isNaN('1.2.3'); // false
Number.isNaN(Number('1.2.3')); // true
- 29.2 ใช้
Number.isFiniteแทนisFinite
ทำไม?
isFiniteบีบบังคับค่าที่ไม่ใช่ตัวเลขให้เป็นตัวเลข โดยคืนค่า true สำหรับทุกสิ่งที่บีบบังคับเป็นตัวเลขจำกัด หากต้องการพฤติกรรมนี้ ให้ทำให้ชัดเจน
// bad
isFinite('2e3'); // true
// good
Number.isFinite('2e3'); // false
Number.isFinite(parseInt('2e3', 10)); // true
การทดสอบ (Testing)
- 30.1 ใช่
function foo() {
return true;
}
- 30.2 ไม่, แต่จริงๆ นะ:
- ไม่ว่าคุณจะใช้เฟรมเวิร์กการทดสอบใด คุณควรเขียนการทดสอบ!
- พยายามเขียนฟังก์ชันบริสุทธิ์ (pure functions) ขนาดเล็กจำนวนมาก และลดจุดที่เกิดการเปลี่ยนแปลง (mutation) ให้น้อยที่สุด
- ระวังเกี่ยวกับ stubs และ mocks - พวกมันอาจทำให้การทดสอบของคุณเปราะบางมากขึ้น
- เราส่วนใหญ่ใช้
mochaที่ Airbnbtapeก็ถูกใช้เป็นครั้งคราวสำหรับโมดูลขนาดเล็กที่แยกจากกัน - 100% test coverage เป็นเป้าหมายที่ดีที่ควรพยายามไปให้ถึง แม้ว่าจะไม่สามารถทำได้จริงเสมอไป
- เมื่อใดก็ตามที่คุณแก้ไขข้อบกพร่อง เขียน regression test ข้อบกพร่องที่ได้รับการแก้ไขโดยไม่มี regression test เกือบจะแน่นอนว่าจะพังอีกครั้งในอนาคต
ประสิทธิภาพ (Performance)
- On Layout & Web Performance
- String vs Array Concat
- Try/Catch Cost In a Loop
- Bang Function
- jQuery Find vs Context, Selector
- innerHTML vs textContent for script text
- Long String Concatenation
- Loading…
แหล่งข้อมูล (Resources)
การเรียนรู้ ES6
- Draft ECMA 2015 (ES6) Spec
- ExploringJS
- ES6 Compatibility Table
- Comprehensive Overview of ES6 Features
ควรอ่าน
เครื่องมือ
- Code Style Linters
คู่มือสไตล์อื่นๆ
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
สไตล์อื่นๆ
- Naming this in nested functions - Christian Johansen
- Conditional Callbacks - Ross Allen
- Popular JavaScript Coding Conventions on Github - JeongHoon Byun
- Multiple var statements in JavaScript, not superfluous - Ben Alman
การอ่านเพิ่มเติม
- Understanding JavaScript Closures - Angus Croll
- Basic JavaScript for the impatient programmer - Dr. Axel Rauschmayer
- You Might Not Need jQuery - Zack Bloom & Adam Schwartz
- ES6 Features - Luke Hoban
- Frontend Guidelines - Benjamin De Cock
หนังสือ
- JavaScript: The Good Parts - Douglas Crockford
- JavaScript Patterns - Stoyan Stefanov
- Pro JavaScript Design Patterns - Ross Harmes and Dustin Diaz
- High Performance Web Sites: Essential Knowledge for Front-End Engineers - Steve Souders
- Maintainable JavaScript - Nicholas C. Zakas
- JavaScript Web Applications - Alex MacCaw
- Pro JavaScript Techniques - John Resig
- Smashing Node.js: JavaScript Everywhere - Guillermo Rauch
- Secrets of the JavaScript Ninja - John Resig and Bear Bibeault
- Human JavaScript - Henrik Joreteg
- Superhero.js - Kim Joar Bekkelund, Mads Mobæk, & Olav Bjorkoy
- JSBooks - Julien Bouquillon
- Third Party JavaScript - Ben Vinegar and Anton Kovalyov
- Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript - David Herman
- Eloquent JavaScript - Marijn Haverbeke
- You Don’t Know JS: ES6 & Beyond - Kyle Simpson
บล็อก
- DailyJS
- JavaScript Weekly
- JavaScript, JavaScript…
- Bocoup Weblog
- Adequately Good
- NCZOnline
- Perfection Kills
- Ben Alman
- Dmitry Baranovskiy
- Dustin Diaz
- nettuts
พอดแคสต์
ในโลกแห่งความเป็นจริง (In the Wild)
นี่คือรายชื่อบริษัทที่ใช้คู่มือสไตล์นี้ หากคุณใช้ ส่ง pull request เพื่อตรวจสอบตัวเองเข้ามา
- Aan Zee: AanZee/javascript
- Adult Swim: adult-swim/javascript
- Airbnb: airbnb/javascript
- Apartmint: apartmint/javascript
- Avalara: avalara/javascript
- Avant: avantcredit/javascript
- Billabong: billabong/javascript
- Bisk: bisk/javascript
- Blendle: blendle/javascript
- Brainshark: brainshark/javascript
- ComparaOnline: comparaonline/javascript
- Compass Learning: compasslearning/javascript-style-guide
- DailyMotion: dailymotion/javascript
- Digitpaint digitpaint/javascript
- Ecosia: ecosia/javascript
- Evernote: evernote/javascript-style-guide
- Evolution Gaming: evolution-gaming/javascript
- ExactTarget: ExactTarget/javascript
- Expensify Expensify/Style-Guide
- Flexberry: Flexberry/javascript-style-guide
- Gawker Media: gawkermedia/javascript
- General Electric: GeneralElectric/javascript
- GoodData: gooddata/gdc-js-style
- Grooveshark: grooveshark/javascript
- How About We: howaboutwe/javascript
- Huballin: huballin/javascript
- HubSpot: HubSpot/javascript
- Hyper: hyperoslo/javascript-playbook
- InfoJobs: InfoJobs/JavaScript-Style-Guide
- Intent Media: intentmedia/javascript
- Jam3: Jam3/Javascript-Code-Conventions
- JeopardyBot: kesne/jeopardy-bot
- JSSolutions: JSSolutions/javascript
- Kinetica Solutions: kinetica/javascript
- Mighty Spring: mightyspring/javascript
- MinnPost: MinnPost/javascript
- MitocGroup: MitocGroup/javascript
- ModCloth: modcloth/javascript
- Money Advice Service: moneyadviceservice/javascript
- Muber: muber/javascript
- National Geographic: natgeo/javascript
- National Park Service: nationalparkservice/javascript
- Nimbl3: nimbl3/javascript
- Orion Health: orionhealth/javascript
- OutBoxSoft: OutBoxSoft/javascript
- Peerby: Peerby/javascript
- Razorfish: razorfish/javascript-style-guide
- reddit: reddit/styleguide/javascript
- React: /facebook/react/blob/master/CONTRIBUTING.md#style-guide
- REI: reidev/js-style-guide
- Ripple: ripple/javascript-style-guide
- SeekingAlpha: seekingalpha/javascript-style-guide
- Shutterfly: shutterfly/javascript
- Springload: springload/javascript
- StudentSphere: studentsphere/javascript
- Target: target/javascript
- TheLadders: TheLadders/javascript
- T4R Technology: T4R-Technology/javascript
- VoxFeed: VoxFeed/javascript-style-guide
- WeBox Studio: weboxstudio/javascript
- Weggo: Weggo/javascript
- Zillow: zillow/javascript
- ZocDoc: ZocDoc/javascript
การแปล (Translation)
คู่มือสไตล์นี้ยังมีให้ในภาษาอื่นๆ:
Brazilian Portuguese: armoucar/javascript-style-guide
Bulgarian: borislavvv/javascript
Catalan: fpmweb/javascript-style-guide
Chinese (Simplified): sivan/javascript-style-guide
Chinese (Traditional): jigsawye/javascript
French: nmussy/javascript-style-guide
German: timofurrer/javascript-style-guide
Italian: sinkswim/javascript-style-guide
Japanese: mitsuruog/javacript-style-guide
Korean: tipjs/javascript-style-guide
Polish: mjurczyk/javascript
Russian: uprock/javascript
Spanish: paolocarrasco/javascript-style-guide
Thai: lvarayut/javascript-style-guide
The JavaScript Style Guide Guide
แชทกับเราเกี่ยวกับ JavaScript
- พบกับเราได้ที่ gitter
ผู้สนับสนุน (Contributors)
License
(The MIT License)
Copyright (c) 2014-2016 Airbnb
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Amendments
เราขอแนะนำให้คุณ fork คู่มือนี้และเปลี่ยนกฎให้เหมาะกับคู่มือสไตล์ของทีมคุณ ด้านล่างนี้ คุณอาจระบุการแก้ไขบางอย่างในคู่มือสไตล์ สิ่งนี้ช่วยให้คุณสามารถอัปเดตคู่มือสไตล์ของคุณเป็นระยะๆ โดยไม่ต้องจัดการกับความขัดแย้งในการรวม (merge conflicts)