Skip to main content

Bug Tracker

Side navigation

#10867 closed enhancement (wontfix)

Opened November 22, 2011 09:32PM UTC

Closed December 06, 2011 06:56PM UTC

Last modified October 15, 2012 10:04PM UTC

jQuery.extend replaces the target object when deep extend is explicitly false

Reported by: gibson042 Owned by: gibson042
Priority: low Milestone: None
Component: core Version: 1.7.1
Keywords: Cc:
Blocked by: Blocking:

jQuery.extend has an optional leading parameter that toggles deep (recursive) merging. But when it is set to false, the function creates, extends, and returns a new object instead of the specified target.

Problematic call: jQuery.extend(false, objTarget, objExtension, ...)

Attachments (0)
Change History (13)

Changed November 22, 2011 09:42PM UTC by gibson042 comment:1

Changed November 23, 2011 01:24AM UTC by rwaldron comment:2

owner: → gibson042
status: newpending

Where does it say that passing false should have this behavior?

Changed November 23, 2011 04:32AM UTC by gibson042 comment:3

status: pendingnew

It's implied from both documentation and code. specifies the jQuery.extend( [deep], target, object1 [, objectN] ) signature:

  • **deep** If true, the merge becomes recursive (aka. deep copy).
  • **target** The object to extend. It will receive the new properties.

And further specifies:

Keep in mind that the target object (first argument) will be modified, and will also be returned from $.extend().

Since there is a change in behavior *if* **deep** is true, it should be possible to specify it non-true, meaning that a jQuery.extend(false, deep3, deep4) call has a **target** of deep3. And since documentation specifies that the target object is modified and returned, it should further be true that jQuery.extend(false, deep3, deep4) === deep3.

I also call your attention to line 311, which tests if ( typeof target === "boolean" ); rather pointless after target = arguments[0] || {} since it might as well be if ( target === true ).

All that aside, the change makes jQuery.extend more consistent, smaller, and slightly more useful (since without it, jQuery.extend(false, deep3, deep4) is equivalent to jQuery.extend({}, deep3, deep4)).

Changed November 23, 2011 05:05PM UTC by rwaldron comment:4

component: unfiledcore
priority: undecidedlow
resolution: → duplicate
status: newclosed

Sorry, but we'e already been down this road. A quick ticket search for "extend false" brings up the original

Changed November 23, 2011 05:05PM UTC by rwaldron comment:5

Duplicate of #9994.

Changed November 23, 2011 05:22PM UTC by gibson042 comment:6

With all due respect, the tickets are distinct.

#9994 was asking for the replacement of if ( typeof target === "boolean" ) with if ( target === true ).

I am asking for the replacement of target = arguments[0] || {} with target = arguments[0], to support dmethvin's $.extend(iCanHazDeep, defaults, options) example from comment 3.

Changed November 23, 2011 05:23PM UTC by rwaldron comment:7

For clarification...

"duplicate in that they both seek to arbitrarily change jQuery.extend() where no change is needed, with regard to the left most argument"

Changed November 23, 2011 06:19PM UTC by timmywil comment:8

keywords: → 1.8-discuss
resolution: duplicate
status: closedreopened

rwaldron and I have discussed this and we're reopening for further discussion.

Granted passing false is not documented, but I'm of the opinion that it seems a fair assumption that false can be passed if true can be passed, especially given the possibility of a boolean var being passed. I'd rather have it do nothing special when false is passed than create a new object. In other words, these should not be equivalent, but they currently are:

$.extend(false, obj, otherObj);
$.extend({}, obj, otherObj);

It should not have an affect on the original source object that is returned.

Besides, the pull has no performance (actually a micro-optimization) or size issues so that's mostly why I'm fine with it.

Changed November 23, 2011 06:20PM UTC by timmywil comment:9

status: reopenedopen
type: bugenhancement

Changed November 25, 2011 12:02PM UTC by Erik_A comment:10

A few days before, I also ran into this issue and fixed it myself.

I replaced the declaration of target, i and depth variable declaration with the following:

deep = arguments[0] === true,
i = 1 & (deep === arguments[0]),
target = arguments[i++] || {};

What's happening in my code is the following: first the deep argument is assigned and set to true if only the first argument is actually a boolean and equals true.

The variable i is set to 1 and bitwise compared to whether or not, deep is of the same type of the fist argument. If the first argument is a boolean, then 1 & true becomes 1. (If arg0 is a object, deep === arg0 is false, thus is not a boolean. But if arg0 is false, deep === arg0 makes true).

Finally the target value is assigned: if arg0 is not a boolean, i === 0, else i === 1. After setting the target is set, i is incremented.

The deep, i and target variables containes now correct values and makes the check of target redundant. The following should be removed:

// Handle a deep copy situation
if (typeof target === "boolean") {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;

Changed December 06, 2011 06:56PM UTC by dmethvin comment:11

keywords: 1.8-discussneedsdocs
resolution: → wontfix
status: openclosed

Changed February 28, 2012 05:41PM UTC by timmywil comment:12

#11411 is a duplicate of this ticket.

Changed October 15, 2012 10:04PM UTC by mikesherov comment:13

keywords: needsdocs