question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Eliminate use of isPlainObject and deprecate the method

See original GitHub issue

Description

The isPlainObject function is complicated and unnecessary (certainly internally). I count five cases where it is used in the core. Here is one example:

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {

		// Shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		// The url can be an options object (which then must have .url)
		return jQuery.ajax( jQuery.extend( {
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		}, jQuery.isPlainObject( url ) && url ) );
	};
} );

This sort of faux “overloading” is generally ill-advised in ECMAScript. The above code should make that clear as it is relatively inefficient and hard to follow. We won’t clean it up entirely here as can’t break compatibility (and one thing at a time).

What do we have in this case? A string or an Object object. If it is an object, then it is “mixed in” to the object created to house the various options. Obviously this is a strange interface as we could pass method, callback and type twice (once as the named arguments and again in the url object).

How about this instead?

jQuery.each( [ "get", "post" ], function( i, method ) {
    jQuery[ method ] = function( url, data, callback, type ) {

        // Shift arguments if data argument was omitted
        
        if ( jQuery.isFunction( data ) ) {
            type = type || callback;
            callback = data;
            data = undefined;
        }
        
        var options = {
            type: method,
            dataType: type,
            data: data,
            success: callback
        };
        
        // If url is a string...
        
        if (typeof url == 'string') {
            options.url = url;
        } else {
            
            // If not a string, the only other *allowed* possibility is an Object object
            // Calls with anything *other* than a string or an Object object for url
            // will have undefined behavior.
            // The object must have a "url" property, otherwise behavior is also undefined
            
            jQuery.extend(options, url);
        }
        
        return jQuery.ajax( options );
    };
} );

And let’s get rid of that isFunction call as well as that method should get the same treatment in another issue. Just foreshadowing here:

jQuery.each( [ "get", "post" ], function( i, method ) {
    jQuery[ method ] = function( url, data, callback, type ) {

        // Shift arguments if data argument was omitted
        
        if ( typeof data == 'function' ) {
            type = type || callback;
            callback = data;
            data = undefined;
        }
        
        var options = {
            type: method,
            dataType: type,
            data: data,
            success: callback
        };
        
        // If url is a string...
        
        if (typeof url == 'string') {
            options.url = url;
        } else {
            
            // If not a string, the only other *allowed* possibility is an Object object
            // Calls with anything *other* than a string or an Object object for url
            // will have undefined behavior.
            // The object must have a "url" property, otherwise the behavior is also undefined
            
            jQuery.extend(options, url);
        }
        
        return jQuery.ajax( options );
    };
} );

Better, right? Faster and much easier to follow. There’s enough going on in that function to start without adding calls to other odd functions.

It’s important to understand why isFunction isn’t needed here (or anywhere most likely). It’s a callback, which must be a Function object. Not any old callable object (e.g. host objects), but an object constructed by Function. All such Function objects have the same typeof result in all browsers: 'function'.

That’s one down and four to go. Not going to bother with the rest until there is some indication of agreement that this strategy is sound. It will be much the same deal for the rest of the calls to this method and then it can be deprecated (shouldn’t encourage developers to use such functions either). In the bigger picture, there is a lot more in the core that can be made easier to follow through similar restructuring.

Link to test case

No test case. Behavior is to remain the same, so existing test cases are appropriate.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:17 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
david-markcommented, Dec 5, 2016

Ignoring the second part (which is a logically separate issue), I’m fine with switching this type check from object to string, provided it doesn’t increase the gzipped size of jquery.min.js.

Thanks, but we’re not yet done here.

Here’s the thing “Brain”: that was a sample of 5 required changes before isPlainObject is deprecated and eventually deleted, which will easily make up for any additions incurred.

The idea of checking every commit to see whether it added a few bytes to the file size is just wrong. It’s premature… something. Call it premature counting of bytes. It’s like checking the gas level to the ounce every time you hit a stop light.

Leave it until a trend develops, which will most likely be a downward one for my fork (there’s plenty of fat to trim in this project). But what if over time it increased it 1K? Or 2K? There are other factors to weigh against size increases (e.g. code clarity, performance).

Anyway, on to round two…

0reactions
david-markcommented, Dec 10, 2016

Re-did extend and without splitting it in two. Turns out that wouldn’t have made much difference.

https://github.com/jquery/jquery/issues/3444

As expected, didn’t need near the amount of code in isPlainObject for extend. The needed bits (slightly rewritten for clarity and speed) are now “private” to extend. The jQuery.isPlainObject method is now deprecated.

isPlainObject: function( obj ) {

	/* eslint-disable no-undef */
	// This function is conditionally created, depending on whether console.warn exists
	// NOTE: This is "scaffolding" code that can be removed in production builds
        if ( deprecated ) {
	       deprecrated( "isPlainObject" );
        }

The deprecated function, which will be used in other soon to follow methods:

/* eslint-disable no-unused-vars */
// Conditionally created function is used inside functions when it exists
var deprecated = null;

if ( typeof window.console != "undefined" && typeof window.console.warn != "undefined" ) {
	deprecated = jQuery.deprecated = function( feature, alternative ) {
		alternative = alternative || "Please find another way.";
		window.console.warn( feature + " is deprecated! " + alternative );
	};
}

Though there used to be a similar function, but could only find code like this in Deferred exception handling. Should likely restructure so that all of the code can share a common warn function.

And that’s that. As mentioned in the new extend rehab issue, the lessons learned during the investigation and adjustment of the affected code is indicative of the sort of work that is required throughout this project (if it is to get off the treadmill of nagging bug fixes). See this as the only way to regain and maintain any semblance of relevancy.

There are other problems unrelated to this theme (e.g. multi-browser builds), but it would be pointless to get into them until these basic issues are addressed.

Strongly advise reopening this ticket. Even if we buy into none of the above, there are clear mistakes and inconsistencies that have been uncovered in previous comments. Read it carefully from the top and they should be apparent.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Deprecated and obsolete features - JavaScript - MDN Web Docs
Acquiring source text. The toSource() methods of arrays, numbers, strings, etc. and the uneval() global function are obsolete. Use ...
Read more >
Legacy jQuery.sap Replacement - Documentation - Demo Kit
Old API Call New Module Replacement Type jQuery.sap.assert sap/base/assert Simple replacement jQuery.sap.resources sap/base/i18n/ResourceBundle Method changed jQuery.sap.log sap/base/Log Simple replacement
Read more >
jQuery.isPlainObject() | jQuery API Documentation
The object that will be checked to see if it's a plain object. Note: Host objects (or objects used by browser host environments...
Read more >
is-plain-object - Product Documentation
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,...
Read more >
Lodash _.isPlainObject() Method - GeeksforGeeks
value: It is the function that is used to check the value. Return Value: This method returns true if value is a plain...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found