2

As someone used to python and C++, having an = copy objects by reference rather than value is not intuitive at all. Not just that, but there seems to be no direct way of copying objects to begin with. JSON.parse(JSON.stringify) is the closest option (if I know correctly), and even that has problems.

a) In a language where all variables are anyway treated as objects, why does the = operator distinguish between primitive and non-primitive data types to decide whether to copy by value or reference?

b) Why is copy by value not possible for objects?

c) What techniques are helpful for a beginner used to copying objects by value to code without it?

  • you can use [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to copy js objects – Nijeesh Joshy Jan 09 '20 at 08:14
  • 4
    @NijeeshJoshy No, that only does a shallow copy – ghosts_in_the_code Jan 09 '20 at 08:17
  • In Java and C++ you have to write a copy constructor. – SPlatten Jan 09 '20 at 08:30
  • Some languages have a mechanism for deep copy but they are almost all "some assembly required". Very few provide a truly simple way of doing a deep copy. JavaScript is just one of a few that doesn't provide a *standard* way of doing it. In general there aren't many scenarios where a deep copy is useful in a front-end context, which is what JavaScript was originally designed for – Dan Jan 09 '20 at 09:28
  • `=` does **not** distinguish between primitives and non-primitives and it’s **always** *pass by value*. It’s only that for non-primitives, the *value* IS a reference. So even though the reference itself gets copied, the same existing object is the one being referenced. – Lennholm Jan 09 '20 at 10:51
  • 1
    A non-primitive is an object that the author arbitrarily describes and the language has no way of ’understanding’ its design logically. This means that because of potential problems with things like circular referencing, references to singletons, etc, the author will also have to be the one to define any deep copy functionality. This is true for all languages that allow data structures of arbitrary design. – Lennholm Jan 09 '20 at 10:53
  • @Lenholm Thanks, that make sense. Feel free to post an answer, I'll accept it – ghosts_in_the_code Jan 09 '20 at 11:12

2 Answers2

4

a) In a language where all variables are anyway treated as objects, why does the = operator distinguish [...] ?

The =(assign) operator does not distinguish between primitive and non primitive data types. It kinda does the same for both, considering that equality is preserved after assignment (excluding exceptions e.g. NaN, ...).

b) Why is copy by value not possible for objects?

Wrong assumption in a) leads to this. Assignment is no copy and a copy of an object does not preserve equality.

Or think about: var obj = {a: {b: 1}} .

What is the value of obj.a ? It is just the reference to {b:1}.

c) What techniques are helpful for a beginner used to copying objects by value to code without it?

There are many approaches for this. And two trivial cases.

As the first case one knows the layout of the object. Thus creates a template or constructor and passes all values into the corresponding properties.

As the second case one assumes a cyclic object containing everything possible in javascript (functions, regexp, symbols, undefined, ...) of depth n and builds something (not json.stringify).

For starters: possible duplicate


Assumptions:

primitive and non primitive data types have default getter,setter, ...

Estradiaz
  • 3,483
  • 1
  • 11
  • 22
0

I guess it's because of the specific nature of JS. When you create an object like this:

let obj = {a: 3, b: 5}

And try to pass this object to another variable like this:

let obj2 = obj

You will still have only 1 object, but with 2 references, so if you try to modify obj.a it will also affect obj2.a.

That's why I created a way around for myself, which may not be the best, but here is it:

//A little helper
Object.isObject = function (object) {
    return object !== null && object instanceof Object && !Array.isArray(object)
}

/*
* Array and Object copy work with each other, so every nested array are 
* copied properly
*/

/*
* ARRAY
*/
Object.defineProperty(Array.prototype, 'copy', {
    value: function (array){

        if(!(Array.isArray(array))) throw new TypeError('passed value should be an instance of array')

        if(array.length <= 0) {
            console.warn('WARNING: Found nothing to copy')
            return this
        }

        this.splice(0, this.length)
        for(let i = 0; i < array.length; i++) {
            if (Array.isArray(array[i])) this[i] = Array().copy(array[i])
            else if (Object.isObject(array[i])) this[i] = Object().copy(array[i])
            else this[i] = array[i]
        }

        return this
    },
    enumerable: false
})

/*
* OBJECT
*/
Object.defineProperty(Object.prototype, 'copy', {
    value: function (object){
        if(!object || !(Object.isObject(object))) return false

        if(Object.entries(object) <= 0) {
            console.warn('WARNING: Found nothing to copy')
            return this
        }

        const props = Object.getOwnPropertyNames(this)
        for (let i = 0; i < props.length; i++) delete this[props[i]]

        const keys      = Object.keys(object)
        const values    = Object.values(object)
        for (let i = 0; i < keys.length; i++) {
            if(Array.isArray(values[i])) this[keys[i]] = Array().copy(values[i])
            else if(Object.isObject([values[i]])) this[keys[i]] = Object().copy(values[i])
            else this[keys[i]] = values[i]
        }

        return this
    },
    enumerable: false
})

//create 2 arrays
let a = [3, 5, {a: 5}, [3, 1]]
let b = []

//copy array of a
b.copy(a)

//modify values
b[0] = 6
b[2].a = 1
b[3][0] = 'test'


console.log(a) //source
console.log(b)

As you can see in the example these 2 arrays (a and b) are completely different and have no relation to each other.

P.S. Sorry if I wrote something wrong, my english is not that good :O