└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | Pragmatic jQuery Style
2 | ========
3 |
4 | ## Why jQuery Style?
5 | * 80% of the lifetime cost of a piece of software goes to maintenance.
6 | * Hardly any software is maintained for its whole life by the original author.
7 | * Code conventions improve the readability of the software, allowing engineers to understand new code more quickly and thoroughly.
8 | * If you ship your source code as a product, you need to make sure it is as well packaged and clean as any other product you create.
9 |
10 | ## Plugin Name
11 | lowercase, avoid multiple words.
12 | ```
13 | // bad
14 | jquery.string.js
15 | jquery.observer.js
16 | jquery.promise.js
17 |
18 | // good
19 | string.js
20 | observer.js
21 | promise.js
22 | ```
23 |
24 | ## Plugin Skeleton
25 |
26 | ```js
27 | // pluginName.js
28 |
29 | (function (factory) {
30 | if (typeof define === 'function') {
31 | define(['$'], factory);
32 | } else {
33 | factory($);
34 | }
35 | })(function ($) {
36 | 'use strict';
37 | var pluginName = 'pluginName';
38 | // balabala...
39 | })
40 | ```
41 |
42 | There are some kinds of jQuery plugins:
43 | - `$.pluginName( { name:"value" } );` Attach to $ because they did not want to create a global, but just indicate it is jQuery-related functionality. They do not do anything with node lists though.
44 | ```js
45 | // pluginName.js
46 |
47 | (function (factory) {
48 | if (typeof define === 'function') {
49 | ...
50 | })(function ($) {
51 | 'use strict';
52 | var pluginName = 'defaultPluginName';
53 |
54 | function plugin( options ){
55 | // ...
56 | }
57 |
58 | $[pluginName] = plugin;
59 | })
60 | ```
61 | - `$(".foo").pluginName( { name:"value" } );` Attach to $.fn because they want to participate in the chained API style when operating with a node list.
62 | ```js
63 | // pluginName.js
64 |
65 | (function (factory) {
66 | if (typeof define === 'function') {
67 | ...
68 | })(function ($) {
69 | 'use strict';
70 | var pluginName = 'defaultPluginName';
71 |
72 | function plugin( element, options ) {
73 | // ...
74 | }
75 |
76 | $.fn[pluginName] = function ( options ) {
77 | return this.each(function () {
78 | // ...
79 | })
80 |
81 | });
82 | })
83 | ```
84 |
85 | - `$.ajax( { dataType:"jsonpi" } );` custom jQuery ajax request http://api.jquery.com/extending-ajax/
86 | ```js
87 | // pluginName.js
88 |
89 | (function (factory) {
90 | if (typeof define === 'function') {
91 | ...
92 | })(function ($) {
93 | 'use strict';
94 | var pluginName = 'jsonpi';
95 |
96 | $.ajaxTransport( pluginName , function(opts, originalOptions, jqXHR) {
97 | // ...
98 | });
99 | })
100 | ```
101 | - `$( 'div:inline' );` custom jQuery selector
102 | ```js
103 | // pluginName.js
104 |
105 | (function (factory) {
106 | if (typeof define === 'function') {
107 | ...
108 | })(function ($) {
109 | 'use strict';
110 | var pluginName = 'defaultPluginName';
111 |
112 | $.expr[':'][pluginName] = function(element) {
113 | return $(element).css('display') === 'inline';
114 | };
115 |
116 | $(':inline'); // Selects ALL inline elements
117 | $('a:inline'); // Selects ALL inline anchors
118 | })
119 | ```
120 | - `$( '#element' ).on('cumstomEvent', function(){});` custom jQuery Event
121 | ```js
122 | // pluginName.js
123 |
124 | (function (factory) {
125 | if (typeof define === 'function') {
126 | ...
127 | })(function ($) {
128 | 'use strict';
129 | var eventName = 'customEventName';
130 |
131 | $.event.special[eventName] = {
132 | // called when the event is bound
133 | setup: function(data, namespaces) {
134 | var $this = $(this);
135 | },
136 | // called when event is unbound
137 | teardown: function(namespaces) {
138 | var $this = $(this);
139 | },
140 | // called when event is dispatched
141 | handler: function(event) {
142 | var $this = $(this);
143 | },
144 | // similar to setup, but is called for each event being bound
145 | add: function(event) {
146 | var $this = $(this);
147 | },
148 | // similar to teardown, but is called for each event being unbound
149 | remove: function(event) {
150 | var $this = $(this);
151 | }
152 | };
153 |
154 | // bind custom event
155 | $("#element").on("customEventName.myNamespace", function(evt) {});
156 | // remove all events under the myNamespace namespace
157 | $("#element").off(".myNamespace");
158 |
159 | })
160 | ```
161 | - `$( 'textarea.foo' ).val();` custom form element value hook
162 | ```js
163 | // valHooks.js
164 |
165 | (function (factory) {
166 | if (typeof define === 'function') {
167 | ...
168 | })(function ($) {
169 | 'use strict';
170 |
171 | $.valHooks.textarea = {
172 | get: function( elem ) {
173 | return elem.value.replace( /\r?\n/g, "\r\n" );
174 | }
175 | };
176 |
177 | })
178 | ```
179 |
180 | ## Indentation
181 | The unit of indentation is four spaces.
182 | ```js
183 | arr.forEach(function(val, key){
184 | ....// indentation with four spaces
185 | });
186 | ```
187 |
188 | ## Variable Name
189 | lowerCamelCase
190 | ```js
191 | var thisIsMyVal;
192 | var md5Encoder;
193 | var xmlReader;
194 | var httpServer;
195 | ```
196 |
197 | ## Method Name
198 | lowerCamelCase
199 | ```js
200 | function thisIsMyFunction() {
201 | // balabala
202 | }
203 | ```
204 |
205 | ```
206 | // Some special cases
207 | getMd5() instead of getMD5()
208 | getHtml() instead of getHTML()
209 | getJsonResponse() instead of getJSONResponse()
210 | parseXmlContent() instead of parseXMLContent()
211 | ```
212 |
213 | ### Why lowerCamelCase?
214 |
215 | We know that when you write Ruby or Python, you use under_scored method names and UpperCamelCase class names. But JavaScript isn't Ruby or Python.
216 |
217 | Consider:
218 |
219 | * In browsers, all methods are lowerCamelCase.
220 | * In node.js's standard library, all methods are lowerCamelCase.
221 | * In commonjs, all methods are lowerCamelCase.
222 | * In jQuery, all methods are lowerCamelCase.
223 | * In MooTools, all methods are lowerCamelCase.
224 | * In Prototype, all method are lowerCamelCase.
225 | * In YUI, all method are lowerCamelCase.
226 | * In JavaScript: The Good Parts, all methods are lowerCamelCase.
227 |
228 | We not saying you must write JavaScript a certain way. Do what makes you the most comfortable, write code that is fun to write. But if you're trying to figure out what everyone else is doing, they're doing lowerCamelCase.
229 |
230 | ## Constant
231 |
232 | ```js
233 | var LOCALHOST = "http://localhost";
234 | var REMORT_PORT = "8080";
235 | ```
236 |
237 | ## Class Name
238 | UpperCamelCase
239 | ```js
240 | var Greeter = Class.extend({
241 | name: null,
242 |
243 | _constructor: function(name) {
244 | this.name = name;
245 | },
246 |
247 | greet: function() {
248 | alert('Good Morning ' + this.name);
249 | }
250 | });
251 | ```
252 |
253 | ## Variables Declare
254 |
255 | ```js
256 | var a = "alpha";
257 | var b = "beta";
258 | var c = "chi";
259 | ```
260 |
261 | ### Why?
262 | ```js
263 | var a = "alpha",
264 | b = "beta" // <-- note the forgotten comma
265 | c = "chi"; // <-- and now c is global
266 | ```
267 |
268 |
269 | ## Leading Commas
270 |
271 | ```js
272 |
273 | var hero = {
274 | firstName: 'Bob'
275 | , lastName: 'Parr'
276 | , heroName: 'Mr. Incredible'
277 | , superPower: 'strength'
278 | };
279 | ```
280 | ### Why?
281 | ```
282 | var hero = {
283 | firstName: 'Bob',
284 | lastName: 'Parr',
285 | heroName: 'Mr. Incredible', // <-- forgot remove comma, when comment the last item
286 | //superPower: 'strength'
287 | };
288 | ```
289 |
290 |
291 | ## jQuery Object
292 | Prefix jQuery object variables with a `$`.
293 | The dollar notation on all jQuery-related variables helps us easily distinguish jQuery variables from standard JavaScript variables at a glance.
294 |
295 | ```js
296 | // bad
297 | var sidebar = $('.sidebar');
298 | var that = $(this);
299 |
300 | // good
301 | var $sidebar = $('.sidebar');
302 | var $this = $(this);
303 | ```
304 |
305 | ## Method Chains
306 | Use indentation when making long method chains, and avoid more than 6 methods chained.
307 | Less method chains, more friendly debugging.
308 |
309 | ```js
310 | // bad
311 | $('#items').find('.selected').highlight().end().find('.open').updateCount();
312 |
313 | // good
314 | $('#items')
315 | .find('.selected')
316 | .highlight()
317 | .end()
318 | .find('.open')
319 | .updateCount();
320 | ```
321 |
322 | ## Determine jQuery Object
323 | Determine if an object is a jQuery object
324 |
325 | ```js
326 | // bad (fast)
327 | if( obj.jquery ){}
328 |
329 | // good (slow)
330 | if( obj instanceof jQuery){}
331 | ```
332 |
333 | ## Document Ready
334 |
335 | ```js
336 | // bad
337 | $(function() {
338 | // Handler for .ready() called.
339 | });
340 |
341 | // good
342 | $(document).ready(function() {
343 | // Handler for .ready() called.
344 | });
345 | ```
346 |
347 | ## Event Bind
348 | ```js
349 | // bad
350 | $( "#members li a" ).bind( "click", function( e ) {} );
351 |
352 | // good
353 | $( "#members li a" ).on( "click", function( e ) {} );
354 |
355 | // good
356 | $( "#members li a" ).click( function( e ) {} );
357 |
358 | ```
359 |
360 | ## Event Live
361 |
362 | ```js
363 | // bad, .live() deprecated jQuery 1.7, removed jQuery 1.9
364 | $( "#members li a" ).live( "click", function( e ) {} );
365 |
366 | // good
367 | $( document ).on( "click", "#members li a", function( e ) {} );
368 |
369 | ```
370 |
371 | ## Event Delegate
372 | ```js
373 | // bad, as of jQuery 1.7, .delegate() has been superseded by the .on() method
374 | $( "#members" ).delegate( "li a", "click", function( e ) {} );
375 |
376 | // good
377 | $( "#members" ).on( "click", "li a", function( e ) {} );
378 | ```
379 | ## Event Prevent
380 | ```js
381 | // bad
382 | $(".btn").click(function(event){
383 | // @more: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
384 | return false;
385 | });
386 |
387 | // good
388 | $(".btn").click(function(event){
389 | event.preventDefault();
390 | });
391 |
392 | // good
393 | $(".btn").click(function(event){
394 | event.preventDefault();
395 | event.stopImmediatePropagation()
396 | });
397 |
398 | // good
399 | $(".btn").click(function(event){
400 | event.stopPropagation();
401 | event.preventDefault();
402 | event.stopImmediatePropagation();
403 | });
404 |
405 | ```
406 |
407 | ## Element Create
408 | ```js
409 | // bad
410 | $('')
411 | .attr({
412 | id : 'someId',
413 | className : 'someClass',
414 | href : 'somePath.html'
415 | });
416 |
417 | // good
418 | $('', {
419 | id : 'someId',
420 | className : 'someClass',
421 | href : 'somePath.html'
422 | });
423 |
424 | ```
425 |
426 | ## Element Exists
427 |
428 | ```js
429 | // bad
430 | if ($('#myElement')[0]) {
431 | // balabala...
432 | }
433 |
434 | // good
435 | if ($('#myElement').length) {
436 | // balabala...
437 | }
438 | ```
439 |
440 | ## Element Access
441 | ```
442 | // bad
443 | $('#myElement').click(funtion(){
444 | var id = $(this).attr('id');
445 | })
446 |
447 | // good
448 | $('#myElement').click(funtion(){
449 | var id = this.id;
450 | })
451 | ```
452 |
453 | ## Number Compare
454 | ```
455 | var a = '1';
456 | var b = 1;
457 |
458 | // bad
459 | parseInt(a) === b
460 |
461 | // good, implicit coercion
462 | +a === b
463 |
464 | // better, expressive conversion, and conversion over coercion
465 | Number(a) === b
466 | ```
467 |
468 | ## Array Traversal
469 | ```
470 | var array = new Array(100);
471 | // bad (faster)
472 | for (var i=0,l=array.length; i -1) {
546 | this.tryCount++;
547 | if (this.tryCount <= this.retryLimit) {
548 | //try again
549 | return $.ajax(this);
550 | }
551 | return alert('Oops! There was a problem, please try again later.');
552 | }
553 | }
554 | });
555 |
556 | // better, inspried by https://github.com/mberkom/jQuery.retryAjax
557 | $.retryAjax = function (ajaxParams) {
558 | var errorCallback;
559 | ajaxParams.tryCount = ajaxParams.tryCount > 0 ? ajaxParams.tryCount : 0;
560 | ajaxParams.retryLimit = ajaxParams.retryLimit > 0 : ajaxParams.retryLimit : 2;
561 | // Custom flag for disabling some jQuery global Ajax event handlers for a request
562 | ajaxParams.suppressErrors = true;
563 |
564 | if (ajaxParams.error) {
565 | errorCallback = ajaxParams.error;
566 | ajaxParams.error = null;
567 | } else {
568 | errorCallback = $.noop;
569 | }
570 |
571 | ajaxParams.complete = function (jqXHR, textStatus) {
572 | if ($.inArray(textStatus, ['timeout', 'abort', 'error']) > -1) {
573 | this.tryCount++;
574 | if (this.tryCount <= this.retryLimit) {
575 | // fire error handling on the last try
576 | if (this.tryCount === this.retryLimit) {
577 | this.error = errorCallback;
578 | this.suppressErrors = null;
579 | }
580 | //try again
581 | $.ajax(this);
582 | return true;
583 | }
584 |
585 | alert('Oops! There was a problem, please try again later.');
586 | return true;
587 | }
588 | };
589 |
590 | $.ajax(ajaxParams);
591 | };
592 |
593 | $.retryAjax({
594 | url : 'path/to/url',
595 | type : 'get',
596 | data : {name : 'value'},
597 | dataType : 'json',
598 | timeout : 25000,
599 | success : function(json) {
600 | //do something
601 | }
602 | })
603 | ```
604 |
605 | ## Performance
606 | - Cache jQuery lookups
607 | ```js
608 | // bad
609 | function setSidebar() {
610 | $('.sidebar').hide();
611 |
612 | // ...stuff...
613 |
614 | $('.sidebar').css({
615 | 'background-color': 'pink'
616 | });
617 | }
618 |
619 | // good
620 | function setSidebar() {
621 | var $sidebar = $('.sidebar');
622 | $sidebar.hide();
623 |
624 | // ...stuff...
625 |
626 | $sidebar.css({
627 | 'background-color': 'pink'
628 | });
629 | }
630 | ```
631 |
632 | - For DOM queries use Cascading `$('.sidebar ul')` or parent > child `$('.sidebar > .ul')`. [jsPerf](http://jsperf.com/jquery-find-vs-context-sel/16)
633 | - Use `find` with scoped jQuery object queries.
634 | ```js
635 | // bad
636 | $('.sidebar', 'ul').hide();
637 |
638 | // bad
639 | $('.sidebar').find('ul').hide();
640 |
641 | // good
642 | $('.sidebar ul').hide();
643 |
644 | // good
645 | $('.sidebar > ul').hide();
646 |
647 | // good (slower)
648 | $sidebar.find('ul');
649 |
650 | // good (faster)
651 | $($sidebar[0]).find('ul');
652 | ```
653 | - Use `event delegation` with list of elements.
654 | ```html
655 |
656 | - list1
657 | - list2
658 | - list3
659 | - list4
660 | - list5
661 |
662 | ```
663 |
664 | ```js
665 | // bad
666 | $("ul li").on("click", function() {
667 | $(this).text("aha");
668 | });
669 |
670 | // good
671 | $("ul").on("click", "li", function() {
672 | $(this).text("aha");
673 | });
674 | ```
675 |
676 | # Ref
677 | * jQuery Core Style Guide - http://docs.jquery.com/JQuery_Core_Style_Guidelines
678 | * Airbnb JavaScript Style Guide - https://github.com/airbnb/javascript
679 | * Idiomatic.js - https://github.com/rwldrn/idiomatic.js/
680 | * NPM's "funny" coding style - https://npmjs.org/doc/coding-style.html
681 |
--------------------------------------------------------------------------------