항해 99 2주차 Node.js 과제3

Seong Hyeon Kim·2022년 5월 18일
0

항해99

목록 보기
3/16

스코프 체인

  • 어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다. 이러한 식별자의 유효범의를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라고 부른다.

[예시]

var c = 4;

function outer() {
  var a = 1;
  var b = 3;

  function inner() {
    var a = 2;
    console.log(a); // 2
    console.log(b); // 3
    console.log(c); // 4
  }
	
  inner();
}

outer();
  • console.log(a)에서 a의 값을 찾으려고 할 때 들여다보는 곳이 스코프이고 스코프는 함수 단위이다. 이 때, inner 함수 스코프, outer 함수 스코프, global 스코프가 존재하는데 inner 스코프 안에서 먼저 a가 존재하는지 찾는다.

  • 마찬가지로 console.log(b)에서 b의 값을 inner 스코프 안에서 먼저 찾는다. inner 스코프에는 b가 없기 때문에 그 다음 outer 스코프에 b가 있는지 찾아본다. inner 함수가 생성된 곳이 outer 함수 범위 안에 있기 때문이다.

  • 마찬가지로 console.log(c)에서 c의 값을 inner 스코프 안에서 먼저 찾는다. inner 스코프에는 c가 없기 때문에 그 다음 outer 스코프에 c가 있는지 찾아본다. outer 스코프에도 c가 없기 때문에 global 스코프에서 찾는다. inner 함수가 생성된 곳이 outer 함수 범위 안에 있고, outer 함수가 생성된 곳이 global 범위 안에 있기 때문이다.

  • 이런식으로 스코프끼리 연결된 것이 바로 스코프 체인이다.

클로저 (변수 은닉화)

var c = 4;

function outer() {
  var a = 1;
  var b = 3;

  return function inner() {
    var a = 2;
    console.log(b); // 3
  }
}

var someFunc = outer();
someFunc();
  • outer 함수는 inner 함수를 return 하고 있고 someFunc에는 outer 함수의 실행결과 값이 들어가기 때문에 inner 함수가 들어가게 된다.

  • 그런데, var someFunc = outer(); 에서 outer 함수를 호출해서 실행이 완료되면 outer 함수 내 지역 변수들은 사라질 것 같지만 사라지지 않는다. 몇몇 프로그래밍 언어에서는 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재하지만 자바스크립트는 함수를 리턴하고 리턴하는 함수가 클로저를 형성하기 때문이다.

  • 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

  • someFunc는 outer이 실행될 때 생성된 inner 함수의 인스턴스에 대한 참조다. inner의 인스턴스는 변수 b가 있는 어휘적 환경에 대한 참조를 유지한다. 이런 이유로 someFunc가 호출될 때 b는 사용할 수 있는 상태로 남게된다.

  • 즉, 원래는 함수 내부에 선언한 변수는 함수가 끝나면 사라지지만, 클로저가 스코프 체인을 계속 들고 있으므로 outer 함수 내부의 변수를 참조할 수 있게 된다.


* 불변성 (immutability)

  • 불변함을 의미하는 단어이다. 프로그래밍에서 immutability는 데이터의 원본이 훼손되는 것을 막는 것을 의미한다.

* 불변하게 만드는 방법

  • JavaScript에서 일반적으로 변수를 선언하는 방법은 ‘var’ 키워드를 사용하는 방식이었다.

  • 그러나 이 같은 방식은 값이 변할 수 있기 때문에 원본 훼손에 대한 위험에 노출되어 있었다.

  • 따라서 ES6 이후 문법에서는 const라는 키워드를 도입해 상수(항상 같은 변수) 개념을 도입해 불변성을 지킬 수 있도록 해주었다.

var v = 1
// some code
v = 2
console.log('v: ', v) // v: 2 =>

/* 처음 v를 만든 개발자는 1값을 기대하고 만들었을 것이다. 
그러나 다른 개발자가 위 코드를 모르고 새로운 값을 할당하면서 immutability가 훼손됐다. */

