└── 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 | 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 | --------------------------------------------------------------------------------