Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dollar Symbol 深入淺出 2015/1 @ 5xRuby

Aaron Huang
January 14, 2016

Dollar Symbol 深入淺出 2015/1 @ 5xRuby

去年在 5xRuby 開的付費課程的 Slide

Aaron Huang

January 14, 2016
Tweet

More Decks by Aaron Huang

Other Decks in Technology

Transcript

  1. Aaron Huang • Sr. F2E @ • F2E Lead @

    Faria Systems • Cloud Service Engineer @ Waveface • Xing “⾏行” - Evernote DevCup 2013 Top 6
  2. https://jquery.org/history/ • Aug 22, 2005 - John Resig first hints

    • Jan 14, 2006 - jQuery announced • Jan, 2006 - First jQuery Plugin • Aug, 2006 - v1.0 released • Jul, 2007 - jQuery UI announced • Aug, 2008 - jQuery Conference • Jan, 2009 - Sizzle.js • 歷史
  3. https://jquery.org/history/ • Aug 22, 2005 - John Resig first hints

    • Jan 14, 2006 - jQuery announced • Jan, 2006 - First jQuery Plugin • Aug, 2006 - v1.0 released • Jul, 2007 - jQuery UI announced • Aug, 2008 - jQuery Conference • Jan, 2009 - Sizzle.js • Nov, 2011 - jQuery Mobile 1.0 • Jan, 2013 - jQuery 1.9, 2.0-beta, jquery migrate • April 18, 2013 - jQuery 2.0 • Sep 19, 2013 - jQuery 1.11 and 2.1 Beta 1 Released 歷史
  4. https://jquery.org/history/ • Aug, 2008 - jQuery Conference • Jan, 2009

    - Sizzle.js • Nov, 2011 - jQuery Mobile 1.0 • Jan, 2013 - jQuery 1.9, 2.0-beta, jquery migrate • April 18, 2013 - jQuery 2.0 • Sep 19, 2013 - jQuery 1.11 and 2.1 Beta 1 Released
  5. RequireJS (After v1.11) •core/ •var/ •event/ •…./ •jquery.js •core.js •event.js

    •selector.js •… •… dependency resolve r.js convert wrap output
  6. RequireJS (After v1.11) •core/ •var/ •event/ •…./ •jquery.js •core.js •event.js

    •selector.js •… •… dependency resolve r.js convert wrap output
  7. RequireJS (After v1.11) •core/ •var/ •event/ •…./ •jquery.js •core.js •event.js

    •selector.js •… •… dependency resolve r.js convert wrap output jQuery.js
  8. // $.fn.ready #2 $(function(){ // callback content. }); // $.fn.ready

    #1 $(document).ready(function(){ // callback content. });
  9. // Kick off the DOM ready check even if the

    user does not jQuery.ready.promise(); Challenge
  10. // Kick off the DOM ready check even if the

    user does not jQuery.ready.promise(); http://macb.in/do3E Challenge
  11. // Define a local copy of jQuery jQuery = function(

    selector, context ) { return new jQuery.fn.init( selector, context ); },
  12. // A central reference to the root jQuery(document) var rootjQuery,

    // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, init = jQuery.fn.init = function( selector, context ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); }
  13. // A central reference to the root jQuery(document) var rootjQuery,

    // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, init = jQuery.fn.init = function( selector, context ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/
  14. Selector 以外的狀況 // HANDLE: $(""), $(null), $(undefined), $(false) if (

    !selector ) { return this; } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return typeof rootjQuery.ready !== "undefined" ? rootjQuery.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); }
  15. // A central reference to the root jQuery(document) var rootjQuery,

    // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, init = jQuery.fn.init = function( selector, context ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); }
  16. // A central reference to the root jQuery(document) var rootjQuery,

    // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, init = jQuery.fn.init = function( selector, context ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/
  17. rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ Non-capturing Group 1 Group 2 match =

    rquickExpr.exec( selector ); • match[0] = // Matched String
  18. rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ Non-capturing Group 1 Group 2 match =

    rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // Tag html
  19. rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ Non-capturing Group 1 Group 2 match =

    rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // Tag html • match[2] = // ID name
  20. rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ Non-capturing Group 1 Group 2 match =

    rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // Tag html • match[2] = // ID name $('#id')
  21. rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/ Non-capturing Group 1 Group 2 match =

    rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // Tag html • match[2] = // ID name $('#id') $('<div/>')
  22. // Handle HTML strings if ( typeof selector === "string"

    ) { if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); }
  23. // Match html or make sure no context is specified

    for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] );
  24. // ...and otherwise set as attributes } else { this.attr(

    match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[2] ); // Support: Blackberry 4.6 // gEBID returns nodes no longer in the document (#6963) if ( elem && elem.parentNode ) { // Inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; }
  25. this.context = document; this.selector = selector; return this; } //

    HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); }
  26. node type Name Value ELEMENT_NODE 1 ATTRIBUTE_NODE 2 TEXT_NODE 3

    CDATA_SECTION_NODE 4 ENTITY_REFERENCE_NODE 5 ENTITY_NODE 6 PROCESSING_INSTRUCTION_NODE 7 COMMENT_NODE 8 DOCUMENT_NODE 9 DOCUMENT_TYPE_NODE 10 DOCUMENT_FRAGMENT_NODE 11 NOTATION_NODE 12
  27. node type Name Value ELEMENT_NODE 1 ATTRIBUTE_NODE 2 TEXT_NODE 3

    CDATA_SECTION_NODE 4 ENTITY_REFERENCE_NODE 5 ENTITY_NODE 6 PROCESSING_INSTRUCTION_NODE 7 COMMENT_NODE 8 DOCUMENT_NODE 9 DOCUMENT_TYPE_NODE 10 DOCUMENT_FRAGMENT_NODE 11 NOTATION_NODE 12
  28. var match, elem, m, nodeType, // QSA vars i, groups,

    old, nid, newContext, newSelector; if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; results = results || []; nodeType = context.nodeType; if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; }
  29. var match, elem, m, nodeType, // QSA vars i, groups,

    old, nid, newContext, newSelector; if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; results = results || []; nodeType = context.nodeType; if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; }
  30. if ( !seed && documentIsHTML ) { // Try to

    shortcut find operations when possible (e.g., not under DocumentFragment) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { // Context is not a document
  31. if ( !seed && documentIsHTML ) { // Try to

    shortcut find operations when possible (e.g., not under DocumentFragment) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { // Context is not a document
  32. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String
  33. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // ID name
  34. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // ID name • match[2] = // TAG name
  35. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // ID name • match[2] = // TAG name • match[3] = // CLASS name
  36. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // ID name • match[2] = // TAG name • match[3] = // CLASS name $(‘.class')
  37. rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/ Group 1 Group 2 Group 3 match

    = rquickExpr.exec( selector ); • match[0] = // Matched String • match[1] = // ID name • match[2] = // TAG name • match[3] = // CLASS name $(‘.class') $('div')
  38. if ( !seed && documentIsHTML ) { // Try to

    shortcut find operations when possible (e.g., not under DocumentFragment) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { // Context is not a document
  39. if ( !seed && documentIsHTML ) { // Try to

    shortcut find operations when possible (e.g., not under DocumentFragment) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { // Context is not a document
  40. } else { // Context is not a document if

    ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } }
  41. } else { // Context is not a document if

    ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } e.g.,$(‘body’).find(‘#id’)
  42. // Speed-up: Sizzle("TAG") } else if ( match[2] ) {

    push.apply( results, context.getElementsByTagName( selector ) ); return results; // Speed-up: Sizzle(".CLASS") } else if ( (m = match[3]) && support.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } }
  43. // Speed-up: Sizzle("TAG") } else if ( match[2] ) {

    push.apply( results, context.getElementsByTagName( selector ) ); return results; // Speed-up: Sizzle(".CLASS") } else if ( (m = match[3]) && support.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } }
  44. // QSA path if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test(

    selector )) ) { nid = old = expando; newContext = context; newSelector = nodeType !== 1 && selector; // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { groups = tokenize( selector ); if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); } nid = "[id='" + nid + "'] "; i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; newSelector = groups.join(","); }
  45. Challenge // qSA works strangely on Element-rooted queries // We

    can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements
  46. http://macb.in/1fDSb Challenge // qSA works strangely on Element-rooted queries //

    We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements
  47. // QSA path if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test(

    selector )) ) { nid = old = expando; newContext = context; newSelector = nodeType !== 1 && selector; // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { groups = tokenize( selector ); if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); } nid = "[id='" + nid + "'] "; i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; newSelector = groups.join(","); }
  48. if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll(

    newSelector ) ); return results; } catch(qsaError) { } finally { if ( !old ) { context.removeAttribute("id"); } } } }
  49. jQuery.Deferred() • A constructor function that returns a chainable utility

    object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function. • jQuery Deferred is based on the CommonJS Promises/A design.
  50. 了解 Deferred • ⾏行為 • 假設 • 狀態 (resolve, reject,

    notify) (done, fail, progress) (resolved, rejected, pending)
  51. • deferred.promise() -> state: pending • deferred.resolve() -> deferred.done_stack ->

    state: resolved • deferred.reject() -> deferred.fail_stack -> state: rejected
  52. Deferred: function( func ) { var tuples = [ //

    action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments );
  53. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ],
  54. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為
  55. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設
  56. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態
  57. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態 Callback list
  58. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態 0 Callback list
  59. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態 0 1 Callback list
  60. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態 0 1 2 Callback list
  61. var tuples = [ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved"

    ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], ⾏行為 假設 狀態 0 1 2 Callback list 3
  62. 想⼀一想 • Deferred 會建構⼀一個全新並獨⽴立的 callbacks list • 三個⾏行為定義的 function 幾乎⼀一樣

    • Callbacks list 提供了所有的必要⽅方法, 包含 add, remove, fire, disable
  63. 想⼀一想 • Deferred 會建構⼀一個全新並獨⽴立的 callbacks list • 三個⾏行為定義的 function 幾乎⼀一樣

    • Callbacks list 提供了所有的必要⽅方法, 包含 add, remove, fire, disable • Resolve 跟 Reject 是相對的
  64. 想⼀一想 • Deferred 會建構⼀一個全新並獨⽴立的 callbacks list • 三個⾏行為定義的 function 幾乎⼀一樣

    • Callbacks list 提供了所有的必要⽅方法, 包含 add, remove, fire, disable • Resolve 跟 Reject 是相對的 • resolve() 與 reject() 其中⼀一⽅方作動, 另⼀一個的 callbacks 則觸發 .disabled()
  65. deferred = {}; // Add list-specific methods jQuery.each( tuples, function(

    i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 3 ]; // promise[ done | fail | progress ] = list.add promise[ tuple[1] ] = list.add; // Handle state if ( stateString ) { list.add(function() { // state = [ resolved | rejected ] state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); } // deferred[ resolve | reject | notify ] deferred[ tuple[0] ] = function() { deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); return this; }; deferred[ tuple[0] + "With" ] = list.fireWith;
  66. list.add(function() { // state = [ resolved | rejected ]

    state = stateString; // [ reject_list | resolve_list ].disable; progress_list.lock }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); } // deferred[ resolve | reject | notify ] deferred[ tuple[0] ] = function() { deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); return this; }; deferred[ tuple[0] + "With" ] = list.fireWith; }); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; },
  67. Deferred: function( func ) { var tuples = [ //

    action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, then: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) {
  68. then: function( /* fnDone, fnFail, fnProgress */ ) { var

    fns = arguments; return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise() .done( newDefer.resolve ) .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }); }); fns = null; }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise;
  69. .fail( newDefer.reject ) .progress( newDefer.notify ); } else { newDefer[

    tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }); }); fns = null; }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {};
  70. $.Deferred(function(dfd){ dfd .done(function(){ $('#foo').fadeIn(); }) .done(function(){ $('#bar').fadeOut(); }); }).resolved(); $.Deferred(function(dfd){

    dfd .then(function(){ $('#foo').fadeIn(); }) .then(function(){ $('#bar').fadeOut(); }); }).resolved();
  71. done • deferred 對象始終是同⼀一個 • done 定義的 callback 都在同⼀一個 stack

    list • resolve 之後 callbacks 是近乎同時呼叫 • 不應該預期 callbacks 會照順序執⾏行
  72. then • 每次都會建構⼀一個新的 deferred 物件 • then 定義的 callback 都是在⼀一個獨⽴立的

    callbacks list • 順序呼叫多次不同 deferred 物件的 resolved • 預期是循序執⾏行