Object.assign is not a deep clone
The JsPlumb API, as with most Javascript APIs, makes extensive use of Javascript objects for configuration. A good practice to follow with inputs from the user is to make an internal copy, so as to be sure that your library is not introducing any unexpected side effects. Keep in mind that Javascript objects are passed by reference, ie:
function doSomething(obj) {
obj.title = "A new value."
}
const myObject = {
title:"Initial title"
}
doSomething(myObject)
console.log(myObject.title)
=> "A new value."
...this is why we want to make a copy of any object we are given, because we don't know where else the user is using this JS object it passed in.
function doSomething(obj) {
obj = Object.assign({}, obj)
obj.title = "A new value."
}
const myObject = {
title:"Initial title"
}
doSomething(myObject)
console.log(myObject.title)
=> "Initial title"
Obviously this example is a little contrived, but hopefully you get the point. By using Object.assign({}, obj) to create a copy of the object we were given, we've now insulated ourselves from userland...or have we? What about if I pass in a more complex object:
function doSomething(obj) {
obj = Object.assign({}, obj)
obj.child.title = "A new value."
}
const myObject = {
title:"Initial title",
child:{
title:"Initial child title"
}
}
doSomething(myObject)
console.log(myObject.child.title)
=> "A new value."
Aur naur! What's happened here? The problem is that Object.assign is not a deep clone.
Cloning an object
What you should do instead of using Object.assign is to clone the object. Most browsers have offered a structuredClone method on the window since about 2022, so an approach like this is widely supported:
function doSomething(obj) {
obj = structuredClone(obj)
obj.child.title = "A new value."
}
const myObject = {
title:"Initial title",
child:{
title:"Initial child title"
}
}
doSomething(myObject)
console.log(myObject.child.title)
=> "Initial child title"
core-js Polyfill
If the fact that it's not guaranteed that your users have a browser supporting structuredClone is a concern, you could use a polyfill from core-js.
Clone example code
If you don't want to use a polyfill from core-js, feel free to grab this code instead:
function clone(a) {
if (a == null) {
return null
} else if (typeof a === "string") {
// string
return "" + a
} else if (typeof a === "boolean") {
// boolean
return !!a
} else if (Object.prototype.toString.call(a) === "[object Date]") {
// Date
return new Date(a.getTime())
} else if (Object.prototype.toString.call(a) === "[object Function]") {
// Function (not cloned; returned as-is)
return a
} else if (Array.isArray(a)) {
// Array - create new array and clone entries from existing array
return a.map(e => clone(e))
} else if (Object.prototype.toString.call(a).match(/\[object .*Element]/) != null) {
// DOM Element (not cloned; returned as-is)
return a
} else if (Object.prototype.toString.call(a) === "[object Text]") {
// DOM Text Node (not cloned; returned as-is)
return a
} else if (Object.prototype.toString.call(a) === "[object Object]") {
// JS Object
const c = {}
for (let j in a) {
c[j] = clone(a[j])
}
return c
}
else {
return a
}
}
Read more
- Read about
structuredCloneon MDN