![Gitter](https://badges.gitter.im/Join Chat.svg)
คู่มือแนะนำการเขียนจาวาสคริปต์ที่เข้าท่ามากที่สุด โดย Airbnb
คู่มือนี้ผมแปลโดยใส่คำอธิบายและตัวอย่างเพิ่มเติม (ไม่แปลตรงตัว) เพื่อให้ผู้อ่านสามารถเข้าใจเนื้อหาต่าง ๆ ได้ดียิ่งขึ้น ในกรณีที่เจอข้อผิดพลาดใด ๆ กรุณา Fork และ PR ถ้ามีคำถามสามารถเปิด Issue ได้เลยครับ หวังว่าคู่มือนี้จะมีประโยชน์ต่อผู้อ่านไม่มากก็น้อย 🙏
- Types
- Objects
- Arrays
- Strings
- Functions
- Properties
- Variables
- Hoisting
- Comparison Operators & Equality
- Blocks
- Comments
- Whitespace
- Commas
- Semicolons
- Type Casting & Coercion
- Naming Conventions
- Accessors
- Constructors
- Events
- Modules
- jQuery
- ECMAScript 5 Compatibility
- Testing
- Performance
- Resources
- In the Wild
- Translation
- The JavaScript Style Guide Guide
- Chat With Us About Javascript
- Contributors
- License
-
Primitives: เมื่อใช้งานตัวแปรพื้นฐาน (ตัวแปรที่อ้างอิงด้วยค่า) สามารถเข้าใช้งานได้โดยอ้างอิงค่าของตัวแปร
string
number
boolean
null
undefined
var foo = 1; var bar = foo; // bar เก็บค่า 1 โดยจะไม่เกี่ยวข้องกับ foo อีกต่อไป bar = 9; console.log(foo, bar); // => 1, 9
-
Complex: เมื่อใช้งานตัวแปรที่มีความซับซ้อน (ตัวแปรที่อ้างอิงไปยังค่าที่อยู่ของตัวแปรอื่น) สามารถเข้าใช้งานได้โดยอ้างอิงค่าที่อยู่ของตัวแปรนั้น ๆ
object
array
function
var foo = [1, 2]; var bar = foo; // bar ไม่ได้เก็บค่า [1,2] แต่ bar ชี้ไปยังที่อยู่ของอาร์เรย์ ซึ่งเป็นที่ ๆ เดียวกันกับที่ foo ชี้ไป bar[0] = 9; // เมื่อเปลี่ยนแปลง bar, foo ก็จะถูกเปลี่ยนแปลงด้วย console.log(foo[0], bar[0]); // => 9, 9
-
ควรใช้ปีกกา
{}
ในการประกาศออบเจ็กต์// ไม่ดี var item = new Object(); // ดี var item = {};
-
อย่าใช้คำสงวน เป็นคีย์ เพราะมันจะใช้ไม่ได้ใน IE8. อ่านเพิ่มเติม.
// ไม่ดี var superman = { default: { clark: 'kent' }, // default เป็นคำสงวน private: true }; // ดี var superman = { defaults: { clark: 'kent' }, hidden: true };
-
ใช้คำที่มีความหมายเหมือนกันแทนคำสงวน
// ไม่ดี var superman = { class: 'alien' // class เป็นคำสงวน }; // ไม่ดี var superman = { klass: 'alien' // แปลงคำไม่ใช่สิ่งดี เพราะจะทำให้เดาความหมายได้ยาก }; // ดี var superman = { type: 'alien' };
-
ใช้วงเล็บก้ามปู
[]
ในการประกาศอาร์เรย์// ไม่ดี var items = new Array(); // ดี var items = [];
-
ใช้ฟังก์ชัน Array#push ในการใส่ค่าเข้าไปในอาร์เรย์แทนการใส่ค่าโดยตรง
var someStack = []; // ไม่ดี someStack[someStack.length] = 'abracadabra'; // ดี someStack.push('abracadabra');
-
ใช้ฟังก์ชัน Array#slice ในการทำสำเนาอาร์เรย์ - jsPerf
var len = items.length; var itemsCopy = []; var i; // ไม่ดี for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // ดี itemsCopy = items.slice();
-
ใช้ฟังก์ชัน Array#slice ใช้การแปลงอาร์เรย์ให้เป็นออบเจ็กต์ (ต้องเป็นอาร์เรย์ที่จัดวางเหมือนออบเจ็กต์เท่านั้นถึงจะทำการแปลงได้)
function trigger() { var args = Array.prototype.slice.call(arguments); ... }
-
ใช้เขี้ยวเดียว (Single quotes)
''
สำหรับสตริง// ไม่ดี var name = "Bob Parr"; // ดี var name = 'Bob Parr'; // ไม่ดี var fullName = "Bob " + this.lastName; // ดี var fullName = 'Bob ' + this.lastName;
-
สตริงที่ยาวกว่า 80 ตัวอักษร ควรจะแยกเขียนในหลายบรรทัด และค่อยทำการเชื่อมต่อกัน
-
หมายเหตุ: ไม่ควรใช้สตริงที่ยาวมากเกินไป เพราะจะมีผลต่อประสิทธิภาพของแอพพลิเคชั่น - jsPerf & Discussion.
// ไม่ดี var 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.'; // ไม่ดี var 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.'; // ดี var 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.';
-
เมื่อต้องการสร้างสตริง ควรใช้ฟังก์ชัน Array#join แทนการเชื่อมต่อโดยใช้เครื่องหมายบวก
+
โดยเฉพาะสำหรับ IE - jsPerf.var items; var messages; var length; var i; messages = [{ state: 'success', message: 'This one worked.' }, { state: 'success', message: 'This one worked as well.' }, { state: 'error', message: 'This one did not work.' }]; length = messages.length; // ไม่ดี function inbox(messages) { items = '<ul>'; for (i = 0; i < length; i++) { items += '<li>' + messages[i].message + '</li>'; } return items + '</ul>'; } // ดี function inbox(messages) { items = []; for (i = 0; i < length; i++) { items[i] = '<li>' + messages[i].message + '</li>'; } return '<ul>' + items.join('') + '</ul>'; }
-
Function expressions - การประกาศฟังก์ชันและใช้ตัวแปรในการอ้างอิงฟังก์ชันดังกล่าว ดังตัวอย่างต่อไปนี้
// anonymous function expression var anonymous = function() { return true; }; // named function expression var named = function named() { return true; }; // immediately-invoked function expression (IIFE) (function() { console.log('Welcome to the Internet. Please follow me.'); })();
-
อย่าประกาศฟังก์ชันประเภท Function Declaration ไว้ภายใน if, else, while, และอื่น ๆ เพราะบราวเซอร์จะตีความหมายผิด ถ้าจำเป็นต้องประกาศ ให้ประกาศในรูปแบบของ Function Expression
-
หมายเหตุ: ECMA-262 บอกไว้ว่าใน if, else, while, และอื่น ๆ จะต้องประกอบไปด้วย statements เท่านั้น ซึ่งการประกาศฟังก์ชันประเภท Function Declaration ไม่ใช่ statement อ่านเพิ่มเติมเกี่ยวกับ ECMA-262.
// ไม่ดี if (currentUser) { function test() { console.log('Nope.'); } } // ดี var test; if (currentUser) { test = function test() { console.log('Yup.'); }; }
-
อย่าตั้งชื่อพารามิเตอร์ว่า
arguments
เพราะมันจะไปทับออบเจ็กต์arguments
ที่จาวาสคริปต์มีให้ในทุก ๆ ฟังก์ชัน// ไม่ดี function nope(name, options, arguments) { // ...stuff... } // ดี function yup(name, options, args) { // ...stuff... }
-
ใช้จุด
.
ในการเข้าถึงพรอพเพอร์ตี้ (properties).var luke = { jedi: true, age: 28 }; // ไม่ดี var isJedi = luke['jedi']; // ดี var isJedi = luke.jedi;
-
ใช้วงเล็บก้ามปู
[]
ในการเข้าถึงพรอพเพอร์ตี้ โดยการใช้ตัวแปรvar luke = { jedi: true, age: 28 }; function getProp(prop) { return luke[prop]; // เข้าถึงพรอพเพอร์ตี้ี้ของ luke โดยใช้ตัวแปร prop } var isJedi = getProp('jedi');
-
ใช้
var
ในการประกาศตัวแปรเสมอ ถ้าไม่ใช้จะมีผลให้ตัวแปรที่ประกาศขึ้นใหม่เป็นตัวแปรแบบglobal
ซึ่งอาจมีผลต่อไฟล์หรือโมดูลอื่น ๆ// ไม่ดี superPower = new SuperPower(); // ดี var superPower = new SuperPower();
-
ใช้หนึ่ง
var
ต่อหนึ่งตัวแปร เพราะดูง่ายกว่า และป้องกันข้อผิดพลาดได้ อย่างเช่น บางครั้งใส่สลับกันระหว่าง;
และ,
ซึ่งทำให้ได้ผลลัพธ์ที่ผิด// ไม่ดี var items = getItems(), goSportsTeam = true, dragonball = 'z'; // ไม่ดี var items = getItems(), goSportsTeam = true; // ตัวอย่างข้อผิดพลาดที่ใส่ ; แทนที่จะเป็น , dragonball = 'z'; // ดี var items = getItems(); var goSportsTeam = true; var dragonball = 'z';
-
ตัวแปรที่ยังไม่มีค่า ให้ประกาศไว้ข้างหลังสุดของการประกาศตัวแปรทั้งหมด ซึ่งจะมีประโยชน์เมื่อเรามาใส่ค่าให้ตัวแปรเหล่านั้นในภายหลัง โดยที่ตัวแปรเหล่านั้นจะต้องใช้ค่าของตัวแปรอื่น ๆ ที่เราประกาศไว้ก่อนหน้า
// ไม่ดี var i, len, dragonball, items = getItems(), goSportsTeam = true; // ไม่ดี var i; var items = getItems(); var dragonball; var goSportsTeam = true; var len; // ดี var items = getItems(); var goSportsTeam = true; var dragonball; var length; var i;
-
ประกาศตัวแปรทั้งหมดไว้ข้างบนสุดของฟังก์ชัน ซึ่งจะทำให้เราไม่สับสนและยังป้องกันการ hoisting ของจาวาสคริปต์ได้อีกด้วย
// ไม่ดี function() { test(); console.log('doing stuff..'); //..other stuff.. var name = getName(); if (name === 'test') { return false; } return name; } // ดี function() { var name = getName(); test(); console.log('doing stuff..'); //..other stuff.. if (name === 'test') { return false; } return name; } // ไม่ดี function() { var name = getName(); if (!arguments.length) { return false; } return true; } // ดี function() { if (!arguments.length) { return false; } var name = getName(); return true; }
-
เวลาคอมไพล์จาวาสคริปต์จะอ่านตัวแปรที่ประกาศไว้ก่อนหน้าสิ่งอื่น ๆ ในสโคป แต่ค่าที่ใส่ให้ตัวแปรจะยังไม่ถูกอ่าน
// สมมุติว่าเราไม่ได้ประกาศตัวแปร notDefined function example() { console.log(notDefined); // => throws a ReferenceError } // ประกาศตัวแปรหลังจากใช้งาน ในจาวาสคริปต์นั้นทำได้ (ไม่มี error) // เพราะว่าตัวแปรจะถูกคอมไพล์และดึงขึ้นมาไว้ข้างบนสโคป // แต่ค่าของตัวแปรไม่ได้ถูกดึงขึ้นมาด้วย จึงทำให้ค่าของตัวแปรนั้นเป็น undefined function example() { console.log(declaredButNotAssigned); // => undefined (ไม่ error แต่ยังไม่มีค่า) var declaredButNotAssigned = true; } // ตัวอย่างเมื่อคอมไพล์เลอร์ทำงานในตัวอย่างข้างต้น // คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นมาไว้ด้านบนของสโคป function example() { var declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; }
-
Anonymous function expressions - การประกาศฟังก์ชันโดยไม่ใส่ชื่อฟังก์ชัน คอมไพล์เลอร์จะอ่านตัวแปรและดึงขึ้นไปด้านบนของสโคป แต่จะยังไม่อ่านฟังก์ชัน
function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function() { console.log('anonymous function expression'); }; }
-
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'); } }
-
Function declarations - การประกาศฟังก์ชันโดยไม่ได้ใส่ค่าฟังก์ชันให้ตัวแปร คอมไพล์เลอร์จะอ่านทั้งชื่อและฟังก์ชัน
function example() { superPower(); // => Flying function superPower() { // ถึงจะประกาศทีหลังใช้แต่ คอมไพล์เลอร์จะอ่านทั้งชื่อและตัวฟังก์ชัน console.log('Flying'); } }
-
อ่านเพิ่มเติมได้ที่ JavaScript Scoping & Hoisting โดย Ben Cherry.
-
ใช้
===
และ!==
แทน==
และ!=
-
การเปรียบเทียบโอเปอเรเตอร์ จาวาสคริปต์จะแปลงค่าเหล่านั้นเป็น boolean โดยใช้ฟังก์ชัน
ToBoolean
และใช้กฏต่าง ๆ ดังต่อไปนี้:- Objects ได้ผลลัพธ์เป็น true
- Undefined ได้ผลลัพธ์เป็น false
- Null ได้ผลลัพธ์เป็น false
- Booleans ได้ผลลัพธ์ ขึ้นอยู่กับค่าของ boolean
- Numbers ได้ผลลัพธ์เป็น false ถ้า +0, -0, or NaN, นอกนั้นได้ true
- Strings ได้ผลลัพธ์เป็น false ถ้า
''
, นอกนั้นได้ true
if ([0]) { // true // เพราะ array คือออบเจ็กต์ }
-
ใช้ Shortcuts.
// ไม่ดี if (name !== '') { // ...stuff... } // ดี if (name) { // ...stuff... } // ไม่ดี if (collection.length > 0) { // ...stuff... } // ดี if (collection.length) { // ...stuff... }
-
อ่านเพิ่มเติมได้ที่ Truth Equality and JavaScript โดย Angus Croll.
-
ใช้วงเล็บปีกกา
{}
ในกรณีที่ประกาศบล็อกมากกว่าหนึ่งบรรทัด// ไม่ดี if (test) return false; // ดี if (test) return false; // วางไว้บรรทัดเดียวกันจะอ่านง่ายกว่า // ดี if (test) { return false; } // ไม่ดี function() { return false; } // ดี function() { // ถ้ามีวงเล็บปีกกาให้วางไว้คนละบรรทัดจะอ่านง่ายกว่า return false; }
-
ถ้าประกาศโดยมีทั้ง
if
และelse
ให้ใส่else
ไว้บรรทัดเดียวกับวงเล็บปีกกาปิดของif
// ไม่ดี if (test) { thing1(); thing2(); } else { thing3(); } // ดี if (test) { thing1(); thing2(); } else { thing3(); }
-
ใช้
/** ... */
สำหรับคอมเม้นต์ที่มากกว่าหนึ่งบรรทัด และควรจะบอกประเภทและค่าของพารามิเตอร์พร้อมทั้งค่าที่จะรีเทิร์น// ไม่ดี // 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; }
-
ใช้
//
สำหรับคอมเม้นต์บรรทัดเดียว โดยใส่ไว้บรรทัดบนของสิ่งที่ต้องการคอมเม้นต์ และเพิ่มบรรทัดว่างไว้ด้านบนคอมเม้นต์ด้วย// ไม่ดี var active = true; // is current tab // ดี // is current tab var active = true; // ไม่ดี function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; } // ดี function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; }
-
ใส่
FIXME
หรือTODO
ไว้ด้านหน้าคอมเม้นต์ ซึ่งจะช่วยให้ผู้พัฒนาระบบท่านอื่น ๆ ทราบได้ว่าสิ่งเหล่านั้นอาจจะต้องแก้ไข หรือยังไม่ได้ทำ (IDE บางตัวสามารถค้นหาคอมเม้นต์เหล่านี้อัตโนมัติ และบอกถึงสิ่งที่ควรจะแก้ไขหรือทำเพิ่ม) -
ใช้
// FIXME:
เพื่อบอกปัญหาfunction Calculator() { // FIXME: shouldn't use a global here total = 0; return this; }
-
ใช้
// TODO:
เพื่อบอกแนวทางในกาแก้ไขปัญหา (แต่ยังไม่ได้ทำ)function Calculator() { // TODO: total ควรจะใส่เป็นพารามิเตอร์ โดยมีค่าหรือไม่มีค่าก็ได้ (ถ้าไม่มีค่าใส่มา ให้ค่าเริ่มต้นเป็น 0) this.total = 0; return this; }
-
ควรตั้งค่าหนึ่งแท็บเท่ากับสองช่องว่าง (สามารถตั้งค่าใน Editor หรือ IDE ได้)
// ไม่ดี function() { ∙∙∙∙var name; } // ไม่ดี function() { ∙var name; } // ดี function() { ∙∙var name; }
-
ใส่ช่องว่างก่อนวงเล็บปีกกาเปิด
// ไม่ดี 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' });
-
ใส่ช่องว่างก่อนเปิดวงเล็บสำหรับ control statements (
if
,else
,while
, และอื่น ๆ) แต่สำหรับพารามิเตอร์ไม่ต้องใส่ช่องว่าง// ไม่ดี if(isJedi) { fight (); } // ดี if (isJedi) { fight(); } // ไม่ดี function fight () { console.log ('Swooosh!'); } // ดี function fight() { console.log('Swooosh!'); }
-
ใส่ช่องว่างเวลาประกาศตัวแปร
// ไม่ดี var x=y+5; // ดี var x = y + 5;
-
ลงท้ายไฟล์ด้วยการขึ้นบรรทัดใหม่เสมอ (แค่หนึ่งบรรทัดเท่านั้น)
// ไม่ดี (function(global) { // ...stuff... })(this);
// ไม่ดี (function(global) { // ...stuff... })(this);↵ ↵
// ดี (function(global) { // ...stuff... })(this);↵
-
ใส่ย่อหน้าเวลาเรียกใช้เมท็อตแบบต่อเนื่อง (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(); // ไม่ดี var 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); // ดี var 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);
-
ใส่บรรทัดว่างหลังจากบล็อก และก่อนที่จะขึ้น statement ใหม่
// ไม่ดี if (foo) { return bar; } return baz; // ดี if (foo) { return bar; } return baz; // ไม่ดี var obj = { foo: function() { }, bar: function() { } }; return obj; // ดี var obj = { foo: function() { }, bar: function() { } }; return obj;
-
อย่าวางจุลภาค
,
ไว้ด้านหน้า// ไม่ดี var story = [ once , upon , aTime ]; // ดี var story = [ once, upon, aTime ]; // ไม่ดี var hero = { firstName: 'Bob' , lastName: 'Parr' , heroName: 'Mr. Incredible' , superPower: 'strength' }; // ดี var hero = { firstName: 'Bob', lastName: 'Parr', heroName: 'Mr. Incredible', superPower: 'strength' };
-
อย่าใส่จุลภาค ถ้าไม่มีค่าอื่น ๆ ต่อท้ายแล้ว เพราะอาจจะทำให้เกิดปัญหาใน IE6/7 และ IE9 นอกจากนั้นใน ES3 จะเพิ่มความยาวของอาร์เรย์ถ้าเจอจุลภาคอยู่ด้านหลังสุดซึ่งเป็นสิ่งที่ผิด อ่านเพิ่มเติมที่ (ES5):
Edition 5 ได้แก้ไขข้อผิดพลาดนี้ โดยไม่เพิ่มความยาวของอาร์เรย์จากจุลภาคที่อยู่หลังสุด ซึ่งเป็นข้อผิดพลาดใน ES3
// ไม่ดี var hero = { firstName: 'Kevin', lastName: 'Flynn', }; var heroes = [ 'Batman', 'Superman', ]; // ดี var hero = { firstName: 'Kevin', lastName: 'Flynn' }; var heroes = [ 'Batman', 'Superman' ];
-
ควรใส่
;
เมื่อจบ statement// ไม่ดี (function() { var name = 'Skywalker' return name })() // ดี (function() { var name = 'Skywalker'; return name; })(); // ดี (เป็นการป้องกันไม่ให้ฟังก์ชันถูกตีความเป็น argument เมื่อทำการต่อไฟล์สองไฟล์ที่ใช้ IIFEs) ;(function() { var name = 'Skywalker'; return name; })();
-
เวลาแปลงค่าสตริงให้ใส่
''
ไว้ด้านหน้า เพราะเวลาอ่านจะทราบได้ทันที่ว่าค่าที่จะได้ จะเป็นชนิดสตริง -
Strings:
// => this.reviewScore = 9; // ไม่ดี var totalScore = this.reviewScore + ''; // ดี var totalScore = '' + this.reviewScore; // ไม่ดี var totalScore = '' + this.reviewScore + ' total score'; // ดี var totalScore = this.reviewScore + ' total score';
-
เวลาใช้
parseInt
ในการแปลงค่าให้เป็นตัวเลข ควรจะใส่เลขฐานที่ต้องการแปลงด้วย เพราะถ้าไม่ใส่อาจจะมีข้อผิดพลาดได้ถ้าค่าที่แปลงเป็นสตริงที่ไม่ได้ประกอบไปด้วยตัวเลขทั้งหมดvar inputValue = '4'; // ไม่ดี var val = new Number(inputValue); // ไม่ดี var val = +inputValue; // ไม่ดี var val = inputValue >> 0; // ไม่ดี var val = parseInt(inputValue); // ดี var val = Number(inputValue); // ดี var val = parseInt(inputValue, 10);
-
ในบางกรณีที่ต้องการให้ได้ประสิทธิภาพสูงสุดด้วยการใช้ Bitshift แทนการแปลงค่าโดยใช้
parseInt
สามารถอ่านเพิ่มเติมได้ที่ performance reasons, มีคอมเม้นต์ต่าง ๆ ที่อธิบายถึงเรื่องประสิทธิภาพ// ดี /** * ถ้า parseInt ทำให้โค้ดช้า ให้ใช้ * Bitshifting เพื่อแปลงค่าเป็นตัวเลขแทน * ซึ่งทำให้โค้ดสามารถทำงานได้เร็วขึ้นอย่างมาก */ var val = inputValue >> 0;
-
หมายเหตุ: ควรระวังการใช้งาน 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 จึงทำให้เกิดข้อผิดพลาด
-
Booleans:
var age = 0; // ไม่ดี var hasAge = new Boolean(age); // ดี var hasAge = Boolean(age); // ดี var hasAge = !!age;
-
ควรจะตั้งชื่อให้สื่อความหมาย
// ไม่ดี function q() { // ...stuff... } // ดี function query() { // ..stuff.. }
-
ใช้ camelCase (ขึ้นต้นด้วยตัวเล็กและคำต่อไปขึ้นต้นด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อออบเจ็กต์, ฟังก์ชัน, และ instance
// ไม่ดี var OBJEcttsssss = {}; var this_is_my_object = {}; function c() {} var u = new user({ name: 'Bob Parr' }); // ดี var thisIsMyObject = {}; function thisIsMyFunction() {} var user = new User({ name: 'Bob Parr' });
-
ใช้ PascalCase (ขึ้นต้นทุกคำด้วยตัวใหญ่) เมื่อต้องการตั้งชื่อ constructor หรือ class
// ไม่ดี function user(options) { this.name = options.name; } var ไม่ดี = new user({ name: 'nope' }); // ดี function User(options) { this.name = options.name; } var ดี = new User({ name: 'yup' });
-
ขึ้นต้นด้วยขีดล่าง (
_
) เมื่อต้องการตั้งชื่อพรอพเพอร์ตี้ที่เป็น Private// ไม่ดี this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // ดี this._firstName = 'Panda';
-
เมื่อต้องการบันทึกค่า
this
ไว้ใช้ ให้ใส่ไว้ในตัวแปรชื่อ_this
// ไม่ดี function() { var self = this; return function() { console.log(self); }; } // ไม่ดี function() { var that = this; return function() { console.log(that); }; } // ดี function() { var _this = this; return function() { console.log(_this); }; }
-
ตั้งชื่อฟังก์ชันเสมอ ซึ่งจะเป็นประโยชน์เวลาดู Stack traces เมื่อทำการ Debug
// ไม่ดี var log = function(msg) { console.log(msg); }; // ดี var log = function log(msg) { console.log(msg); };
-
หมายเหตุ สำหรับบราวเซอร์ที่ต่ำกว่า IE8 อาจจะมีข้อผิดพลาดถ้าตั้งชื่อให้กับ Function expression อ่านเพิ่มเติมได้ที่ http://kangax.github.io/nfe/
-
ถ้าในไฟล์มีแค่หนึ่งคลาส ให้ตั้งชื่อไฟล์ให้เป็นชื่อเดียวกับชื่อคลาส
// file contents class CheckBox { // ... } module.exports = CheckBox; // in some other file // ไม่ดี var CheckBox = require('./checkBox'); // ไม่ดี var CheckBox = require('./check_box'); // ดี var CheckBox = require('./CheckBox');
-
Accessor functions (ฟังก์ชันที่ใช้ในการเข้าถึงพรอพเพอร์ตี้) ไม่จำเป็นต้องมีก็ได้
-
แต่ถ้ามีควรจะตั้งชื่อในรูปแบบ getVal() และ setVal('hello')
// ไม่ดี dragon.age(); // ดี dragon.getAge(); // ไม่ดี dragon.age(25); // ดี dragon.setAge(25);
-
ถ้าพรอพเพอร์ตี้เป็นค่าบูลีน (boolean) ให้ใช้ isVal() หรือ hasVal().
// ไม่ดี if (!dragon.age()) { return false; } // ดี if (!dragon.hasAge()) { return false; }
-
ความจริงแล้วตั้งชื่อ get() และ set() ก็ไม่เสียหายอะไร แต่ต้องตั้งให้เหมือนกันในทุก ๆ ที่
function Jedi(options) { options || (options = {}); var lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } Jedi.prototype.set = function(key, val) { this[key] = val; }; Jedi.prototype.get = function(key) { return this[key]; };
-
ควรเพิ่มเมท็อตของออบเจ็คต์ ผ่านทาง Prototype ด้วยการใช้จุด
.
เพราะจะเป็นการเพิ่มพรอพเพอร์ตี้ ไม่ใช่การสร้างออบเจ็คต์ใหม่ ถ้าสร้างออบเจ็คต์ใหม่ จะไม่สามารถทำ Inheritance ได้อีกfunction Jedi() { console.log('new jedi'); } // ไม่ดี Jedi.prototype = { fight: function fight() { console.log('fighting'); }, block: function block() { console.log('blocking'); } }; // ดี Jedi.prototype.fight = function fight() { console.log('fighting'); }; Jedi.prototype.block = function block() { console.log('blocking'); };
-
เมท็อตควรคืนค่าเป็นออบเจ็ค
this
เพื่อช่วยให้สามารถทำ Method chaining.// ไม่ดี Jedi.prototype.jump = function() { this.jumping = true; return true; }; Jedi.prototype.setHeight = function(height) { this.height = height; }; var luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // ดี Jedi.prototype.jump = function() { this.jumping = true; return this; }; Jedi.prototype.setHeight = function(height) { this.height = height; return this; }; var luke = new Jedi(); luke.jump() .setHeight(20);
-
สามารถทำการ Overwrite เมท็อต toString() ได้แต่ควรจะตรวจสอบให้มั่นใจว่าจะไม่เกิดข้อผิดพลาดขึ้นได้ในอนาคต
function Jedi(options) { options || (options = {}); this.name = options.name || 'no name'; } Jedi.prototype.getName = function getName() { return this.name; }; Jedi.prototype.toString = function toString() { return 'Jedi - ' + this.getName(); };
-
เมื่อทำการเชื่อมต่ออีเว้นต์ ให้ส่งค่าที่เป็นออบเจ็คต์ไป ซึ่งจะดีกว่าการส่งค่าแบบธรรมดา เพราะจะช่วยให้ตัวเมท็อตที่รับค่าสามารถแก้ไขค่าและเพิ่มพรอพเพอร์ตี้ได้ง่ายขึ้น
// ไม่ดี $(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 });
-
โมดูล (Module) ควรเริ่มต้นไฟล์ด้วยเครื่องหมายอัศเจรีย์
!
เพื่อให้แน่ใจว่าถ้ามีโมดูลอื่นที่ลืมใส่ semicolon;
ในบรรทัดสุดท้าย และนำไฟล์มาต่อกับโมดูลนี้็จะไม่ทำให้เกิดข้อผิดพลาดขึ้น (ปกติถ้าใช้ uglify ในการต่อไฟล์ จะใส่ ! ให้อัตโนมัติ) คำอธิบาย -
ไฟล์ขึ้นจะตั้งชื่อแบบ camelCase และใส่ไว้ในโฟลเดอร์ชื่อเดียวกัน นอกจากนั้นฟังก์ชันที่นำออกมาจากไฟล์ควรจะเป็นชื่อเดียวกับชื่อไฟล์
-
เพิ่มเมท็อตชื่อ
noConflict()
เพื่อใช้ในการนำออกโมดูลเวอร์ชันก่อนหน้าที่จะทำการเปลี่ยนแปลง -
ใส่
'use strict';
ใช้ที่บรรทัดบนสุดของโมดูลเสมอ// fancyInput/fancyInput.js !function(global) { 'use strict'; var previousFancyInput = global.FancyInput; function FancyInput(options) { this.options = options || {}; } FancyInput.noConflict = function noConflict() { global.FancyInput = previousFancyInput; return FancyInput; }; global.FancyInput = FancyInput; }(this);
-
ใส่สัญลักษณ์
$
ไว้ด้านหน้าตัวแปรทุกตัวที่เป็น jQuery Object// ไม่ดี var sidebar = $('.sidebar'); // ดี var $sidebar = $('.sidebar');
-
ในกรณีที่ต้องค้นหา DOM โดยใช้ jQuery ควรจะเก็บแคช (Cache) ไว้เสมอ เพราะการค้นหา DOM ซ้ำ ๆ หลายรอบจะส่งผลต่อประสิทธิภาพของโค้ด
// ไม่ดี function setSidebar() { $('.sidebar').hide(); // ...stuff... $('.sidebar').css({ 'background-color': 'pink' }); } // ดี function setSidebar() { var $sidebar = $('.sidebar'); // เก็บแคชในการค้นหาไว้ในตัวแปร เพื่อนำไปใช้ต่อไป $sidebar.hide(); // ...stuff... $sidebar.css({ 'background-color': 'pink' }); }
-
เวลาค้นหา DOM ให้ใช้รูปแบบของ Cascading เช่น
$('.sidebar ul')
หรือ parent > child$('.sidebar > ul')
- jsPerf -
ใช้
find
ร่วมกับ jQuery object (ที่เราแคชไว้ก่อนหน้านี้)// ไม่ดี $('ul', '.sidebar').hide(); // ไม่ดี $('.sidebar').find('ul').hide(); // ดี $('.sidebar ul').hide(); // ดี $('.sidebar > ul').hide(); // ดี $sidebar.find('ul').hide();
- อ่านเพิ่มเติมได้ที่ Kangax's ES5 compatibility table.
-
Yup.
function() { return true; }
อ่านเพิ่มเติมจากข้อมูลต่อไปนี้
- 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...
อ่านเพิ่มเติม
เครื่องมือต่าง ๆ
- Code Style Linters
ข้อมูลแนะนำการเขียนจาวาสคริปต์อื่น ๆ
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
- JavaScript Standard Style
ข้อมูลแนะนำสไตล์อื่น ๆ
- 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 - Kyle Simpson
บล็อก
- DailyJS
- JavaScript Weekly
- JavaScript, JavaScript...
- Bocoup Weblog
- Adequately Good
- NCZOnline
- Perfection Kills
- Ben Alman
- Dmitry Baranovskiy
- Dustin Diaz
- nettuts
พอดคาสต์ (Podcasts)
รายชื่อองกรค์ที่ทำตามคู่มือแนะนำการเขียนจาวาสคริปต์นี้ ถ้าองค์กรของคุณทำตามคู่มือนี้เช่นกัน กรุณาส่ง Pull request หรือเปิด Issue แล้วเราจะเพิ่มคุณเข้าไปในรายชื่อต่อไปนี้
- Aan Zee: AanZee/javascript
- Adult Swim: adult-swim/javascript
- Airbnb: airbnb/javascript
- American Insitutes for Research: AIRAST/javascript
- Apartmint: apartmint/javascript
- Avalara: avalara/javascript
- Compass Learning: compasslearning/javascript-style-guide
- DailyMotion: dailymotion/javascript
- Digitpaint digitpaint/javascript
- Evernote: evernote/javascript-style-guide
- ExactTarget: ExactTarget/javascript
- Gawker Media: gawkermedia/javascript
- GeneralElectric: GeneralElectric/javascript
- GoodData: gooddata/gdc-js-style
- Grooveshark: grooveshark/javascript
- How About We: howaboutwe/javascript
- InfoJobs: InfoJobs/JavaScript-Style-Guide
- Intent Media: intentmedia/javascript
- Jam3: Jam3/Javascript-Code-Conventions
- Kinetica Solutions: kinetica/javascript
- Mighty Spring: mightyspring/javascript
- MinnPost: MinnPost/javascript
- ModCloth: modcloth/javascript
- Money Advice Service: moneyadviceservice/javascript
- Muber: muber/javascript
- National Geographic: natgeo/javascript
- National Park Service: nationalparkservice/javascript
- Nimbl3: nimbl3/javascript
- Nordic Venture Family: CodeDistillery/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
- Userify: userify/javascript
- VoxFeed: VoxFeed/javascript-style-guide
- Weggo: Weggo/javascript
- Zillow: zillow/javascript
- ZocDoc: ZocDoc/javascript
คู่มือแนะนำการเขียนจาวาสคริปต์นี้ได้ถูกแปลเป็นภาษาต่าง ๆ มากมายดังต่อไปนี้:
- Brazilian Portuguese: armoucar/javascript-style-guide
- Bulgarian: borislavvv/javascript
- Catalan: fpmweb/javascript-style-guide
- Chinese(Traditional): jigsawye/javascript
- Chinese(Simplified): adamlu/javascript-style-guide
- French: nmussy/javascript-style-guide
- German: timofurrer/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
- ติดต่อเราบน gitter.
(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.