New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eliminate use of isPlainObject and deprecate the method #3430
Comments
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 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... |
Second one is easy as well. From the var prefix,
s = [],
add = function( key, valueOrFunction ) {
// If value is a function, invoke it and use its return value
var value = jQuery.isFunction( valueOrFunction ) ?
valueOrFunction() :
valueOrFunction;
s[ s.length ] = encodeURIComponent( key ) + "=" +
encodeURIComponent( value == null ? "" : value );
};
// If an array was passed in, assume that it is an array of form elements.
if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
// Serialize the form elements
jQuery.each( a, function() {
add( this.name, this.value );
} );
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for ( prefix in a ) {
buildParams( prefix, a[ prefix ], traditional, add );
}
}
// Return the resulting serialization
return s.join( "&" ); Clearly there are three possibilities for
#2 objects have Bad variable name in This line checks if if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { ...Something that is an Object not constructed by the base Object object constructor (call it a "fancy " object?) and that has a "truthy" How about this direct inference (and we'll get to if ( jQuery.isArray( a ) || a instanceof jQuery ) { Fair enough? We still make the assumption about the contents of the I know it will come up, so will head it off now. What about frames? The short answer is: what about them? If we have a frameset (unlikely) or an Wouldn't make any sense to pass a jQuery object constructed in one frame to In other words, it would take a perfect storm of incompetence on the part of the caller to break this. On the other hand, it's trivially easy to break the original indirect inference. Finally, let's dump the var prefix,
s = [],
add = function( key, valueOrFunction ) {
// If value is a function, invoke it and use its return value
var value = typeof valueOrFunction == 'function' ?
valueOrFunction() :
valueOrFunction;
s[ s.length ] = encodeURIComponent( key ) + "=" +
encodeURIComponent( value == null ? "" : value );
};
// If an array was passed in, assume that it is an array (or jQuery object) of form elements.
if ( jQuery.isArray( a ) || a instanceof jQuery ) {
// Serialize the form elements
jQuery.each( a, function() {
add( this.name, this.value );
} );
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for ( prefix in a ) {
buildParams( prefix, a[ prefix ], traditional, add );
}
}
// Return the resulting serialization
return s.join( "&" ); That leaves three to go: one in What I expect is that the two in Will look at the next one when I have time and after confirmation that we are all on the same page with these changes. |
|
Also, |
I feel quite sure that I won't require every line in that function to eliminate the call from Let me know on round 2... |
Did you read about the dueling scenarios? The existing one is trivial to break (however canonical you consider the test), the other would require such a bizarre turn of events (including more than just using separate instances) that it's not even worth considering. So should be clear which makes more sense. Furthermore, are we not allowed to pass non-array-like "fancy" objects with a "truthy" Regardless, we can make a more direct inference to determine if it is an array-like object containing form control elements (or simply an array-like object as we are clearly assuming what the contents will be). Or perhaps validate that it is a jQuery object with something like: a.jquery && !Object.hasOwnProperty.call(a, 'jquery') Note: Only calling Think it over and I'll check in tomorrow. Best of luck! PS. One other hint: if |
|
See my edited response above. Sorry, but I didn't notice the new reply in the interim. |
I found this line from
Clearly the canon is not consistent throughout. |
Three to go... Here is the snippet from // 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 ] );
}
}
}
return this;
// HANDLE: $(#id)
} else { And the line in play is: if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { This is the case where So what are we attempting to discern here? A clue is in a line above: context && context.nodeType ? context.ownerDocument || context Apparently
Now we have an idea of the sort of test we need and an example in this same function that indicates what seems to be an easy solution. We can eliminate #1 by simply asserting that if ( rsingleTag.test( match[ 1 ] ) && context && !context.nodeType) { And it is also consistent with the previous duck typing in this function. Furthermore, it allows "fancy" objects (i.e. constructed with something other than But never like to see boolean type conversion on host object properties as they have been known to throw exceptions in the past (and history is our only guide for such host object reactions), so this is more appropriate: if ( rsingleTag.test( match[ 1 ] ) && context && typeof context.nodeType != 'number' ) { And once again, let's knock out the unneeded // 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 && typeof context.nodeType == 'number' ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
if ( rsingleTag.test( match[ 1 ] ) && context && typeof context.nodeType != 'number' ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( typeof this[ match ] == 'function' ) {
this[ match ]( context[ match ] );
// ...and otherwise set properties or attributes (depending on DOM type)
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
// HANDLE: $(#id)
} else { Updated the comment about That leaves just |
Let's take a step back. What exactly are these changes intended to accomplish? Generally we revisit code when users report bugs or performance issues. Refactoring code that doesn't have problems can introduce compatibility problems and requires the time and attention of multiple people (you and team members). The best time to do this type of refactoring is when that region of code has other reported issues and the refactoring helps to resolve the issue (and in the process, makes the code better). We have a few dozen issues that would be a great starting point for changing code that would also fix real problems that users have reported. |
AFAIK, we have yet to take a step forward. I'm proposing several steps forward and we'll see where that takes us (or not).
As I've explained: clarity, performance and eliminating the crutch of isPlainObject, which will soon be followed by other is* functions as they just encourage poor design (as we've seen). I'm untangling the code as best I can while hemmed in to design decisions that date back a decade and will likely never be changed; regardless, am making good progress. Long term, expect to reduce the amount of code as well (and code size seems to be a major focus here).
I'd say that's led to the state that the code is in. How exactly do you define a "performance issue"? I'm looking at the code and seeing all sorts of unneeded function calls, which are expensive. Furthermore, the untangling process has identified inconsistencies (e.g. use of instanceof in one place and duck typing in another to try to sniff out a jQuery object), inefficiencies, redundancies and code that is harder to follow and less clear about what it is doing than it should be (which makes it harder to document).
Not sure how you define "problems", but it goes without saying that refactoring code can break compatibility and requires time and attention (which I'm providing). I consider obscure and inconsistent code to be a problem to document and harder to maintain than clear and consistent code. Every time you go to patch a bug, the chance of creating more bugs is tied to the clarity and consistency of the code. I also know from experience (as well as from reading the code) that jQuery is relatively slow in virtually everything it does (remember the JSPerf comparisons I posted comparing to My Library?) No coincidence that I know what to do to improve the performance, just as I did for Dojo several years back. Some of them balked too and I'd urge you not to follow in their footsteps as they certainly don't lead forward.
And good luck with those bug fixes. That's not what I'm doing here at the moment. Feel free to use or not use whatever you want; that's why I'm explaining everything in detail as I go. If you really feel you have a pat hand because nobody is complaining, I'd advise you to reevaluate that position. ;) Recall, that no users reported any real problems with UA sniffing. On the other hand, there was never any shortage of issues related to the Another such coincidence (related to SELECT elements) was pointed out years ago and is still in the code today. Sure the unit tests and users have yet to complain about that code either, though it may well have caused issues that were also dismissed as "edge cases", sun spots, problems with the phase of the moon, etc. I'll get to that one, or perhaps somebody else can have a go at it while I deal with the issue at hand. That problem and its solution have been discussed in public numerous times over the years. Ironically, the solution was dismissed because it may have added an extra line or two (sound familiar?) and Resig couldn't see any evidence that the extant code didn't "work" (i.e. no complaints). Problems in philosophy and design can't be patched (or fit neatly into tickets). Piling up patches without a long-term vision is exactly how code ends up convoluted and inconsistent (and that much harder to patch without creating new issues). Don't expect users to evaluate the clarity of the code or the performance either, though they may compare the latter to what they can get from similar libraries. The only way to stay ahead of the curve is to be proactive. But I digress; at the moment I'm dealing (mostly) with the Stay tuned... |
After inspecting I will spin off the recursive bit to make it simpler to follow. And yes, I am sure that I will break this issue into at least two as well. I don't care to mess with the method at all in its present state. Will be much simpler to see what I'm doing after splitting it. Once I do that, I'll wrap up the isPlainObject abatement and split up this issue. This one will likely be renamed to something like "Reduce dependency on isPlainObject" and the second will be the final nail that also restructures |
I see your point about the old nature of Besides, you may be right about the needs here, but we've discussed it and given that we have limited time to work on things–we're all volunteers after all–we've decided we'd like to focus on more important issues based on practical use cases. Thank you for your contributions. We'd love it if you'd like to pick an existing issue to help with. |
Didn't you write a blog post about five years after I first mentioned the It was a design problem and required adding a companion We have history repeating itself here. There may well have been reports of failures related to some of the issues I've kicked up, but they could have fallen through the cracks here or on StackOverflow (or on Usenet in years past). Why wait to get a coherent bug report from users when we can see the bugs right now? And if John had wanted to fix Have personally dealt with projects that were frozen in time supporting IE 7 or 8 at best, simply because they were using an old version of jQuery with the old
The point is that it should be deprecated (not removed any time soon) as there is no reason for us or the users to rely on such a crutch. And shouldn't increase the code size much (if at all) to address this concern (as well as the several related concerns uncovered in dealing with it here). If you think about it,
I never suggested ripping up the guts of it at all, simply deprecating it. It's the other methods that require some ripping up due to inconsistencies, redundancies and inefficiencies uncovered during the investigation of this issue. Though not impossible, it's unlikely you will get motivation to fix these issues due to bug reports. They are simply rare cases (or cause only performance issues). Motivation to fix such things needs to come from within. Your best motivation should be to keep the issues from multiplying. The easier the code is to follow, the easier it is to debug. Misunderstandings can lead to the creation of new bugs when dealing with reported issues. Should be particularly concerned about bugs that are flying under the radar of the unit tests. But also realize that this is not just about bugs, but about unclear code and silent failures. A call to
I don't understand why you need to close the issue to allocate your focus. Contend it should be reopened, at least until I finish with it. Then those who have time can chime in.
I never suggested changing one line in
Thanks and that could happen, but as I am also a volunteer, I will work on what I am most motivated to do at any given time. Right now, I am motivated to wrap this issue up and paste the code into my fork. Whether it ever gets merged is irrelevant to me. May also add additional issues springing from this one (e.g. breaking up Best of luck! |
Re-did As expected, didn't need near the amount of code in 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 /* 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 And that's that. As mentioned in the new 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. |
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: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 passmethod
,callback
andtype
twice (once as the named arguments and again in theurl
object).How about this instead?
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: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 aFunction
object. Not any old callable object (e.g. host objects), but an object constructed byFunction
. All suchFunction
objects have the sametypeof
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.
The text was updated successfully, but these errors were encountered: