Search code examples
javascriptjqueryarrayscloneextend

Methods of Array.prototype appear as keys of array after deep cloning with jQuery.extend


Lets say the Array prototype has been augmented with some helper function:

Array.prototype.doSomething = function() { ... }

When I log out a simple array into the console ...

console.dir([1,2,3]);

... I get:

Array [3]
    0: 1
    1: 2
    2: 3
    length: 3
    __proto__: Array[0]
       doSomething: function()

everything looks fine so far.

But as soon as I deep clone the array using $.extend (deep clone because my actual data is more complex - an object with arrays as properties, but it happens in any case) ...

$.extend(true, [], [1,2,3]);

I suddenly get:

Array [3]
    0: 1
    1: 2
    2: 3
    doSomething: function()     // ???
    length: 3
    __proto__: Array[0]
       doSomething: function() 

It looks like the prototype method has been added as an actual item of the array instance.

Does JQuery.extend not test for hasOwnProperty() before copying, or am I doing something wrong here?


Solution

  • Does JQuery.extend not test for hasOwnProperty() before copying

    No it does not. From the source it uses a for ... in loop to iterate over the properties of whatever is being cloned, but does not do a hasOwnProperty check before setting the properties.

    https://github.com/jquery/jquery/blob/7103d8ef47e04a4cf373abee0e8bfa9062fd616f/src/core.js#L120

    jQuery.extend = jQuery.fn.extend = function() {
      var options, name, src, copy, copyIsArray, clone,
          target = arguments[ 0 ] || {},
          i = 1,
          length = arguments.length,
          deep = false;
    
      // Handle a deep copy situation
      if ( typeof target === "boolean" ) {
          deep = target;
    
          // Skip the boolean and the target
          target = arguments[ i ] || {};
          i++;
      }
    
      // Handle case when target is a string or something (possible in deep copy)
      if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
          target = {};
      }
    
      // Extend jQuery itself if only one argument is passed
      if ( i === length ) {
          target = this;
          i--;
      }
    
      for ( ; i < length; i++ ) {
    
          // Only deal with non-null/undefined values
          if ( ( options = arguments[ i ] ) != null ) {
    
              // Extend the base object
              for ( name in options ) {
                  src = target[ name ];
                  copy = options[ name ];
    
                  // Prevent never-ending loop
                  if ( target === copy ) {
                      continue;
                  }
    
                  // Recurse if we're merging plain objects or arrays
                  if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                      ( copyIsArray = jQuery.isArray( copy ) ) ) ) {
    
                      if ( copyIsArray ) {
                          copyIsArray = false;
                          clone = src && jQuery.isArray( src ) ? src : [];
    
                      } else {
                          clone = src && jQuery.isPlainObject( src ) ? src : {};
                      }
    
                      // Never move original objects, clone them
                      target[ name ] = jQuery.extend( deep, clone, copy );
    
                  // Don't bring in undefined values
                  } else if ( copy !== undefined ) {
                      target[ name ] = copy;
                  }
              }
          }
      }
    
      // Return the modified object
      return target;
    };
    

    Does it effect your array data? Depending on how you use the array probably not. Iterating over the array elements would still be the same, as long as you use the correct loop procedures. Meaning for(;;), for ... of, or .forEach. And doing JSON.stringify would still give you the correct JSON.