Airbnb JavaScript Style Guide() {
คู่มือแนะนำการเขียนจาวาสคริปต์ที่เข้าท่ามากที่สุด โดย Airbnb
คู่มือนี้ผมแปลโดยใส่คำอธิบายและตัวอย่างเพิ่มเติม (ไม่แปลตรงตัว) เพื่อให้ผู้อ่านสามารถเข้าใจเนื้อหาต่าง ๆ ได้ดียิ่งขึ้น ในกรณีที่เจอข้อผิดพลาดใด ๆ กรุณา Fork และ PR ถ้ามีคำถามสามารถเปิด Issue ได้เลยครับ หวังว่าคู่มือนี้จะมีประโยชน์ต่อผู้อ่านไม่มากก็น้อย
🙏
คู่มือแนะนำอื่น ๆ
สารบัญ
- Types
- References
- Objects
- Arrays
- Destructuring
- Strings
- Functions
- Arrow Functions
- Classes & Constructors
- Modules
- Iterators and Generators
- Properties
- Variables
- Hoisting
- Comparison Operators & Equality
- Blocks
- Control Statements
- Comments
- Whitespace
- Commas
- Semicolons
- Type Casting & Coercion
- Naming Conventions
- Accessors
- Events
- jQuery
- ECMAScript 5 Compatibility
- ECMAScript 6+ (ES 2015+) Styles
- Standard Library
- Testing
- Performance
- Resources
- In the Wild
- Translation
- The JavaScript Style Guide Guide
- Chat With Us About Javascript
- Contributors
- License
- Amendments
Types
-
1.1 Primitives: เมื่อใช้งานตัวแปรพื้นฐาน (ตัวแปรที่อ้างอิงด้วยค่า) สามารถเข้าใช้งานได้โดยอ้างอิงค่าของตัวแปร
string
number
boolean
null
undefined
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
-
1.2 Complex: เมื่อใช้งานตัวแปรที่มีความซับซ้อน (ตัวแปรที่อ้างอิงไปยังค่าที่อยู่ของตัวแปรอื่น) สามารถเข้าใช้งานได้โดยอ้างอิงค่าที่อยู่ของตัวแปรนั้น ๆ
object
array
function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
References
-
2.1 ใช้
const
สำหรับค่าคงที่ และหลีกเลี่ยงการใช้var
เพราะว่าการใช้งาน
const
จะทำให้เราไม่สามารถเปลี่ยนแปลงค่าได้อีก ซึ่งป้องกันข้อผิดพลาดต่าง ๆ ที่อาจจะเกิดขึ้น (ในกรณีที่เราลืมไปเปลี่ยนแปลงค่าของตัวแปร หรือมีไลบรารี่อื่น ๆ ที่เราใช้มาเปลี่ยนแปลงค่าตัวแปรของเรา)// ไม่ดี var a = 1; var b = 2; // ดี const a = 1; const b = 2;
-
2.2 ถ้าต้องการตัวแปรที่เปลี่ยนแปลงค่าได้ให้ใช้
let
และหลีกเลี่ยงการใช้var
เพราะว่า
let
จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) ซึ่งไม่เหมือนvar
ที่มีค่าอยู่ในฟังก์ชันที่ประกาศ (Function-scoped)// ไม่ดี var count = 1; if (true) { count += 1; } // ดี let count = 1; if (true) { count += 1; }
-
2.3
let
และconst
จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) เท่านั้น{ let a = 1; const b = 1; } console.log(a); // ReferenceError เมื่อออกนอกปีกกาที่ประกาศจะไม่สามารถเรียกใช้งานตัวแปรได้ console.log(b); // ReferenceError เมื่อออกนอกปีกกาที่ประกาศจะไม่สามารถเรียกใช้งานตัวแปรได้
Objects
-
3.1 ควรใช้ปีกกา
{}
ในการประกาศออบเจ็กต์// ไม่ดี const item = new Object(); // ดี const item = {};
-
3.2 อย่าใช้คำสงวน เป็นคีย์ เพราะมันจะใช้ไม่ได้ใน IE8. อ่านเพิ่มเติม แต่ถ้าเราสร้างโมดูลของตัวเองก็สามารถใช้คำเหล่านี้ได้ (แต่ไม่ใช้จะดีกว่าในความเห็นของผมนะครับ)
// ไม่ดี const superman = { default: { clark: 'kent' }, // default เป็นคำสงวน private: true, }; // ดี const superman = { defaults: { clark: 'kent' }, hidden: true, };
-
3.3 ใช้คำที่มีความหมายเหมือนกันแทนคำสงวน
// ไม่ดี const superman = { class: 'alien', // class เป็นคำสงวน }; // ไม่ดี const superman = { klass: 'alien', // แปลงคำไม่ใช่สิ่งดี เพราะจะทำให้เดาความหมายได้ยาก }; // ดี const superman = { type: 'alien', };
-
3.4 ถ้าต้องการสร้างพรอพเพอร์ตี้ของออบเจ็กต์จากตัวแปร (Dynamic property) ให้สร้างตอนที่ประกาศออบเจ็กต์โดยใช้
[]
เพราะจะทำให้พรอพเพอร์ตี้ทั้งหมดถูกสร้างไว้ในที่เดียว ซึ่งทำให้ดูได้ง่ายกว่าการสร้างแยกกัน
function getKey(k) { return `a key named ${k}`; } // ไม่ดี const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // สร้างหลังจากประกาศออบเจ็กต์เสร็จแล้ว ทำให้มองยากกว่า // ดี const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, // สร้างตอนประกาศออบเจ็กต์ ทำให้มองเห็นพรอพเพอร์ตี้ของออบเจ็กต์ทั้งหมดในที่เดียว };
-
3.5 สร้างเมท็อตโดยใช้วิธีการประกาศแบบย่อ (Object method shorthand)
// ไม่ดี const atom = { value: 1, addValue: function (value) { // การประกาศแบบปกติ return atom.value + value; }, }; // ดี const atom = { value: 1, addValue(value) { // การประกาศแบบย่อ ซึ่งตัดคีย์เวิร์ดฟังก์ชันออกไป ทำให้โค้ดอ่านง่ายขึ้น return atom.value + value; }, };
-
3.6 สร้องพรอพเพอร์ตี้โดยใช้วิธีการประกาศแบบย่อ (Property value shorthand)
เพราะว่าทำให้อ่านง่ายขึ้น และเข้าใจได้ง่ายกว่า
const lukeSkywalker = 'Luke Skywalker'; // ไม่ดี const obj = { lukeSkywalker: lukeSkywalker, }; // ดี const obj = { lukeSkywalker, // มีค่าเท่ากับด้านบนเพียงแต่ทำให้อ่านง่ายขึ้น (ถ้าต้องการให้ชื่อตัวแปรและชื่อพรอพเพอร์ตี้ต่างกัน ต้องใช้วิธีการประกาศแบบด้านบน) };
-
3.7 พรอพเพอร์ตี้ที่ประกาศโดยใช้วิธีการประกาศแบบย่อ ให้ใส่ไว้ด้านบนสุดของการประกาศออบเจ็กต์
เพราะทำให้รู้ได้ว่าพรอพเพอร์ตี้ใด ที่ประกาศโดยใช้วิธีการประกาศแบบย่อ
const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // ไม่ดี const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // ดี const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
Arrays
-
4.1 ใช้วงเล็บก้ามปู
[]
ในการประกาศอาร์เรย์// ไม่ดี const items = new Array(); // ดี const items = [];
-
4.2 ใช้ฟังก์ชัน Array#push ในการใส่ค่าเข้าไปในอาร์เรย์แทนการใส่ค่าโดยตรง
const someStack = []; // ไม่ดี someStack[someStack.length] = 'abracadabra'; // ดี someStack.push('abracadabra');
-
4.3 ใช้
...
(Spreads) ในการทำสำเนาอาร์เรย์.// ไม่ดี const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // ดี const itemsCopy = [...items];
-
4.4 ใช้ฟังก์ชัน Array#from ในการแปลงอ็อบเจ็กต์เป็นอาร์เรย์
const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo);
Destructuring
-
5.1 ใช้รูปแบบของ
Destructuring
เมื่อต้องการแปลงพรอพเพอร์ตี้ของอ็อบเจ็กต์ให้เป็นตัวแปรเพราะจะได้ไม่ต้องสร้างตัวแปรชั่วคราวมารับพรอพเพอร์ตี้เหล่านั้น
// ไม่ดี function getFullName(user) { const firstName = user.firstName; // ต้องสร้างตัวแปรทั่วคราวมารับค่า const lastName = user.lastName; // ต้องสร้างตัวแปรทั่วคราวมารับค่า return `${firstName} ${lastName}`; } // ดี function getFullName(obj) { const { firstName, lastName } = obj; // ใช้ Destructuring ในการแปลงค่าอ็อบเจ็กต์ให้เป็นตัวแปร return `${firstName} ${lastName}`; } // ดีที่สุด function getFullName({ firstName, lastName }) { // รับค่าโดยใช้ Destructuring return `${firstName} ${lastName}`; }
-
5.2 ใช้รูปแบบของ
Destructuring
เมื่อต้องการแปลงอิลีเม้นท์ของอาร์เรย์ให้เป็นตัวแปรconst arr = [1, 2, 3, 4]; // ไม่ดี const first = arr[0]; const second = arr[1]; // ดี const [first, second] = arr; // ใช้ Destructuring ในการแปลงค่าอาร์เรย์ให้เป็นตัวแปร
-
5.3 ใช้
Destructuring
ในรูปแบบออบเจ็กต์ในการส่งค่าหลายค่ากลับไปจากฟังก์ชัน (อย่าใช้ Destructuring ในรูปแบบอาร์เรย์ )เพราะ Destructuring ในรูปแบบออบเจ็กต์จะทำให้ลำดับการส่งค่าไม่สำคัญ (สามารถสลับที่กันได้) เผื่อว่าในอนาคตเราอาจจะเพิ่มค่าเข้าไป ก็จะไม่ต้องกังวลเรื่องลำดับ
// ไม่ดี function processInput(input) { return [left, right, top, bottom]; } // คนที่เรียกใช้งานฟังก์ชันจะต้องคำนึงถึงลำดับของตัวแปรที่จะส่งไป const [left, __, top] = processInput(input); // ดี function processInput(input) { return { left, right, top, bottom }; } // คนที่เรียกใช้งานฟังก์ชันสามารถส่งเฉพาะค่าที่ตนเองต้องการ โดยมันจะจับคู่ให้อัตโนมัติ const { left, right } = processInput(input);
Strings
-
6.1 ใช้เขี้ยวเดียว (Single quotes)
''
สำหรับสตริง// ไม่ดี const name = "Capt. Janeway"; // ดี const name = 'Capt. Janeway';
-
6.2 สตริงที่ยาวกว่า 80 ตัวอักษร ควรจะแยกเขียนในหลายบรรทัด และค่อยทำการเชื่อมต่อกัน
-
6.3 หมายเหตุ: ไม่ควรใช้สตริงที่ยาวมากเกินไป เพราะจะมีผลต่อประสิทธิภาพของแอพพลิเคชั่น - jsPerf & Discussion
// ไม่ดี 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.'; // ไม่ดี 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.'; // ดี 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) ซึ่งดีกว่าการเชื่อมสตริงด้วยตนเอง
เพราะว่าเทมเพลตสตริงจะทำให้อ่านง่ายกว่า
// ไม่ดี function sayHi(name) { return 'How are you, ' + name + '?'; } // ไม่ดี function sayHi(name) { return ['How are you, ', name, '?'].join(); } // ดี function sayHi(name) { return `How are you, ${name}?`; }
Functions
-
7.1 ทุกครั้งที่จะประกาศฟังก์ชันให้ประกาศในรูปแบบ Function declarations (อย่าประกาศแบบ Function expressions)
เพราะ Function declarations มีชื่อให้เห็นชัดเจน เมื่อทำการดีบัคโค้ดจะสามารถเห็นชื่อฟังก์ชันใน Call stacks นอกจากนั้นจาวาสคริปต์จะ Hoisting ฟังก์ชันที่ประกาศแบบ Function declarations ทำให้สามารถเรียกใช้ฟังก์ชันได้ทุกที่ เมื่อใดที่ต้องการใช้งาน Function expressions ให้ใช้ Arrow Functions แทนเสมอ
// ไม่ดี const foo = function () { }; // ดี function foo() { }
-
7.2 Function expressions - การประกาศฟังก์ชันและใช้ตัวแปรในการอ้างอิงฟังก์ชันดังกล่าว (อาจจะไม่ใช้ตัวแปรในกรณีที่เป็น Anonymous function) ดังตัวอย่างต่อไปนี้
// immediately-invoked function expression (IIFE) (() => { console.log('Welcome to the Internet. Please follow me.'); })();
-
7.3 อย่าประกาศฟังก์ชันประเภท Function Declarations ไว้ภายใน if, else, while, และอื่น ๆ เพราะบราวเซอร์จะตีความหมายผิด ถ้าจำเป็นต้องประกาศ ให้ประกาศในรูปแบบของ Function Expressions
-
7.4 หมายเหตุ: ECMA-262 บอกไว้ว่าใน if, else, while, และอื่น ๆ จะต้องประกอบไปด้วย statements เท่านั้น ซึ่งการประกาศฟังก์ชันประเภท Function Declarations ไม่ใช่ statement อ่านเพิ่มเติมเกี่ยวกับ ECMA-262
// ไม่ดี if (currentUser) { function test() { console.log('Nope.'); } } // ดี let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
-
7.5 อย่าตั้งชื่อพารามิเตอร์ว่า
arguments
เพราะมันจะไปทับออบเจ็กต์arguments
ที่จาวาสคริปต์มีให้ในทุก ๆ ฟังก์ชัน// ไม่ดี function nope(name, options, arguments) { // ...stuff... } // ดี function yup(name, options, args) { // ...stuff... }
-
7.6 ให้ใช้
...
(Rest) แทนการใช้พารามิเตอร์arguments
เพราะ
...
สามารถทำให้รู้ว่าฟังก์ชันนั้นมีการรับค่าพารามิเตอร์ อีกทั้ง...
จะได้ค่าอาร์เรย์จริง ๆ ไม่ใช่ค่าออบเจ็กต์เหมือนarguments
// ไม่ดี function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // ดี function concatenateAll(...args) { // ทำให้รู้ว่าฟังก์ชันนี้รับพารามิเตอร์ return args.join(''); }
-
7.7 ใส่ค่าเริ่มต้นให้กับพารามิเตอร์ทุกตัว (Default parameter)
// แย่มาก function handleThings(opts) { // ไม่ควรเปลี่ยนค่าของพารามิเตอร์ อ่านเพิ่มเติมได้ที่ http://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments // นอกจากนั้นถ้า opts เป็นเท็จ จะได้ค่าที่เป็นออบเจ็กต์ ซึ่งดูเหมือนว่า // จะเป็นค่าที่เราต้องการ แต่ความจริงนั้นจะทำให้เกิดบัค opts = opts || {}; // ... } // แย่ function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // ดี function handleThings(opts = {}) { // ... }
-
7.8 หลีกเลี่ยงการตั้งค่าที่ยาก ๆ เป็นค่าเริ่มต้นของพารามิเตอร์
เพราะจะทำให้สับสนได้ง่าย
var b = 1; // ไม่ดี function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 เพราะว่ามีการกำหนดอาร์กิวเมนต์เป็น 3 ดังนั้นค่าเริ่มต้นจะไม่ถูกเรียก (= b++ ไม่ถูกเรียก) count(); // 3
Arrow Functions
-
8.1 ทุกครั้งที่ต้องการใช้งาน
Function expressions
(รวมถึง Anonymous functions) ให้ใช้Arrow Functions
แทนเพราะค่าของ this ใน Arrow functions จะมีค่าเท่ากับค่า this ของฟังก์ชันที่ห่อหุ้ม Arrow functions อยู่
แต่ถ้าฟังก์ชันยาวมาก ๆ ให้แยกออกมาเป็น Function declarations แทน
// ไม่ดี [1, 2, 3].map(function (x) { return x * x; }); // ดี [1, 2, 3].map((x) => { return x * x; });
-
8.2 ถ้าฟังก์ชันมีแค่บรรทัดเดียวและมีแค่อาร์กิวเมนต์เดียวให้ลบ
{}
และ()
ออกได้ แต่ในกรณีอื่น ๆ ให้ใช้{}
,()
และreturn
คีย์เวิร์ดเพราะอ่านง่ายกว่า โดยเฉพาะเวลาที่มีการเรียกใช้เมท็อตแบบต่อเนื่อง (Method chaining)
แต่ถ้าต้องการส่งค่ากลับไปแบบออบเจ็กต์ ก็ให้ใช้
{}
และ()
ตามปกติ// ดี [1, 2, 3].map(x => x * x); // จะสังเกตว่า Arrow functions ที่สั้น ๆ แบบนี้เมื่อไม่ใส่ {} และ () แล้วทำให้ดูง่ายขึ้น // ดี [1, 2, 3].reduce((total, n) => { // Arrow functions ที่ซับซ้อนมากขึ้นควรใส่ {} และ () return total + n; }, 0);
Classes & Constructors
-
9.1 ใช้
class
และหลีกเลี่ยงการเรียกใช้prototype
โดยตรงเพราะว่า
class
อ่านง่ายกว่า และง่ายต่อการเข้าใจ// ไม่ดี function Queue(contents = []) { this._queue = [...contents]; } Queue.prototype.pop = function() { const value = this._queue[0]; this._queue.splice(0, 1); return value; } // ดี class Queue { constructor(contents = []) { this._queue = [...contents]; } pop() { const value = this._queue[0]; this._queue.splice(0, 1); return value; } }
-
9.2 ใช้
extends
ในการสืบทอดคลาส (Inheritance)เพราะว่าวิธีนี้เป็นวิธีที่ ES6 ใช้ในการสืบทอดคลาส ซึ่งจะช่วยให้ฟังก์ชัน
instanceof
ทำงานได้อย่างถูกต้อง// ไม่ดี const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function() { return this._queue[0]; } // ดี class PeekableQueue extends Queue { peek() { return this._queue[0]; } }
-
9.3 เมท็อตควรคืนค่าเป็นออบเจ็ค
this
เพื่อช่วยให้สามารถทำ Method chaining// ไม่ดี 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 // ดี 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 สามารถทำการ Overwrite เมท็อต toString() ได้แต่ควรจะตรวจสอบให้มั่นใจว่าจะไม่เกิดข้อผิดพลาดขึ้นได้ในอนาคต
class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } }
-
9.5 โดยปกติ
class
จะมี default constructor ถ้าไม่มีการระบุ constructor ใหม่ ดังนั้นการใส่ constructor ที่ว่างเปล่าจึงไม่มีความจำเป็น// ไม่ดี class Jedi { constructor() {} getName() { return this.name; } } // ไม่ดี class Rey extends Jedi { constructor(...args) { super(...args); } } // ดี class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } }
-
9.6 หลีกเลี้ยงการมีเมท็อตของคลาสที่มีชื่อเดียวกัน
การที่มีเมท็อตที่มีชื่อซ้ำกันในคลาสเดียวกันอาจทำให้เกิดบัค
// ไม่ดี class Foo { bar() { return 1; } bar() { return 2; } } // ดี class Foo { bar() { return 1; } } // ดี class Foo { bar() { return 2; } }
Modules
-
10.1 ควรใช้งานโมดูลในรูปแบบที่ ES6 มีให้ (
import
/export
) แทนการใช้งานโมดูลรูปแบบอื่น ๆ เนื่องจากเราสามารถที่จะคอมไพล์ไฟล์เป็นโมดูลในระบบอื่น ๆ ในภายหลังได้เพราะว่าโมดูลจะเป็นรูปแบบที่ถูกใช้อย่างแพร่หลายในอนาคต
// ไม่ดี const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // ดีที่สุด import { es6 } from './AirbnbStyleGuide'; export default es6;
-
10.2 หลีกเลี่ยงการใช้
*
ในการอิมพอร์ต// ไม่ดี import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // ดี import AirbnbStyleGuide from './AirbnbStyleGuide';
-
10.3 หลีกเลี่ยงการเอ็กพอร์ต โดยตรงจากอิมพอร์ต
เพราะว่ามันจะทำให้ดูยากขึ้น แล้วไม่ค่อยเคลียร์ ถึงแม้ว่ามันจะสั้นกว่าก็ตาม
// ไม่ดี // filename es6.js export { es6 as default } from './airbnbStyleGuide'; // ดี // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;
Iterators and Generators
-
11.1 หลีกเลี่ยงการใช้
Iterators
และหันมาใช้ Higher-order functions เช่นmap()
และreduce()
แทนการใช้งานลูปอย่างfor-of
เพราะว่าการทำงานกับ Pure functions นั้นดูง่ายกว่า และเพื่อเป็นการลดผลกระทบอื่น ๆ ที่อาจจะตามมาได้
const numbers = [1, 2, 3, 4, 5]; // ไม่ดี let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // ดี let sum = 0; numbers.forEach((num) => sum += num); sum === 15; // ดีที่สุด (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15;
-
11.2 หลีกเลี่ยงการใช้งาน
Generators
(ณ ปัจจุบัน)เพราะว่ายังไม่สามารถคอมไพล์กลับไปเป็น ES5 ได้อย่างสมบูรณ์
Properties
-
12.1 ใช้จุด
.
ในการเข้าถึงพรอพเพอร์ตี้ (properties)const luke = { jedi: true, age: 28, }; // ไม่ดี const isJedi = luke['jedi']; // ดี const isJedi = luke.jedi;
-
12.2 ใช้วงเล็บก้ามปู
[]
ในการเข้าถึงพรอพเพอร์ตี้โดยการใช้ตัวแปรconst luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');
Variables
-
13.1 ใช้
const
ในการประกาศตัวแปรเสมอ ถ้าไม่ใช้จะมีผลให้ตัวแปรที่ประกาศขึ้นใหม่เป็นตัวแปรแบบglobal
ซึ่งอาจมีผลต่อไฟล์หรือโมดูลอื่น ๆ// ไม่ดี superPower = new SuperPower(); // ดี const superPower = new SuperPower();
-
13.2 ใช้หนึ่ง
const
ต่อหนึ่งตัวแปรเพราะดูง่ายกว่า และป้องกันข้อผิดพลาดได้ อย่างเช่น บางครั้งใส่สลับกันระหว่าง
;
และ,
ซึ่งทำให้ได้ผลลัพธ์ที่ผิด// ไม่ดี const items = getItems(), goSportsTeam = true, dragonball = 'z'; // ไม่ดี // (compare to above, and try to spot the mistake) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // ดี const items = getItems(); const goSportsTeam = true; const dragonball = 'z';
-
13.3 ประกาศ
const
ไว้ที่เดียวกัน จากนั้นตามด้วยการประกาศlet
ไว้ที่เดียวกัน (อย่าสลับไปมา) และใส่ตัวแปรที่ยังไม่ได้กำหนดค่าไว้ด้านล่างเสมอเพราะเมื่อต้องการที่จะกำหนดค่าให้กับตัวแปรโดยใช้ตัวแปรที่ประกาศไปก่อนหน้านั้น จะสามารถทำได้ง่ายกว่า
// ไม่ดี let i, len, dragonball, items = getItems(), goSportsTeam = true; // ไม่ดี let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // ดี const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length;
-
13.4 ประกาศตัวแปรในที่ ๆ เหมาะสม (จากวิจารณญาณของเราเอง โดยคิดว่าจะทำให้โค้ดมีระเบียบและอ่านง่ายมากขึ้น)
เพราะว่า
let
และconst
จะมีค่าอยู่แค่ในปีกกาที่ประกาศ (Block-scoped) เท่านั้น จึงปลอดภัย// ดี function() { test(); console.log('doing stuff..'); //..other stuff.. const name = getName(); if (name === 'test') { return false; } return name; } // ไม่ดี - unnecessary function call function(hasName) { const name = getName(); if (!hasName) { return false; } this.setFirstName(name); return true; } // ดี function(hasName) { if (!hasName) { return false; } const name = getName(); this.setFirstName(name); return true; }
Hoisting
-
14.1 เวลาคอมไพล์จาวาสคริปต์จะอ่านตัวแปร
var
ที่ประกาศไว้ก่อนหน้าสิ่งอื่น ๆ ในสโคป แต่ค่าที่ใส่ให้ตัวแปรจะยังไม่ถูกอ่าน ส่วนการประกาศconst
และlet
จะใช้วิธีการใหม่ที่เรียกว่า Temporal Dead Zones (TDZ) อ่านเหตุผลของวิธีการใหม่นี้ได้จาก typeof is no longer safeผมขอสรุปคร่าว ๆ จากลิ๊งทางข้างต้นเผื่อคนที่ไม่มีเวลาอ่านนะครับ สำหรับ
let
และconst
นั้น ภายในปีกกาเดียวกันจะไม่สามารถประกาศซ้ำกันสองครั้งได้ นอกจากนั้นตัวแปรจะมีค่าก็ต่อเมื่อคอมไพล์เลอร์อ่านถึงบรรทัดที่ประกาศตัวแปร ดังนั้น typeof foo (ถ้ายังไม่มีการประกาศตัวแปร foo) จะได้ผลลัพธ์เป็น ReferenceError แทนที่จะได้ undefined เหมือนใน ES5// สมมุติว่าเราไม่ได้ประกาศตัวแปร notDefined function example() { console.log(notDefined); // => throws a ReferenceError } // ประกาศตัวแปรหลังจากใช้งาน ในจาวาสคริปต์นั้นทำได้ (ไม่มี error) // เพราะว่าตัวแปรจะถูกคอมไพล์และดึงขึ้นมาไว้ข้างบนสโคป // แต่ค่าของตัวแปรไม่ได้ถูกดึงขึ้นมาด้วย จึงทำให้ค่าของตัวแปรนั้นเป็น undefined function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // ตัวอย่างเมื่อคอมไพล์เลอร์ทำงานในตัวอย่างข้างต้น // คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นมาไว้ด้านบนของสโคป function example() { var 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 - การประกาศฟังก์ชันโดยไม่ใส่ชื่อฟังก์ชัน คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นไปด้านบนของสโคป แต่จะยังไม่อ่านฟังก์ชัน
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 - การประกาศฟังก์ชันโดยใส่ชื่อฟังก์ชัน ได้ผลลัพธ์เหมือนตัวอย่างก่อนหน้า
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'); }; } // ประกาศฟังก์ชันชื่อเดียวกับตัวแปร ก็ได้ผลลัพธ์เช่นเดียวกันกับตัวอย่างก่อนหน้า function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); } }
-
14.4 Function declarations - การประกาศฟังก์ชันโดยไม่ได้ใส่ค่าฟังก์ชันให้ตัวแปร คอมไพล์เลอร์จะอ่านทั้งชื่อและฟังก์ชัน
function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } }
-
อ่านเพิ่มเติมได้ที่ JavaScript Scoping & Hoisting โดย Ben Cherry
Comparison Operators & Equality
-
15.1 ใช้
===
และ!==
แทน==
และ!=
-
15.2 การเปรียบเทียบโอเปอเรเตอร์ จาวาสคริปต์จะแปลงค่าเหล่านั้นเป็น boolean โดยใช้ฟังก์ชัน
ToBoolean
และใช้กฎต่าง ๆ ดังต่อไปนี้:- Objects ได้ผลลัพธ์เป็น true
- Undefined ได้ผลลัพธ์เป็น false
- Null ได้ผลลัพธ์เป็น false
- Booleans ได้ผลลัพธ์ ขึ้นอยู่กับค่าของ boolean
- Numbers ได้ผลลัพธ์เป็น false ถ้า +0, -0, or NaN, นอกนั้นได้ true
- Strings ได้ผลลัพธ์เป็น false ถ้า
''
, นอกนั้นได้ true
if ([0]) { // true // เพราะ array คือออบเจ็กต์ }
-
15.3 ใช้ Shortcuts
// ไม่ดี if (name !== '') { // ...stuff... } // ดี if (name) { // ...stuff... } // ไม่ดี if (collection.length > 0) { // ...stuff... } // ดี if (collection.length) { // ...stuff... }
-
15.4 อ่านเพิ่มเติมได้ที่ Truth Equality and JavaScript โดย Angus Croll.
Blocks
-
16.1 ใช้วงเล็บปีกกา
{}
ในกรณีที่ประกาศบล็อกมากกว่าหนึ่งบรรทัด// ไม่ดี if (test) return false; // ดี if (test) return false; // วางไว้บรรทัดเดียวกันจะอ่านง่ายกว่า // ดี if (test) { return false; } // ไม่ดี function() { return false; } // ดี function() { // ถ้ามีวงเล็บปีกกาให้วางไว้คนละบรรทัดจะอ่านง่ายกว่า return false; }
-
16.2 ถ้าประกาศโดยมีทั้ง
if
และelse
ให้ใส่else
ไว้บรรทัดเดียวกับวงเล็บปีกกาปิดของif
// ไม่ดี if (test) { thing1(); thing2(); } else { thing3(); } // ดี if (test) { thing1(); thing2(); } else { thing3(); }
Control Statements
-
17.1 ในกรณีที่มีการใช้ control statement เช่น
if
,while
และอื่น ๆ มีความยาวเกินความยาวต่อบรรทัดสูงสุด ให้รวมกรุ๊ปแต่ละเงื่อนไขและขึ้นเป็นบรรทัดใหม่// ไม่ดี if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // ไม่ดี if (foo === 123 && bar === 'abc') { thing1(); } // ไม่ดี if (foo === 123 && bar === 'abc') { thing1(); } // ไม่ดี if ( foo === 123 && bar === 'abc' ) { thing1(); } // ดี if ( foo === 123 && bar === 'abc' ) { thing1(); } // ดี if ( (foo === 123 || bar === "abc") && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // ดี if (foo === 123 && bar === 'abc') { thing1(); }
Comments
-
18.1 ใช้
/** ... */
สำหรับคอมเม้นต์ที่มากกว่าหนึ่งบรรทัด และควรจะบอกประเภทและค่าของพารามิเตอร์พร้อมทั้งค่าที่จะรีเทิร์น// ไม่ดี // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ...stuff... return element; } // ดี /** * 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 ใช้
//
สำหรับคอมเม้นต์บรรทัดเดียว โดยใส่ไว้บรรทัดบนของสิ่งที่ต้องการคอมเม้นต์ และเพิ่มบรรทัดว่างไว้ด้านบนคอมเม้นต์ด้วย// ไม่ดี const active = true; // is current tab // ดี // is current tab const active = true; // ไม่ดี function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this._type || 'no type'; return type; } // ดี function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this._type || 'no type'; return type; }
-
18.3 ใส่
FIXME
หรือTODO
ไว้ด้านหน้าคอมเม้นต์ ซึ่งจะช่วยให้ผู้พัฒนาระบบท่านอื่น ๆ ทราบได้ว่าสิ่งเหล่านั้นอาจจะต้องแก้ไข หรือยังไม่ได้ทำ (IDE บางตัวสามารถค้นหาคอมเม้นต์เหล่านี้อัตโนมัติ และบอกถึงสิ่งที่ควรจะแก้ไขหรือทำเพิ่ม) -
18.4 ใช้
// FIXME:
เพื่อบอกปัญหาclass Calculator { constructor() { // FIXME: shouldn't use a global here total = 0; } }
-
18.5 ใช้
// TODO:
เพื่อบอกแนวทางในการแก้ไขปัญหา (แต่ยังไม่ได้ทำ)class Calculator { constructor() { // TODO: total should be configurable by an options param this.total = 0; } }
Whitespace
-
19.1 ควรตั้งค่าหนึ่งแท็บเท่ากับสองช่องว่าง (สามารถตั้งค่าใน Editor หรือ IDE ได้)
// ไม่ดี function() { ∙∙∙∙const name; } // ไม่ดี function() { ∙const name; } // ดี function() { ∙∙const name; }
-
19.2 ใส่ช่องว่างก่อนวงเล็บปีกกาเปิด
// ไม่ดี function test(){ console.log('test'); } // ดี function test() { console.log('test'); } // ไม่ดี dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // ดี dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', });
-
19.3 ใส่ช่องว่างก่อนเปิดวงเล็บสำหรับ control statements (
if
,else
,while
, และอื่น ๆ) แต่สำหรับพารามิเตอร์ไม่ต้องใส่ช่องว่าง// ไม่ดี if(isJedi) { fight (); } // ดี if (isJedi) { fight(); } // ไม่ดี function fight () { console.log ('Swooosh!'); } // ดี function fight() { console.log('Swooosh!'); }
-
19.4 ใส่ช่องว่างเวลาประกาศตัวแปร
// ไม่ดี const x=y+5; // ดี const x = y + 5;
-
19.5 ลงท้ายไฟล์ด้วยการขึ้นบรรทัดใหม่เสมอ (แค่หนึ่งบรรทัดเท่านั้น)
// ไม่ดี (function(global) { // ...stuff... })(this);
// ไม่ดี (function(global) { // ...stuff... })(this);↵ ↵
// ดี (function(global) { // ...stuff... })(this);↵
-
19.5 ใส่ย่อหน้าเวลาเรียกใช้เมท็อตแบบต่อเนื่อง (Method chaining) ให้วางจุด
.
ไว้ด้านหน้าเสมอ เพื่อบอกว่าเป็นการเรียกเมท็อต// ไม่ดี $('#items').find('.selected').highlight().end().find('.open').updateCount(); // ไม่ดี $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // ดี $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // ไม่ดี const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') .call(tron.led); // ดี 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);
-
19.6 ใส่บรรทัดว่างหลังจากจบบล็อก และก่อนที่จะขึ้น statement ใหม่
// ไม่ดี if (foo) { return bar; } return baz; // ดี if (foo) { return bar; } return baz; // ไม่ดี const obj = { foo() { }, bar() { }, }; return obj; // ดี const obj = { foo() { }, bar() { }, }; return obj;
Commas
-
20.1 อย่าวางจุลภาค
,
ไว้ด้านหน้า// ไม่ดี const story = [ once , upon , aTime ]; // ดี const story = [ once, upon, aTime, ]; // ไม่ดี const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // ดี const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', };
-
20.2 ควรใส่จุลภาค
,
ต่อท้ายพรอพเพอร์ตี้ตัวสุดท้ายเพราะว่าเวลาดูใน
git diff
จะเป็นการเพิ่มบรรทัดอย่างเดียว โดยไม่มีการลบบรรทัดก่อนหน้า นอกจากนั้นTranspilers
เช่น Babel จะลบตัวจุลภาคนี้ออกเองเวลาคอมไพล์ ทำให้ไม่ต้องกังวลเกี่ยวกับ ปัญหาจุลภาคที่เกินมา ในบราวเซอร์เวอร์ชันเก่า// ไม่ดี - git diff เมื่อไม่มีจุลภาคต่อท้าย const hero = { firstName: 'Florence', - lastName: 'Nightingale' // ลบตัวสุดท้ายออก + lastName: 'Nightingale', + inventorOf: ['coxcomb graph', 'modern nursing'] } // ดี - git diff เมื่อมีจุลภาคต่อท้าย const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], // เพิ่มบรรทัดอย่างเดียว } // ไม่ดี const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // ดี const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ];
Semicolons
-
21.1 ควรใส่
;
เมื่อจบ statement// ไม่ดี (function() { const name = 'Skywalker' return name })() // ดี (() => { const name = 'Skywalker'; return name; })(); // ดี (เป็นการป้องกันไม่ให้ฟังก์ชันถูกตีความเป็น argument เมื่อทำการต่อไฟล์สองไฟล์ที่ใช้ IIFEs) ;(() => { const name = 'Skywalker'; return name; })();
Type Casting & Coercion
-
22.1 ทำการแปลงค่าไว้ด้านหน้าสุดเสมอ เพราะเวลาอ่านจะทราบได้ทันที่ว่าค่าที่จะได้ จะเป็นชนิดใด
-
22.2 สตริง:
// => this.reviewScore = 9; // ไม่ดี const totalScore = this.reviewScore + ''; // ดี const totalScore = String(this.reviewScore);
-
22.3 เวลาใช้
parseInt
ในการแปลงค่าให้เป็นตัวเลข ควรจะใส่เลขฐานที่ต้องการแปลงด้วย เพราะถ้าไม่ใส่อาจจะมีข้อผิดพลาดได้ถ้าค่าที่แปลงเป็นสตริงที่ไม่ได้ประกอบไปด้วยตัวเลขทั้งหมดconst inputValue = '4'; // ไม่ดี const val = new Number(inputValue); // ไม่ดี const val = +inputValue; // ไม่ดี const val = inputValue >> 0; // ไม่ดี const val = parseInt(inputValue); // ดี const val = Number(inputValue); // ดี const val = parseInt(inputValue, 10);
-
22.4 ในบางกรณีที่ต้องการให้ได้ประสิทธิภาพสูงสุดด้วยการใช้ Bitshift แทนการแปลงค่าโดยใช้
parseInt
สามารถอ่านเพิ่มเติมได้ที่ performance reasons นอกจากนั้นควรใส่คอมเม้นต์ต่าง ๆ อธิบายเหตุผลไว้ด้วย// ดี /** * ถ้า parseInt ทำให้โค้ดช้า ให้ใช้ * Bitshifting เพื่อแปลงค่าเป็นตัวเลขแทน * ซึ่งทำให้โค้ดสามารถทำงานได้เร็วขึ้นอย่างมาก */ const val = inputValue >> 0;
-
22.5 ควรระวังการใช้งาน bitshift เพราะตัวเลขปกติจะเป็น 64-bit values, แต่ Bitshift จะคืนค่าเป็น 32-bit เสมอ (ที่มา) Bitshift อาจทำให้ค่าผิดแปลกไปถ้าค่าของตัวเลขใหญ่กว่า 32 bits. ดูการพูดคุยในเรื่องนี้ ตัวเลขที่มากที่สุดของ 32-bit Int คือ 2,147,483,647:
2147483647 >> 0 //=> 2147483647 2147483648 >> 0 //=> -2147483648 เกินค่ามากที่สุดของ 32-bit Int จึงทำให้เกิดข้อผิดพลาด 2147483649 >> 0 //=> -2147483647 เกินค่ามากที่สุดของ 32-bit Int จึงทำให้เกิดข้อผิดพลาด
-
22.6 Booleans:
const age = 0; // ไม่ดี const hasAge = new Boolean(age); // ดี const hasAge = Boolean(age); // ดี const hasAge = !!age;
Naming Conventions
-
23.1 ควรจะตั้งชื่อให้สื่อความหมาย
// ไม่ดี function q() { // ...stuff... } // ดี function query() { // ..stuff.. }
-
23.2 ใช้ camelCase (ขึ้นต้นด้วยตัวเล็กและคำต่อไปขึ้นต้นด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อออบเจ็กต์, ฟังก์ชัน, และ instance
// ไม่ดี const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // ดี const thisIsMyObject = {}; function thisIsMyFunction() {}
-
23.3 ใช้ PascalCase (ขึ้นต้นทุกคำด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อ constructor หรือ class
// ไม่ดี function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // ดี class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', });
-
23.4 ขึ้นต้นด้วยขีดล่าง (
_
) เมื่อต้องการตั้งชื่อพรอพเพอร์ตี้ที่เป็น Private// ไม่ดี this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // ดี this._firstName = 'Panda';
-
23.5 อย่าบันทึกค่า
this
ไว้ใช้ ให้ใช้ Arrow functions หรือ Function#bind.// ไม่ดี function foo() { const self = this; return function() { console.log(self); }; } // ไม่ดี function foo() { const that = this; return function() { console.log(that); }; } // ดี function foo() { return () => { console.log(this); }; }
-
23.6 ถ้าในไฟล์มีแค่หนึ่งคลาส ให้ตั้งชื่อไฟล์ให้เป็นชื่อเดียวกับชื่อคลาส
// file contents class CheckBox { // ... } export default CheckBox; // in some other file // ไม่ดี import CheckBox from './checkBox'; // ไม่ดี import CheckBox from './check_box'; // ดี import CheckBox from './CheckBox';
-
23.7 ใช้ camelCase เมื่อต้องการเอ็กพอร์ตฟังก์ชัน ชื่อไฟล์ควรเป็นชื่อเดียวกับชื่อฟังก์ชัน
function makeStyleGuide() { } export default makeStyleGuide;
-
23.8 ใช้ PascalCase เมื่อเอ็กพอร์ต Singleton / Function library / หรือ Bare object
const AirbnbStyleGuide = { es6: { } }; export default AirbnbStyleGuide;
Accessors
-
24.1 Accessor functions (ฟังก์ชันที่ใช้ในการเข้าถึงพรอพเพอร์ตี้) ไม่จำเป็นต้องมีก็ได้
-
24.2 แต่ถ้ามีควรจะตั้งชื่อในรูปแบบ getVal() และ setVal('hello')
// ไม่ดี dragon.age(); // ดี dragon.getAge(); // ไม่ดี dragon.age(25); // ดี dragon.setAge(25);
-
24.3 ถ้าพรอพเพอร์ตี้เป็นค่าบูลีน (boolean) ให้ใช้ isVal() หรือ hasVal().
// ไม่ดี if (!dragon.age()) { return false; } // ดี 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 เมื่อทำการเชื่อมต่ออีเว้นต์ ให้ส่งค่าที่เป็นออบเจ็กต์ไป ซึ่งจะดีกว่าการส่งค่าแบบธรรมดา เพราะจะช่วยให้ตัวเมท็อตที่รับค่าสามารถแก้ไขค่าและเพิ่มพรอพเพอร์ตี้ได้ง่ายขึ้น
// ไม่ดี $(this).trigger('listingUpdated', listing.id); ... $(this).on('listingUpdated', function(e, listingId) { // do something with listingId });
// ดี $(this).trigger('listingUpdated', { listingId : listing.id }); ... $(this).on('listingUpdated', function(e, data) { // do something with data.listingId });
jQuery
-
26.1 ใส่สัญลักษณ์
$
ไว้ด้านหน้าตัวแปรทุกตัวที่เป็น jQuery Object// ไม่ดี const sidebar = $('.sidebar'); // ดี const $sidebar = $('.sidebar');
-
26.2 ในกรณีที่ต้องค้นหา DOM โดยใช้ jQuery ควรจะเก็บแคช (Cache) ไว้เสมอ เพราะการค้นหา DOM ซ้ำ ๆ หลายรอบจะส่งผลต่อประสิทธิภาพของโค้ด
// ไม่ดี function setSidebar() { $('.sidebar').hide(); // ...stuff... $('.sidebar').css({ 'background-color': 'pink' }); } // ดี function setSidebar() { const $sidebar = $('.sidebar'); // เก็บแคชในการค้นหาไว้ในตัวแปร เพื่อนำไปใช้ต่อไป $sidebar.hide(); // ...stuff... $sidebar.css({ 'background-color': 'pink' }); }
-
26.3 เวลาค้นหา DOM ให้ใช้รูปแบบของ Cascading เช่น
$('.sidebar ul')
หรือ parent > child$('.sidebar > ul')
- jsPerf -
26.4 ใช้
find
ร่วมกับ jQuery object (ที่เราแคชไว้ก่อนหน้านี้)// ไม่ดี $('ul', '.sidebar').hide(); // ไม่ดี $('.sidebar').find('ul').hide(); // ดี $('.sidebar ul').hide(); // ดี $('.sidebar > ul').hide(); // ดี $sidebar.find('ul').hide();
ECMAScript 5 Compatibility
- 27.1 อ่านเพิ่มเติมได้ที่ Kangax's ES5 compatibility table
ECMAScript 6+ (ES 2015+) Styles
- 28.1 อ่านเพิ่มเติมเกี่ยวกับฟีเจอร์ต่าง ๆ ของ ES6:
- Arrow Functions
- Classes
- Object Shorthand
- Object Concise
- Object Computed Properties
- Template Strings
- Destructuring
- Default Parameters
- Rest
- Array Spreads
- Let and Const
- Iterators and Generators
- Modules
Standard Library
ตัว Standard Library มี Utilities หลายตัวที่อาจจะทำงานผิดพลาดหรือไม่ถูกต้องแต่ยังมีให้ใช้อยู่ด้วยเหตุผลทางด้าน Legacy ดังนั้นให้เลือกใช้ตัวที่เหมาะสม
-
29.1 ใช้
Number.isNaN
แทนที่จะใช้isNaN
ที่เป็นฟังก์ชัน Globalเพราะว่าฟังก์ชั่น
isNaN
ที่เป็น Global จะแปลงค่าที่ไม่ได้เป็น numbers ให้กลายเป็น numbers และ return ค่า true// ไม่ดี isNaN('1.2'); // false isNaN('1.2.3'); // true // ดี Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true
-
29.2 ใช้
Number.isFinite
แทนที่จะใช้isFinite
ที่เป็นฟังก์ชั่น Globalเพราะว่าฟังก์ชั่น
isFinite
ที่เป็น Global จะแปลงค่าที่ไม่ได้เป็น numbers ให้กลายเป็น numbers และ return ค่า true// ไม่ดี isFinite('2e3'); // true // ดี Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true
Testing
-
30.1 Yup.
function() { return true; }
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
- Annotated ECMAScript 5.1
เครื่องมือต่าง ๆ
- 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
พอดคาสต์ (Podcasts)
In the Wild
รายชื่อองกรค์ที่ทำตามคู่มือแนะนำการเขียนจาวาสคริปต์นี้ ถ้าองค์กรของคุณทำตามคู่มือนี้เช่นกัน กรุณาส่ง Pull request หรือเปิด Issue แล้วเราจะเพิ่มคุณเข้าไปในรายชื่อต่อไปนี้
- Aan Zee: AanZee/javascript
- Adult Swim: adult-swim/javascript
- Airbnb: airbnb/javascript
- Apartmint: apartmint/javascript
- Avalara: avalara/javascript
- Billabong: billabong/javascript
- Blendle: blendle/javascript
- Compass Learning: compasslearning/javascript-style-guide
- DailyMotion: dailymotion/javascript
- Digitpaint digitpaint/javascript
- Evernote: evernote/javascript-style-guide
- 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
- InfoJobs: InfoJobs/JavaScript-Style-Guide
- Intent Media: intentmedia/javascript
- Jam3: Jam3/Javascript-Code-Conventions
- 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
- Peerby: Peerby/javascript
- Razorfish: razorfish/javascript-style-guide
- reddit: reddit/styleguide/javascript
- REI: reidev/js-style-guide
- Ripple: ripple/javascript-style-guide
- SeekingAlpha: seekingalpha/javascript-style-guide
- Shutterfly: shutterfly/javascript
- StudentSphere: studentsphere/javascript
- Target: target/javascript
- TheLadders: TheLadders/javascript
- T4R Technology: T4R-Technology/javascript
- VoxFeed: VoxFeed/javascript-style-guide
- 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/javascript-style-guide
- Korean: tipjs/javascript-style-guide
- Russian: leonidlebedev/javascript-airbnb
- Spanish: paolocarrasco/javascript-style-guide
- Thai: lvarayut/javascript-style-guide
- Ukrainian: ivanzusko/javascript
- Vietnam: hngiang/javascript-style-guide
คู่มือแนะนำการเขียนจาวาสคริปต์
พูดคุยกับพวกเราเกี่ยวกับจาวาสคริปต์
- Find us on gitter.
ผู้ที่มีส่วนช่วย
License
(The MIT License)
Copyright (c) 2014 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