Comics: "Is task ready?" Driven Development

Opublikowano: - tagi:

JavaScript: Jak stworzyć głęboką kopię zagnieżdżonego obiektu?

Opublikowano: - tagi:

Kopiowanie obiektu

Stworzenie kopii obiekt jest proste:

const originalObj = {
	title: 'Foo',
	value: 100,
	tags: ['a', 'b', 'c']
};

const copyObj = {
	...originalObj
};

copyObj.title = 'Bar';
copyObj.value = 50;
copyObj.tags.push('d');

console.log('originalObj: ', originalObj); // { title: 'Foo', value: 100, tags: ['a', 'b', 'c'] }
console.log('copyObj: ', copyObj); // { title: 'Bar', value: 50, tags: ['a', 'b', 'c', 'd'] }

Za pomocą operatora spread: ... tworzymy kopię obiektu. Następnie zmieniamy w skopiowanym obiekcie jego parametry. Oryginał się nie zmieniał. I tak miało być :)

Niestety to nie zadziała dla obiektu zagnieżdżonego.

Kopiowanie obiektu zagnieżdżonego

Najpierw przykład:

const originalObj = {
	name: 'Janusz Kowalski',
	age: 57,
	address: {
		city: 'New York',
		street: '99 East Golf Street'
	}
};

const copyObj = {
	...originalObj
};

copyObj.name = 'Grażyna Kowalska';
copyObj.address.city = 'Chicago';
copyObj.address.street = '3109 15th St';

console.log('originalObj: ', originalObj); 
/* 
originalObj: 
{
  name: 'Janusz Kowalski',
  age: 57,
  address: {
    city: 'Chicago',
    street: '3109 15th St'
  }
}
*/

console.log('copyObj: ', copyObj);
/*
copyObj:
{
  name: 'Grażyna Kowalska',
  age: 57,
  address: {
    city: 'Chicago',
    street: '3109 15th St'
  }
}
*/

Kopia została stworzona, ale nie dla zagnieżdżonej części obiektu jak: address. Po zmianie danych w kopii zmieniły się też dane w oryginale!

Jak sobie z tym poradzić?

const originalObj = {
	name: 'Janusz Kowalski',
	age: 57,
	address: {
		city: 'New York',
		street: '99 East Golf Street'
	}
};

const copyObj = JSON.parse(JSON.stringify(originalObj))

copyObj.name = 'Grażyna Kowalska';
copyObj.address.city = 'Chicago';
copyObj.address.street = '3109 15th St';

console.log('originalObj: ', originalObj); 
/* 
originalObj: 
{
  ...,
	address: {
		city: 'New York',
		street: '99 East Golf Street'
	}
}
*/

console.log('copyObj: ', copyObj);
/*
copyObj:
{
  ...,
  "address": {
    "city": "Chicago",
    "street": "3109 15th St"
  }
}
*/

Kombinacja: JSON.parse i JSON.stringify rozwiązuje ten problem.

To rozwiązanie nie zadziała jeśli obiekt zawiera funkcje. Funkcje nie zostaną skopiowane.


JavaScript: Czym jest czysta funkcja?

Opublikowano: - tagi:

Czym to jest czysta funkcja?

Czysta funkcja (ang.: pure function) jest to termin z programowania funkcjonalnego, który mówi, że funkcja jest "czysta" jeśli są spełnione następujące warunki:

  1. Te same dane przekazane do funkcji zawsze dają taki sam wynik.
  2. Funkcja nie tworzy efektów ubocznych.

Przyjrzyjmy się obu warunkom bliżej.

Dane na wejściu, wynik na wyjściu

Te same dane przekazane do funkcji zawsze dają taki sam wynik.

Czyli: dla taki samych parametrów funkcja zawsze powinna zwrócić ten sam wynik:

const multiply = (num, by) => num * by;

console.log(multiply(2, 3)); // 6
console.log(multiply(2, 4)); // 8
console.log(multiply(2, 3)); // 6

Podajemy dwa razy te same parametry: 2 i 3 i dostajemy ten sam wynik: 6. To jest czysta funkcja.

