1

I have a javascript file (lets call it newconfig.js) that is including a module (of type Object) through a require() action in a config.js file:

Consider core.js to be:

module.exports = {
    configuration: {
        showLanguageSelector: false
    },
    tableStructure: {
        columns: [
        {
            tooltip: 'Indicates if this load has alerts or notes',
            name: 'Alerts <em>& Notes</em>'
        },
        {
            tooltip: 'Trailer number and trailer type',
            name: 'Trailer <em>Type</em>'
        },
        {
            tooltip: 'Door number',
            name: 'Door'
        },
        {
            tooltip: 'Trailer opened date/time',
            name: 'Open<span>ed</span>'
        },
        {
            tooltip: 'Trailer closed date/time',
            name: 'Closed'
        }
        ]
     }     
 };

My newconfig.js file contains:

const core = require('./core/config');

I then clone the instance of core in my file:

let config = Object.assign({}, core);

I then mutate my local object

config.Configuration = {
    showLanguageSelector: true
};

config.tableStructure.columns = [
    {
        tooltip: 'Indicates if this load has alerts or notes',
        name: 'Alerts <em>& Notes</em>',
    }, {
        tooltip: 'Trailer number and trailer type',
        name: 'Trailer <em>Type</em>',
    }
];

so that I can export this as another config that extends the core configuration:

module.exports = config;

When an external file attempts to include the ./core/config file locally to use, it has the changes of newconfig.js

IE (mylayout.js):

const core = require('./core/config');
console.log(core);

the core value when outputted is:

    {
Configuration: {
            showLanguageSelector: false // interesting how this wasn't mutated!!!!!
        },
        tableStructure {
columns: [
            {
                tooltip: 'Indicates if this load has alerts or notes',
                name: 'Alerts <em>& Notes</em>',
            }, {
                tooltip: 'Trailer number and trailer type',
                name: 'Trailer <em>Type</em>',
            }
        ]
      }
}

Where am I going wrong that is causing my original core config to get mutated, if I am cloning the object to a new object before changing it, and exporting that new object?

requiring my newconfig.js in another js file returns the desired behavior:

{
    Configuration: {
            showLanguageSelector: true
    },  
    tableStructure {
       columns: [
            {
                tooltip: 'Indicates if this load has alerts or notes',
                name: 'Alerts <em>& Notes</em>',
            }, {
                tooltip: 'Trailer number and trailer type',
                name: 'Trailer <em>Type</em>',
            }
        ]
      }
}
Chris West
  • 55
  • 3
  • 1
    Object.assign makes shallow copies. You need to do a deep-copy (recurse into sub-objects and arrays). – CollinD Oct 24 '18 at 14:38
  • I would typically use the approach: let cloned = JSON.parse(JSON.stringify(core)); .. but that will unbind any functions that I have as items in the object – Chris West Oct 24 '18 at 14:41
  • @ChrisWest - Making a pass through text is usually not the best approach. – T.J. Crowder Oct 24 '18 at 14:43

2 Answers2

2

I then clone the instance of core in my file:

let config = Object.assign({}, core);

That's just a shallow clone. So the configuration and tableStructure properties on the clone still refer to the original properties:

const original = {
  configuration: {
    foo: "bar"
  }
};
const clone = Object.assign({}, original);
console.log(original.configuration === clone.configuration); // true

The code above just creates this structure:

                      +−−−−−−−−−−−−−−−−−−−−−−−−+
original:Ref74132−−−−>|        (object)        |
                      +−−−−−−−−−−−−−−−−−−−−−−−−+  
                      | configuration:Ref33562 |−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−+  |
                                                  |
                                                  |  +−−−−−−−−−−−−+
                                                  +−>|  (Object)  |
                                                  |  +−−−−−−−−−−−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−+  |  | foo: "bar" |
clone:Ref85432−−−−−−−>|        (object)        |  |  +−−−−−−−−−−−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−+  |
                      | configuration:Ref33562 |−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−+

Instead, you need to deep clone the object.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • understood, but using a non-jQuery deep clone approach such as : JSON.parse(JSON.stringify(obj)) .. will unbind the functions I have in my object. Is there an approach to keep any functions in the object literal intact during a deep clone? – Chris West Oct 24 '18 at 14:45
  • @ChrisWest - See the linked question, which provides several answers with deep clone functions, including non-jQuery-based ones. It's a complex topic with no one-size-fits-all solution. (Note: You're not keeping the *object literal* intact; you're keeping the *object* intact. An object literal is a source code concept; the *result* of parsing one is an object.) – T.J. Crowder Oct 24 '18 at 14:48
0

Instead of deep cloning the whole original object you may just want to clone the parts that needs changing:

let config = Object.assign({}, core,{
  Configuration : {
    showLanguageSelector: true
  },
  tableStructure: Object.assign({},core.tableStructure,{
    columns = [
      {
          tooltip: 'Indicates if this load has alerts or notes',
          name: 'Alerts <em>& Notes</em>',
      }, {
          tooltip: 'Trailer number and trailer type',
          name: 'Trailer <em>Type</em>',
      }
    ]
  })
});
HMR
  • 37,593
  • 24
  • 91
  • 160