const c = 1;
//some code
c = 2; // TypeError: Assignment to constant Variable
/* 실행되기 전에 위와 같은 오류가 발생한다. immutability도 지키면서 오류도 쉽게 잡아낼 수 있게 되었다.

* 값을 불변하게 하는 법

JavaScript에는 원시형 타입과, 객체형 타입을 이해할 필요가 있다.

  • 원시형 타입은 (Number, String, Boolean, Null, Undefined, Symbol)이 있다.

  • 객체형 타입은 (Object, Array, Function 등)이 있다.

* 원시형 타입

  • 원시형 타입은 선언되는 순간 값이 메모리에 저장되고, 변수가 그 값을 가리키게 된다.
    따라서 같은 값을 가리키는 두 변수를 동등 비교연산자(===)로 비교했을 때 true값이 반환된다.
var v1 = 1
var v2 = 1
console.log(v1 === v2) // true

* 객체형 타입

  • 객체형 타입은 선언되는 순간 값 자체가 아닌 새로운 메모리 주소를 생성해 저장하기 때문에 객체 별로 새로운 주소가 생성된다. 따라서 동등 비교를 했을 경우 그 내용이 같더라도 false 값이 반환된다.
var obj1 = {
  name: 'Serzhul',
}

var obj2 = {
  name: 'Serzhul',
}
console.log(obj1 === obj2) // false
  • 결론
    원시형 타입은 값이 그 값 자체이므로, 값을 바꿀 수 없다. ⇒ 불변하다.
    객체는 같은 객체라도 속성 값을 바꿀 수 있기 때문에 ⇒ 불변하지 않다.(가변성을 가진다)

* 얕은 복사(shallow copy)

  • 정의 : 얖은 복사는 참조(주소)값의 복사를 나타낸다.
const obj = { vaule: 1 }
const newObj = obj;

newObj.vaule = 2;

console.log(obj.vaule); // 2
console.log(obj === newObj); // true
  • obj 객체를 새로운 newObj 객체에 할당하였으며 이를 참조 할당 이라 부른다.

  • 복사 후 newObj 객체의 value값을 변경하였더니 기존의 obj.value값도 같이 변경된 것을 알 수 있다. 두 객체를 비교해도 true로 나온다.

  • 이렇게 자바스크립트의 참조 타입은 얕은 복사가 된다고 볼 수 있으며, 이는 데이터가 그대로 생성되는 것이 아닌 해당 데이터의 참조 값(메모리 주소)를 전달하여 결국 한 데이터를 공유하는 것이다.

  • 즉, 데이터가 그대로 하나 더 생성된 것이 아닌 해당 데이터의 메모리 주소를 전달하게 돼서, 결국 한 데이터를 공유하게 되는 것이다.


* 깊은복사

  • 정의 : 깊은 복사는 값 자체의 복사를 나타낸다.
let a = 1;
let b = a;

b = 2;

console.log(a); // 1
console.log(b); // 2
console.log(a === b); // false
  • 변수 a를 새로운 b에 할당하였고 b 값을 변경하여도 기존의 a의 값은 변경되지 않는다.

  • 두 값을 비교하면 false가 출력되며 서로의 값은 단독으로 존재하다는 것을 알 수 있다.

  • 이렇게 자바스크립트의 원시 타입은 깊은 복사가 되며, 이는 독립적인 메모리에 값 자체를 할당하여 생성하는 것이라 볼 수 있다.

참조 타입의 깊은 복사

  • 객체나 배열은 전개연산자나 Object.assign() 메서드를 통해 (깊은)복사 할 수 있다고 배웠었지만, 위 방법들은 일정 깊이 이상의 데이터들은 (객체가 프로퍼티로 객체를 가지고있는 등) 복사 되지않는다는 것을 알게되었다.
let notDeep  =  {
  a : 1,
  b : 2,
  c : {name : "park"}
}


let b = {...notDeep}

notDeep.a = 100
notDeep['c'].name = "Kim"


console.log(notDeep.a) // 100
console.log(b.a) // 1

console.log(notDeep.c) // { name:"Kim" }
console.log(b.c) // { name:"Kim" }
  • 먼저 흔하게 사용하는 전개 연산자를 통한 예제를 만들어 보았다. notDeep 변수에 객체를 할당한뒤 변수 b에 전개 연산자를 통해 notDeep객체를 복사했다.

  • 물론 notDeep 객체의 a란 키에 100을 재할당 후 b의 a를 출력했을때, 100이 아닌, 1로 출력되기에 정상적으로 깊은 복사가 이루어진 것 처럼 보이지만, notDeep 내부의 {name : "park"}이란 객체의 'name'의 값을 "Kim"으로 변경했더니 b의 name까지 변경된 것을 알 수 있다. 즉, 완벽한 복사가 이루어졌다고 하기 힘들다. ((Object.assign()도 동일)


* 그렇다면 완벽한 복사는 어떻게 해야할까??

1. JSON객체의 메소드를 이용

const copyObj = obj => JSON.parse(JSON.stringify(obj));

let notDeep  =  {
  a : 1,
  b : 2,
  c : {name : "park"}
}

const copied = copyObj(notDeep);

notDeep.a = 1000;
notDeep.c.name = "Kim";

console.log(notDeep.c) // { name:"Kim" }
console.log(copied.c);	// { name:"park" }
  • JSON.stringify 메서드는 자바스크립트 객체를 JSON문자열로 변환 시킨다.

  • 반대로 JSON.parse는 JSON문자열을 자바스크립트 객체로 변환시킨다. 그렇기에 객체를 JSON문자열로 변환했다가 다시 객체로 변환하기에, 객체에 대한 참조가 사라진것이다.

  • 그러나 이 방법에는 2가지 문제점이 있는데, 타 방법들에 비해 성능이 떨어진다는점과 JSON.stringify 메서드가 함수를 undefined로 처리한다는 점이다.

2. Lodash의 deepclone 함수

const _ = require("lodash");

let notDeep = {
  a: 1,
  b: 2,
  c: { name: "park" },
  d: () => {
    return "function is work!";
  },
};

const copy = _.cloneDeep(notDeep);

notDeep["c"].name = "Kim";

console.log(notDeep); // { a: 1, b: 2, c: { name: 'Kim' }, d: [Function: d] }
console.log(copy); // { a: 1, b: 2, c: { name: 'park' }, d: [Function: d] }
console.log(copy.d()); // function is work!
  • lodash 라이브러리의 cloneDeep() 메서드를 이용하는 방법이 가장 간편하다고 한다.
  • 위 예제에서 알 수 있듯이 JSON변환을 이용한 방식에서 일어났던 함수 undefined 처리 문제가 해결된것을 알 수 있다.

출처 : https://velog.io/@nomadhash/Java-Script-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-1dus9z79


profile
삽질도 100번 하면 요령이 생긴다. 부족한 건 경험으로 채우는 백엔드 개발자

0개의 댓글