Przekazywanie argumentów do funkcji przez referencję

0

Cześć,
czy w JS obiekty zawsze przekazywane są przez referencję a zmienne proste przez wartość czy może są od tego jakieś wyjątki? Czy da się jakoś zabezpieczyć, żeby obiekt, który jest w funkcji modyfikowany nie uległ modyfikacji?

2

A czym jest referencja jak nie wartością? W JavaScript wszystko jest przekazywane przez wartość, a konkretniej jest to call by sharing

W przypadku obiektów tą wartością jest referencja do przekazywanego obiektu. Przekazywana wartość jest "kopiowana", co ma pewne ciekawe implikacje.

Poniższy kod zmodifikuję wartość zmiennej foo, ponieważ referencja, mimo że skopiowana, cały czas pokazuje na ten sam obiekt, więc możemy bez problemu go modyfikować.

function modifyMember(reference) {
  reference.foo = "baz"
}

var obj = { foo: "bar" }

modifyMember(obj)

console.log(obj)
// { foo: "baz" }

Nie możemy jednak w całości go nadpisać.

function modifyObj(reference) {
  reference = { foo: "baz"}
}

var obj = { foo: "bar" }

modifyObj(obj)

console.log(obj)
// { foo: "bar" } 

Co zrobić, żeby nie dało się zmodyfikować obiektu?

Jako konsument funkcji możemy użyć Object.freeze

function modifyMember(reference) {
  reference.foo = "baz"
}

var obj = { foo: "bar" }

Object.freeze(obj)

modifyMember(obj)

console.log(obj)
// { foo: "bar" }

Jednak Object.freeze nie działa rekurencyjne, więc trzeba o tym pamiętać:

function modifyMember(reference) {
  reference.foo.bar = "baz"
}

var obj = { foo: { bar: "I might get changed" } }

Object.freeze(obj)

modifyMember(obj)

console.log(obj)
// { foo: { bar: "baz" } }

Jest jednak pewna mała biblioteczka, która zamraża nasz obiekt rekurencyjnie, a nazywa się deep-freeze

function modifyMember(reference) {
  reference.foo.bar = "baz"
}

var obj = { foo: { bar: "I might not get changed" } }

deepFreeze(obj)

modifyMember(obj)

console.log(obj)
// { foo: { bar: "I might not get changed" } } 

Jako autor funkcji możemy temu zapobiec nie modyfikując przekazanych argumentów. Wtedy oszczędzimy sobie miliona trudnych do wykrycia bugów. Jeżeli już musimy coś zmodyfikować, to najlepiej jest utworzyć kopię przekazanego obiektu, zmodyfikować kopię i ją zwrócić.

// zamiast tego
function modifyArray(reference) {
  reference.push(4)
}

var arr = [1, 2, 3]

modifyArray(arr)

console.log(arr)
// [1, 2, 3, 4]

// robimy tak
function modifyArray(reference) {
  // możemy użyć też reference.slice();
  // trzy kropki to tzw. spread operator, zachęcam do poczytania.
  reference = [...reference] 
  reference.push(4)

  return reference;
}

var arr = [1, 2, 3]

var modifiedArr = modifyArray(arr)

console.log(arr, modifiedArr)
// [1, 2, 3] [1, 2, 3, 4]

Na sam koniec tylko dodam, że zadeklarowanie zmiennej za pomocą słowa kluczowego const nic nam tu nie pomoże, bo const chroni jedynie przed przypisaniem innej wartości do zmiennej, a nie przed modyfikacją jej.

const foo = [1, 2, 3]
foo.push(1);
// [1, 2, 3, 1]
2

Nawiązując do moich komentarzy powyżej:

function doSomething (arr) {
  const arrCopy = [...arr]
  arrCopy.push(4)

  return arrCopy
}
  • jeszcze lepiej nie robić ręcznie kopii (no chyba, że mamy zagnieżdżoną strukturę, wtedy latwiej użyć np. lodashowego cloneDeep), tylko używać metod nie modyfikujących array'a (spread operator, Array.prototype.slice/.map/.filter/.reduce itp lub jednej z dziesiątek funkcji Lodasha), np:
function doSomething (arr) {
  return [...arr, 4]
}

Ogólnie robienie kopii (ręcznie lub przez metody) jest bardzo dobrą praktyką, jednak w zależności od cyklu życia obiektów może to mieć spory narzut pamięciowy i wydajnościowy - jeśli jest to problemem to można użyć jednej z bibliotek niemutowalnych obiektów, które nie kopiują wszystkiego, jedynie różniece (no i nie trzeba polegać na samodyscyplinie), np. Immutable.js i Mori.

1 użytkowników online, w tym zalogowanych: 0, gości: 1