Inny przykład:

console.log(Math.random()); // 0.2755161550452777
console.log(Math.random()); // 0.5888948222087572
console.log(Math.random()); // 0.2976254575123847

To nie jest czysta funkcja! Dla tych samych danych wejściowych dostajemy różne wyniki. Pierwszy warunek nie został spełniony w tym przypadku.

Efekty uboczne

Funkcja nie tworzy efektów ubocznych.

To znaczy, że czysta funkcja nie zmienia stanu danych do niej przekazanych.

Przykład 1

const setProperty = (property, value, object) => {
  object[property] = value;
}

const obj = { name: 'Object' };
setProperty('size', 100, obj); // { name: 'Object', size: 100 }

console.log(obj);

Funkcja setProperty nie można nazwać czystą, ponieważ tworzy efekt uboczny. Zmienia stan obiektu, który jest przekazany do funkcji.

Poprawiona wersja:

const setProperty = (property, value, object) => {
	return ({
		...object,
		[property]: value
	});
}

const obj = { name: 'Object' };
const updatedObject = setProperty('size', 100, obj);

console.log(obj); // { name: 'Object' }
console.log(updatedObject); // { name: 'Object', size: 100 }

Przykład 2

Czysta funkcja nie polega na stanie z zewnątrz:

const counterValue = 10;
const updateCounter = (value) => value + counterValue;

console.log(updateCounter(5)); // 15
console.log(updateCounter(10)); // 20
console.log(updateCounter(10)); // 20

Tutaj niby wszystko jest ok. Daje takie same wyniki na przykład dla parametru 10. Problem jest taki, że działamy na zmiennej z zewnątrz.

A co jeśli zmienimy nieco kod:

let counterValue = 10;
const updateCounter = (value) => value + counterValue;

console.log(updateCounter(5)); // 15
console.log(updateCounter(10)); // 20
counterValue += 15;
console.log(updateCounter(10)); // 35

Ups... mamy już inne wyniki...


JavaScript: Jak zapobiec zmianie wartości tablicy?

Opublikowano: - tagi:

Kopiowanie tablicy

Załóżmy, że mam taki przykład:

const original = [1, 2, 3, 4, 5];
const copy = original;

copy[0] = 7;

console.log('original: ', original); // [7, 2, 3, 4, 5]
console.log('copy: ', copy); // [7, 2, 3, 4, 5]

Stworzyliśmy kopię tablicy, a następnie zmieniliśmy jej pierwszy element. Niestety zmienił się także oryginał. Jest tak dlatego, że w ten sposób skopiowaliśmy także referencje z macierzystej tablicy.

Jak temu zapobiec?

Operator spread: ...

const original = [1, 2, 3, 4, 5];
const copyArray = [...original];

copyArray[0] = 7;

console.log('original: ', original); // [1, 2, 3, 4, 5]
console.log('copyArray: ', copyArray); // [7, 2, 3, 4, 5]

Funkcja slice

const original = [1, 2, 3, 4, 5];
const copyArray = original.slice();

copyArray[0] = 7;

console.log('original: ', original); // [1, 2, 3, 4, 5]
console.log('copyArray: ', copyArray); // [7, 2, 3, 4, 5]

Funkcja concat

const original = [1, 2, 3, 4, 5];
const copyArray = [].concat(original);

copyArray[0] = 7;

console.log('original: ', original); // [1, 2, 3, 4, 5]
console.log('copyArray: ', copyArray); // [7, 2, 3, 4, 5]

Jak zapobiec zmianie w tablicy?

Jeśli zależy nam na stworzeniu tablicy, której zawartości nie chcemy zmieniać w ogóle, możemy użyć: Object.freeze. Pisałem już o niej w tym wpisie.

Przykład:

const array = Object.freeze([1, 2, 3]);

array[0] = 7; // Błąd!
array.push(12); // Błąd!
array.pop(); // Błąd!

Jak widać każda próba modyfikacji tablicy, powoduje błąd w kodzie.


Comics: Big problem, small steps

Opublikowano: - tagi: