diff --git a/src/core.js b/src/core.js index b882f9b4..d3641365 100644 --- a/src/core.js +++ b/src/core.js @@ -744,31 +744,52 @@ jQuery.extend({ // A global GUID counter for objects guid: 1, - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy; - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; + // XXX BACKCOMPAT: Support old string method. + if ( typeof context === "string" ) { + fn = fn[ context ]; + context = arguments[0]; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( ! jQuery.isFunction( fn ) ) { + return undefined; + } + + if ( jQuery.support.nativeBind ) { + // Native bind + args = slice.call( arguments, 1 ); + if ( args.length ) { + proxy = Function.prototype.bind.apply( fn, args ); + } else { + proxy = fn.bind( context ); + } + } else { + // Simulated bind + args = slice.call( arguments, 2 ); + if ( args.length ) { + proxy = function() { + return arguments.length ? + fn.apply( context, args.concat( slice.call( arguments ) ) ) : + fn.apply( context, args ); + }; + } else { + proxy = function() { + return arguments.length ? + fn.apply( context, arguments ) : + fn.call( context ); + }; } } - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - // So proxy can be declared as an argument return proxy; }, diff --git a/src/event.js b/src/event.js index a4f02a7a..ac0da6b7 100644 --- a/src/event.js +++ b/src/event.js @@ -894,6 +894,8 @@ if ( !jQuery.support.focusinBubbles ) { jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -907,10 +909,15 @@ jQuery.each(["bind", "one"], function( i, name ) { data = undefined; } - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); @@ -974,24 +981,27 @@ jQuery.fn.extend({ toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, - i = 1; + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); + args[ i++ ].guid = guid; } - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); + return this.click( toggler ); }, hover: function( fnOver, fnOut ) { diff --git a/src/support.js b/src/support.js index 10696aab..2d7bc7ca 100644 --- a/src/support.js +++ b/src/support.js @@ -75,6 +75,10 @@ jQuery.support = (function() { // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", + // Test for presence of native Function#bind. + // Not in: >= Chrome 6, >= FireFox 3, Safari 5?, IE 9?, Opera 11? + nativeBind: jQuery.isFunction( Function.prototype.bind ), + // Will be defined later submitBubbles: true, changeBubbles: true, diff --git a/test/unit/core.js b/test/unit/core.js index ed1fb591..c2a23b1a 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -903,7 +903,7 @@ test("jQuery.isEmptyObject", function(){ }); test("jQuery.proxy", function(){ - expect(4); + expect(6); var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; var thisObject = { foo: "bar", method: test }; @@ -917,8 +917,17 @@ test("jQuery.proxy", function(){ // Make sure it doesn't freak out equals( jQuery.proxy( null, thisObject ), undefined, "Make sure no function was returned." ); - // Use the string shortcut - jQuery.proxy( thisObject, "method" )(); + // Partial application + var test2 = function( a ){ equals( a, "pre-applied", "Ensure arguments can be pre-applied." ); }; + jQuery.proxy( test2, null, "pre-applied" )(); + + // Partial application w/ normal arguments + var test3 = function( a, b ){ equals( b, "normal", "Ensure arguments can be pre-applied and passed as usual." ); }; + jQuery.proxy( test3, null, "pre-applied" )( "normal" ); + + // Test old syntax + var test4 = { meth: function( a ){ equals( a, "boom", "Ensure old syntax works." ); } }; + jQuery.proxy( test4, "meth" )( "boom" ); }); test("jQuery.parseJSON", function(){