29
Cleaner, Leaner, Meaner Refactoring your JavaScript Rebecca Murphey • @rmurphey • rebeccamurphey.com Thursday, December 2, 2010

Cleaner, Leaner, Meaner: Refactoring your jQuery

Embed Size (px)

Citation preview

Page 1: Cleaner, Leaner, Meaner: Refactoring your jQuery

Cleaner, Leaner, MeanerRefactoring your JavaScript

Rebecca Murphey • @rmurphey • rebeccamurphey.com

Thursday, December 2, 2010

Page 2: Cleaner, Leaner, Meaner: Refactoring your jQuery

http://github.com/rmurphey/refactor-jquery

2Thursday, December 2, 2010

Page 3: Cleaner, Leaner, Meaner: Refactoring your jQuery

“... changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure.”

Martin Fowler, “Refactoring”

3Thursday, December 2, 2010

Page 4: Cleaner, Leaner, Meaner: Refactoring your jQuery

Hint: Not just because it’s fun.

Why Refactor?Thursday, December 2, 2010

Page 5: Cleaner, Leaner, Meaner: Refactoring your jQuery

“When you sit down and solve a problem, that solution is merely a !rst draft.”

Stoyan Stefanov, “JavaScript Patterns”

5Thursday, December 2, 2010

Page 6: Cleaner, Leaner, Meaner: Refactoring your jQuery

Internal rewards

Increase maintainability

Increase extensibility & reusability

User experience rewards

Improve page performance

Increase testability (reduce bugs)

6Thursday, December 2, 2010

Page 7: Cleaner, Leaner, Meaner: Refactoring your jQuery

Put another way: Refactoring will make your users happier, make your code cheaper to work with, or both.

7Thursday, December 2, 2010

Page 8: Cleaner, Leaner, Meaner: Refactoring your jQuery

8 tell-tale signs your code needs some TLC

JavaScriptCode Smells

Thursday, December 2, 2010

Page 9: Cleaner, Leaner, Meaner: Refactoring your jQuery

HTML in your JavaScript

$('#showMessage').click(function() { $('<div>' + '<h1>' + $('#messageTitle').val() + '</h1>' + '<p>' + $('#messageText').val() + '</p>' + '</div>') .appendTo('#messageContainer')});

// MINTY FRESH: Use templates instead<script type="text/x-jquery-tmpl" id="messageTemplate"> <div> <h1>${title}</h1> <p>${text}</p> </div></script>

$('#messageTemplate').template('messageTemplate');

$.tmpl('messageTemplate', { title : $('#messageTitle').val(), text : $('#messageText').val()}).appendTo('#messageContainer');

9Thursday, December 2, 2010

Page 10: Cleaner, Leaner, Meaner: Refactoring your jQuery

http://api.jquery.com/category/plugins/templates/

http://github.com/janl/mustache.js/

http://documentcloud.github.com/underscore/

10Thursday, December 2, 2010

Page 11: Cleaner, Leaner, Meaner: Refactoring your jQuery

Changing style information in JavaScript

$('p.special').click(function() { $(this).css({ 'color' : 'red', 'font-weight' : 'bold' });})

// MINTY FRESH: Keep presentation information in CSSp.extraSpecial { color: red; font-weight: bold;}

$('p.special').click(function() { $(this).addClass('extraSpecial');});

11Thursday, December 2, 2010

Page 12: Cleaner, Leaner, Meaner: Refactoring your jQuery

Duplication of existing jQuery methods

function isItemInArray(item, arr) { var inArray = false, len = arr.length;

for (var i = 0; i < len; i++) { if (item == arr[i]) { inArray = true; } } return inArray;}

// MINTY FRESH: Use jQuery!function isItemInArray(item, arr) { return $.inArray(item, arr) > -1;}

12Thursday, December 2, 2010

Page 13: Cleaner, Leaner, Meaner: Refactoring your jQuery

http://api.jquery.com/category/utilities/

http://api.jquery.com/category/miscellaneous/

13Thursday, December 2, 2010

Page 14: Cleaner, Leaner, Meaner: Refactoring your jQuery

Repetition that jQuery lets you avoid

$('a.thinger').each(function() { $(this).attr('href', $(this).attr('href') + '?ajax=true');});$('a.thinger').hide();$('#myButton').click(function(){ $('a.thinger').show();})

// MINTY FRESH: Use the chain and setter functions!var thingers = $('a.thinger'), // store selection in a var button = $('#myButton'); // just in case!

thingers.attr('href', function(idx, oldVal) { // pass a setter function & avoid the need // to iterate over matches return oldVal + '?ajax=true';}).hide();

button.click(function() { thingers.show();});

14Thursday, December 2, 2010

Page 15: Cleaner, Leaner, Meaner: Refactoring your jQuery

Deeply nested anonymous functions

$(document).ready(function() { $('#enableAwesome').click(function() { $('ul.foo li').each(function() { var li = $(this);

li.data('name', li.html()) .find('a.remove').click(function(e) { $.ajax({ url : $(this).attr('href'), dataType : 'json', type : 'post', success : function(resp) { if (resp.ok) { li.remove(); } }, error : console.log }) e.preventDefault(); }); }) });});

15Thursday, December 2, 2010

Page 16: Cleaner, Leaner, Meaner: Refactoring your jQuery

// MINTY FRESH: Isolate functionality into an object with methodsvar awesome = { enableListItem : function() { var li = $(this); li.data('name', li.html()); }, removeListItem : function() { var a = $(this), li = a.closest('li');

awesome.removeOnServer({ url : a.attr('href'), success : function(resp) { if (resp.ok) { li.remove(); } } }); }, removeOnServer : function (config) { var defaults = { type : 'post', dataType : 'json', error : console.log }, settings = $.extend({}, defaults, config);

if (!settings.url) { return; } $.ajax(config); }};

