Nearly everything in JavaScript is an object.

Null , undefined , strings, numbers, boolean, and symbols are not. Arrays are considered objects!

All objects in javascript are passed by reference. When you set a variable to an object using the equals sign, you are passing that object by reference. For example, obj is passed by reference to copyObj:

let obj = {'animal':'dog', 'age':2}
let copyObj = obj

Passing by reference simply means that the copy doesn't hold the same values in a different memory location, the copy has a pointer that points to the original memory location. So when you update copyObj, you also automatically update obj.

Everything in Javascript is an object, even arrays.

let arr = [1,2,3]
console.log("typeof arr:", typeof arr)
console.log("arr:", arr)

Output:

typeof arr: object
arr: [ 1, 2, 3 ]

So whatever you do to the copy of an array you do to the original array because all arrays are objects and all objects are passed by reference.

let arr = [1,2,3]
let copyArr = arr
copyArr.push('A')
console.log("arr:", arr)
console.log("copyArr:", copyArr)

Output:

// What was done to copyArr shows up in arr
arr: [ 1, 2, 3, 'A' ]
copyArr: [ 1, 2, 3, 'A' ]

If you are not making a copy by reference, you are making a copy by value. When making a copy by value, you can make changes to the copy without changing the original.

There are a few ways to make a copy by value. One is to use the spread operator (three dots and looks like an ellipsis). See below:

let arr = []
arr.push(1,2,3)
// Copy by value with the spread operator:
let copyArr = [...arr]
copyArr.push('A')
console.log("arr:", arr)
console.log("copyArr:", copyArr)

Output:

arr: [ 1, 2, 3 ]
copyArr: [ 1, 2, 3, 'A' ]

You can copy an array by value and at the same time convert it into an object if you wrap the array object inside of curly brackets. When copying by value, the original does not get updated when the copy is updated:

let arr = []
arr.push(1,2,3)
// Copy by value AND convert it to an object at the same time:
let obj = {...arr}
obj[4] = 4
obj.test = 5
console.log("arr:", arr)
console.log("obj:", obj)

Output:

arr: [ 1, 2, 3 ]
obj: { '0': 1, '1': 2, '2': 3, '4':4, test:5 }

Note, when setting a numeric property to the object, you have to use square brackets as if it was an associative array. (See objCopyArr[4] above). When setting a property that is a string, you can use dot notation. (Set objCopyArr.test above).

If you treat the newly copied object like an array, you'll get an error:

let arr = []
arr.push(1,2,3)
let obj = {...arr}
obj.push('A')

Output:

obj.push is not a function

You cannot convert an object to an array using the spread operator:

let realObj = {a:1,b:2,c:3}
let objArr = [...realObj]
console.log("objArr:", objArr)

Output:

object is not iterable (cannot read property Symbol(Symbol.iterator))

Basically, if you can't use a for-of loop on a variable, it isn't iterable and creates the above error.

If you don't use the spread operator, there are some ways to convert an object to an array.

Another way to make a clone of an object is to use Object.assign. It's basically the same as using the spread operator, but has the ability to mutate the object it is operating on. More on mutations and Object.assign.

Neither Object.assign nor the spread operator make deep copies. 

You can see a change made to the copy nestedCopyObj updates the original nestedObj:

let nestedObj = {
  dog: {
    breed:'Collie'
  }
}

let nestedCopyObj = {...nestedObj}
console.log("nestedObj.dog.breed before:", nestedObj.dog.breed)
nestedCopyObj.dog.breed = "Golden"
console.log("nestedObj.dog.breed after:", nestedObj.dog.breed)

Output:

nestedObj.dog.breed before: Collie
nestedObj.dog.breed after: Golden

A third way to make a copy of an object that can also copy by value is to use the JSON object. Convert an object to a string and then parse that string back into an object.

let nestedObj = {
  dog: {
    breed:'Collie'
  },
  getDogBreed() {
    return this.dog.breed
  }
}

console.log("Original nestedObj():", nestedObj)
let nestedCopyObj = JSON.parse(JSON.stringify(nestedObj));
nestedCopyObj.dog.breed = 'Golden'
console.log("Copy nestedCopyObj.dog.breed:", nestedCopyObj.dog.breed)
console.log("Original nestedObj.dog.breed:", nestedObj.dog.breed)

console.log("Original nestedObj.getDogBreed():", nestedObj.getDogBreed())
console.log("Copy nestedCopyObj.getDogBreed():", nestedCopyObj.getDogBreed())

Output:

Original nestedObj(): { dog: { breed: 'Collie' }, getDogBreed: [Function: getDogBreed] }
// The breed property for the copy is set to Golden
Copy nestedCopyObj.dog.breed: Golden
// The breed property for the original nestedObj stays the same
Original nestedObj.dog.breed: Collie

// The original function getDogBreed() works
Original nestedObj.getDogBreed(): Collie
// The copy of the getDogBreed() was not copied
nestedCopyObj.getDogBreed is not a function

StackOverflow thread for more on making deep copies and third party tools.

Note: the spread operator ... is called a rest parameter when used as an argument in a parameter signature. More on that here.