16Thursday, December 2, 2010

Page 17: Cleaner, Leaner, Meaner: Refactoring your jQuery

$(document).ready(function() { $('#enableAwesome').click(function() { $('ul.foo li') .each(awesome.enableListItem) .delegate('a.remove', 'click', awesome.removeListItem); });});

17Thursday, December 2, 2010

Page 18: Cleaner, Leaner, Meaner: Refactoring your jQuery

Overtesting for truthiness

// SMELLY: Overtesting for truthinessif (errorMsg != null && errorMsg.length > 0) { // ...}

// MINTY FRESH: Be as terse as you canif (errorMsg && errorMsg.length) { // ...}

18Thursday, December 2, 2010

Page 19: Cleaner, Leaner, Meaner: Refactoring your jQuery

// SMELLYif (total == null || total == "0") { // ...}

// MINTY FRESHif (!parseInt(total, 10)) { // ... }

19Thursday, December 2, 2010

Page 20: Cleaner, Leaner, Meaner: Refactoring your jQuery

// SMELLYif (price == null) { // ...} else if(discountPrice != null && price == discountPrice) { // ...}

// MINTY FRESHif (!price) { // ...

// we already know that price isn't null,// so why test if discountPrice is? if it's// equal to price, we know it's not null} else if (price == discountPrice) { // ...}

20Thursday, December 2, 2010

Page 21: Cleaner, Leaner, Meaner: Refactoring your jQuery

Repetitive logic blocks

function isItABigNumber(num) { if(num > 5000) { $('#myContainer').html('<p>It was a big number</p>'); $('#myInput').val(num); $('.thinger').hide(); } else { $('#myContainer').html('<p>It was not a big number</p>'); $('#myInput').val(''); $('.thinger').show(); }}

// MINTY FRESH: Only repeat what needs repeatingfunction isItABigNumber(num) { var big = num > 5000; $('#myContainer').html(big ? '<p>It was a big number</p>' : '<p>It was not a big number</p>'); $('#myInput').val(big ? num : ''); $('.thinger')[big ? 'hide' : 'show']();}

21Thursday, December 2, 2010

Page 22: Cleaner, Leaner, Meaner: Refactoring your jQuery

Passing a lot of arguments to a function

function crazyConcatenation(selector, word1, word2, word3, repeat) { var arr = [], words = [], joinedWords; if (selector == null) { return; } if (word1 == null) { return; } if (word2 != null) { words.push(word2); } if (word3 != null) { words.push(word3); } if (!repeat) { repeat = 5; } joinedWords = words.join(', '); while (repeat--) { arr.push(joinedWords); } $(selector).html(arr.join('<br/>'))}

crazyConcatenation('#foo', 'Hello', null, null, 5);

22Thursday, December 2, 2010

Page 23: Cleaner, Leaner, Meaner: Refactoring your jQuery

// MINTY FRESH: Using an object insteadfunction crazyConcatenation(config) { // indicate clearly what's required if ( !config.selector || !config.words || !config.words.length ) { return; } var defaults = { repeat : 5 }, settings = $.extend({}, defaults, config), joinedWords = settings.words.join(', '); while (settings.repeat--) { arr.push(joinedWords); } $(settings.selector).html(arr.join('<br/>'))}

crazyConcatenation({ selector : '#foo', words : [ 'foo', 'bar', 'baz' ], repeat : 20});

23Thursday, December 2, 2010

Page 24: Cleaner, Leaner, Meaner: Refactoring your jQuery

Advanced MovesCommon patterns for improving your code

Thursday, December 2, 2010

Page 25: Cleaner, Leaner, Meaner: Refactoring your jQuery

“Writing to be read means writing code ... with the idea that someone else will read it. is fact alone will make you edit and think of better ways to solve the problem you have at hand.”

Stoyan Stefanov, “JavaScript Patterns”

25Thursday, December 2, 2010

Page 26: Cleaner, Leaner, Meaner: Refactoring your jQuery

example add the same behavior to similar content without depending on IDs

26Thursday, December 2, 2010

Page 27: Cleaner, Leaner, Meaner: Refactoring your jQuery

example cache XHR responses, and create an API to a server-side service

27Thursday, December 2, 2010

Page 28: Cleaner, Leaner, Meaner: Refactoring your jQuery

example refactor a portlet to use the jQuery UI widget factory

28Thursday, December 2, 2010

Page 29: Cleaner, Leaner, Meaner: Refactoring your jQuery

rebeccamurphey.com

blog.rebeccamurphey.com

@rmurphey

http://github.com/rmurphey/refactor-jquery

http://pinboard.in/u:rmurphey/t:refactor/

Presented at the 2010 Rich Web Experience

29Thursday, December 2, 2010