클로저는 함수가 생성되는 시점에 접근 가능했던 변수들을 생성 이후에도 계속해서 접근할 수 있게 해주는 기능이다. 접근할 수 있는 변수는 그 함수를 감싸고 있는 상위 함수들의 매개변수와 내부 변수들이다.

 

클로저를 사용한 간단한 코드

function makeAddFunc(x) {
  return function add(y) {
    return x+y;
  }
}
const add5 = makeAddFunc(5);
console.log(add5(1)); // 6

const add7 = makeAddFunc(7);
console.log(add7(1)); //8
console.log(add5(1)); //6

add 함수는 상위 함수인 makeAddFunc의 매개변수 x에 접근할 수 있다. add5 함수가 생성된 이후에도 상위 함수를 호출할 때 사용했던 인수에 접근할 수 있다. 중간에 makeAddFunc(7)이 호출되지만 add5에 영향을 주지는 않는다. 즉, 생성된 add 함수별로 클로저 환경이 생성된다.

 

 

ES6+ 에서는 화살표 함수를 이용해 함수를 정의하는 방법이 추가되었다. 화살표 함수를 사용하면 함수를 정말 간결하게 작성할 수 있다.

const add = (a,b) => a+b;

이런식으로 사용되는데 화살표 함수를 중괄호로 감싸지 않으면 오른쪽의 계산 결과가 반환된다. 

const add = a => a+b;

 

매개변수가 하나라면 매개변수를 감싸는 소괄호도 생략 있다.

const addAndReturnObject = (a,b) => ({return: a+b})

객체를 반환해야 한다면 소괄호로 감싸야 한다.

 

화살표 함수의 코드가 여러줄인 경우

const add = (a,b) => {
  if(a <0 || b<= 0) {
    throw new Error('must be position number');
  }
  return a+b;
}

화살표 함수에 여러 줄의 코드가 필요하다면 이와 같이 전체를 중괄호로 묶고 반환값에는 return이라는 키워드를 사용함 된다.

그리고 ES6는 this 바인딩 때문에 버그가 발생하는 경우 화살표 함수를 사용하면 그 버그를 제어할 수 있다.

function Something() {
  this.value = 1;
  this.increase = () => this.value++;
}

const obj = new Something();
obj.increase();
console.log(obj.value);

const increase = obj.increase;
increase();
console.log(obj.value);

화살표 함수의 increase의 this는 가장 가까운 일반 함수인 Something의 this를 참조한다. Something 함수는 생성자이고 밑에 obj 객체가 생성될 때 호출된다. new 키워드를 이용해서 생성자 함수를 호출하면 this는 생성되는 객체를 참조한다는 점에 유의하자. increase함수의 this는 생성된 객체를 가르킨다. 그러니 호출되는 시점의 객체와는 무관하게 increase함수의 this는 항상 생성된 객체를 참조하고 obj.value는 계속 증가한다.

 

비구조화 심화 학습

비구조화는 객체와 배열이 중첩되어 있을 때도 사용할 수 있다.
const obj = {name: 'mike', mother: {name: 'sara'}};
const {
  name,
  mother: {name : motherName},
} = obj;

console.log(name);
console.log(motherName);
console.log(mother);

이 처럼 세개의 단어가 등장하지만, 비구조화 결과로 motherName이라는 변수만 생성된다. 비구조화에서 기본값의 정의는 변수로 한정되지 않는다.

 

객체비구조화

const index = 1;
const { [`key${index}`]: valueOfTheIndex} = {key1: 123};
console.log(valueOfTheIndex); //123

객체 비구조화에서 계산된 속성명을 사용할 때에는 반드시 별칭을 입력해야한다. 별칭은 단순히 변수명만 입력할 수 있는 것은 아니다.

({foo: obj.prop, bar: arr[0]} = {foo : 123, bar: true});
console.log(obj); // {prop:123}
console.log(arr); // [true]

첫 줄처럼 객체 비구조화를 이용해서 obj 객체의 prop이라는 속성과 배열의 첫 번째 원소에 값을 할당하고 있다.

 

강화된 ES6+ 함수의 기능 

매개변수에 기본값을 줄 수 있게 되었고, 나머지 매개변수를 통해 가변 길이 매개변수를 좀 더 명시적으로 표현할 수 있게 되었다. 명명된 매개변수를 통해 함수를 호출하는 코드의 가독성이 월등히 좋아졌다. 그리고 화살표 함수가 추가되면서 함수 코드가 간결해졌고, this 바인딩에 대한 고민을 덜 수 있게 되었다.

 

매개변수의 추가된 기능

1. 매개변수 기본값 : ES6부터 함수 매개변수에 기본값을 줄 수 있다. 
function printLog(a=1) {
  console.log({a});

}
printLog(); // {a:1}

인수 없이 함수를 호출하므로 a에는 undefined가 입력됨 ! 기본값이 정의된 매개변수에 undefined를 입력하면 정의된 기본값이 1이 사용된다. 객체 비구조화처럼 기본값으로 함수 호출을 넣을 수 있고, 기본값이 필요한 경우에만 함수가 호출된다.

 

2. 나머지 매개변수 : 나머지 매개변수는 입력된 인수 중에서 정의된 매개변수 개수만큼 제외한 나머지를 배열로 만들어 준다. 나머지 매개변수는 매개변수 개수가 가변적일 때 유용하다.
function printLog(a, ...rest) {
  console.log({a, rest});
}

printLog(1,2,3); // {a:1, rest:[2,3]}

하나의 인자를 제외한 나머지 rest 매개변수에 할당한다. ES5에서는 arguments 키워드가 비슷한 역할을 한다. 

 

3. 명명된 매개변수 : 자바스크립트에서 명명된 매개변수는 객체 비구조화를 이용해서 구현할 수 있다.
명명된 매개변수를 사용하면 함수 호출 시 매개변수의 이름과 값을 동시에 적을 수 있으므로 가독성이 높다.
그리고 명명된 매개변수를 사용하면 함수를 호출할 때마다 객체가 생성되기 때문에 비효율적일 것이라고 생각할 수 있다. 하지만 자바스크립트 엔진이 최적화를 통해 새로운 객체를 생성하지 않으므로 안심하고 사용해도 된다.
const numbers = [10,20,30,40];
const result1 = getValues(numbers, 5, 25);
const result2 = getValues({ numbers, greaterThan:5, lessThan:25});// << 이렇게 매개변수의 이름과 값을 동시에 적을 수 있어서 가독성이 좋아졌다.

 

😗이번에는 ES6+에서 객체와 배열에 추가된 문법을 알아보자.

단축 속성명과 계산된 속성명을 이용하면 객체와 배열을 생성하고 수정하는 코드를 쉽게 작성할 수 있다. 또한, 전개 연산자와 비구조화 할당 덕분에 객체와 배열의 속성값을 밖으로 꺼내는 방법이 한결 쉬워졌다.

단축 속성명과 계산된 속성명

1. 단축 속성명

- 단축 속성명은 객체 리터럴 코드를 간편하게 작성할 목적으로 만들어진 문법. 단축 속성명을 사용하면 간편하게 새로운 객체를 만들 수 있다. 

const name = "mike";
const obj = {
 age: 21,
 name,
 getName() { retrun this.name; },
 }

새로 만들려는 객체의 속성값 일부가 이미 변수로 존재하면 간단하게 변수 이름만 적어주면 된다. 이때 속성명은 변수 이름과 같아진다. 속성값이 함수면 function 키워드 없이 함수명만 적어도 된다. 이때 속성명은 함수명과 같아진다. 

 

function makePerson1(age,name) {
 return { age: age, name: name };
}
function makePerson2(age,name) {
 retrun { age, name };
}

첫번째 함수 단축 속성명을 사용하지 않은 경우이고, 두번째 함수가 단축 속성명을 사용한 경우다. 보다시피 단축 속성명을 사용한 경우가 코드를 작성하기도 편하고 간결하고 가독성도 편하다. 

 

2. 계산된 속성명

계산된 속성명은 객체의 속성명을 동적으로 결정하기 위해 나온 문법. 

function makeObject1(key,value){
 const obj = {};
 obj[key] = value;
 return obj;
}
function makeObject2(key,value){
 return{ [key]:value};
}

계산된 속성명을 사용하면 같은 함수를 두 번째 함수처럼 간결하게 작성할 수 있다.

 

전개 연산자와 비구조화 할당

3. 전개 연산자

전개 연산자는 배열이나 객체의 모든 속성을 풀어놓을 때 사용하는 문법이다. 매개변수가 많은 함수를 호출할 때 유용하다 !

Math.max(1,3,7,9); // 이 방식으로는 동적으로 매개변수를 전달할 수 없다.

// 전개 연산자
const numbers = [1,3,7,9];
Math.max(...numbers);

전개 연산자를 사용하면 동적으로 함수의 매개변수를 전달할 수 있다.

 

//배열, 객체 생성
const arr1 = [1,2,3];
const obj1 = {age:23, name:'mike'}

//전개연산자 사용하여 새로운 객체와 배열 생성
const arr2 = [...arr1];
const obj2 = {...obj1};

//전개 연산자 사용해서 새로운 객체가 생성되었기 때문에
//속성을 추가하거나 변경해도 원래의 객체에 영향을 주지 않음!
arr2.push(4);
obj2.age = 80;

배열의 경우 전개 연산자를 사용하면 그 순서가 유지된다.

 

[1, ...[2,3],4]; // [1,2,3,4]
new Date(...[2020, 6, 24]); //2020년 6월 24일

배열 리터럴에서 중간에 전개 연산자를 사용하면 전개 연산자 전후의 순서가 유지된다. 두 번째 함수는 함수의 인수를 정의된 매개변수의 순서대로 입력해야 하므로 순서가 유지되는 전개 연산자의 성질을 이용하기 좋다. 예를 들어 Date 생성자의 매개변수 순서대로 날짜 데이터를 관리하면 Date 객체를 쉽게 생성할 수 있다. 전개 연산자를 사용하면 서로 다른 두 배열이나 객체를 쉽게 합칠 수 있다.

 

const obj1 = {age:21, name:'mike'};
const obj2 = {hobby: 'soccer'};
const obj3 = {...obj1, ...obj2 };
console.log(obj3); // {age:21,name:'mike',hobby:'soccer'}

이 코드에서 obj1과 obj2가 같은 이름의 속성을 가지고 있다면 ? es5까지는 중복된 속성명을 사용하면 에러가 발생했지만, es6부터는 중복된 속성명이 허용된다. 중복된 속성명 사용 시 최종 결과는 마지막 속성명의 값이 되다. 중복된 속성명과 전개 연산자를 이용하면 객체의 특정 속성값을 변경할 때 이전 객체에 영향을 주지 않고 새로운 객체를 만들어 낼 수 있다. 이는 변수를 수정 불가능하도록 관리할 때 유용하게 사용될 수 있다.

 

4. 배열 비구조화

배열 비구조화는 배열의 여러 속성값을 변수로 쉽게 할당할 수 있는 문법이다. 다음은 배열 비구조화를 사용한 코드다.

const arr =[1,2];
const [a,b] = arr;
console.log(a); //1
console.log(b); //2

-------------------

let a, b;
[a,b] = [1,2];

배열의 속성값이 왼쪽의 변수에 순서대로 들어간다. 이렇게 새로운 변수로 할당할 수도 있고 이미 존재하는 변수에 할당할 수도 있다. 

 

const arr =[1,2,3];
const [a,, c] = arr;
console.log(a); //1
console.log(c);//3

그리고 배열 비구조화를 사용하면 제3의 변수가 필요하지 않을 뿐만 아니라 단 한줄의 짧은 코드로 구현할 수 있다. 배열에서 일부 속성값을 무시하고 진행하고 싶다면 건너뛰는 개수만큼 쉼표를 입력하면 된다. 첫 번째 속성값은 변수 a에 할당된다. 두 번째 속성값은 건너뛰고 세 번째 속성값이 변수 c에 할당된다. 쉼표 개수만큼을 제외한 나머지를 새로운 배열로 만들 수도 있다.

 

const arr = [1,2,3];
const [first, ...rest1] = arr;
console.log(rest1);
const [a,b,c, ...rest2] = arr;
console.log(rest2); // []

배열 비구조화 시 마지막에 ...와 함께 변수명을 입력하면 나머지 모든 속성값이 새로운 배열로 만들어진다. 나머지 속성값이 존재하지 않으면 빈 배열이 만들어진다.

 

5. 객체 비구조화

const obj1 = {age:21,name:'mike'}
const {age, name} = obj; // 객체 비구조화에서는 중괄호를 사용
console.log(age); // 21
console.log(name); // mike

객체 비구조화는 객체의 여러 속성값을 변수로 쉽게 할당 할 수 있는 문법이다. 객체 비구조화에서는 중괄호를 사용한다. 배열 비구조화에서는 배열의 순서가 중요 했지만 객체 비구조화에서 순서는 무의미하다. 따라서 name과 age의 순서를 바꿔도 결과는 같다. 단, 배열 비구조화에서 왼쪽 변수의 이름은 임의로 결정할 수 있지만, 객체 비구조화에서는 기존 속성명 그대로 사용해야 한다.

 

ES6는 이크마에서 2015년에 채택한 자바스크립트 표준이다. ES6 이후로 자바스크립트에는 많은 변화가 있었다. 새로 추가된 기능이 많은데 같이 알아봅시다 ! 😃

변수를 정의하는 새로운 방법 const, let

es5까지의 자바스크립트에서는 var를 이용해서 변수를 정의했고 그게 유일한 방법이었다면 ES6에서는 const / let을 이용하는 새로운 변수 정의 방법이 생겼다. 새로운 방법이 나온 이유는 기존 방식으로는 해결되지 않는 문제가 있었기 때문이다. 자바스크르립트가 저급한 언어라고 무시당하던(?) ES6 이전 시절, var가 그 비난에 한몫하지 않았을까 싶다.

var가 가진 문제를 알아보자

1. var = 함수 스코프

  •  var의 첫 번째 문제는 정의된 변수가 함수 스코프를 가진다는 것이다. 스코프란 변수가 사용될 수 있는 영역을 말한다. 스코프는 변수가 정의된 위치에 의해 결정된다. var로 정의된 변수는 함수를 벗어난 영역에서 사용하면 에러가 발생한다.
function example() {
 var i = 1;
 }
 console.log(i);

var 변수를 함수가 아닌 프로그램의 가장 바깥에 정의하면 전역 변수가 되는데, 이는 프로그램 전체를 감싸는 하나의 함수가 있다고 생각하면 이해하기 쉽다. 특이한 점은 함수 안에서 var 키워드를 사용하지 않고 변수에 값을 할당하면 그 변수는 전역 변수가 된다는 점이다. var는 함수 스코프이기 때문에 for 반복문에서 정의된 변수가 반복문이 끝난 이후에도 계속 남는 문제점이 있다. for 문 뿐만 아니라 while문, switch문, if문 등 함수 내부에서 작성되는 모든 코드는 같은 문제를 안고 있다. var 변수의 스코프를 제한하기 위해 즉시 실행 함수를 사용하기도 한다. 

 

즉시 실행 함수란 ?

함수를 정의하는 시점에 바로 실행되고 사라진다. var 변수는 함수 스코프이므로 즉시 실행 함수로 묶으면 변수의 스코프를 제한할 수 있다. 그러나 즉시 실행 함수는 작성하기 번거롭고 가독성도 떨어진다

 

2. 호이스팅

var로 정의된 변수는 그 변수가 속한 스코프의 최상단으로 끌어올려진다. 이를 호이스팅이라고 부른다. 호이스팅은 직관적이지 않으며, 보통의 프로그래밍 언어에서는 찾아보기 힘든 성질이다.

 

3. 재정의

var myvar = 1;
var myvar = 2;

var를 이용하면 한 번 정의된 변수를 재정의할 수 있다. 변수를 정의한다는 것은 이전에 없던 변수를 생성한다는 의미로 통용된다. 따라서 앞의 코드가 에러 없이 사용될 수 있다는 것은 직관적이지 않으며 버그로 이어질 수 있다.

 

 


 

위에 같은 var의 문제를 해결해주는 const, let !

const, let은 블록 스코프다. var는 함수 스코프였지만 const,let은 블록 스코프라 부른다. 블록 스코프에서 if 문의 블록 안에서 정의된 변수는 if 문을 벗어나면 참조할 수 없다. if 문에서 생성된 변수를 블록 바깥에서 사용하려고 하면 에러가 발생한다. 이러한 상황에서 에러가 발생하는 것이 직관적이며 이해하기도 쉽다. 

 

1. const,let에서의 호이스팅

const, let으로 정의된 변수도 호이스팅 된다. 하지만 const, let으로 변수를 정의하기 전에 그 벼수를 사용하려고 하면 참조 에러가 발생한다.

console.log(foo); //참조에러
const foo = 1; 

똑같은 경우 var는 에러가 발생하지 않는다. 따라서 const, let으로 정의된 변수는 호이스팅이 되지 않는다고 생각하기 쉽다. 하지만 const, let으로 정의된 변수도 호이스팅되나. 다만 변수가 정의된 위치와 호이스팅된 위치 사이에서 변수를 사용하려 하면 에러가 발생한다. 이 구간을 임시적 사각지대라고 부른다.

 

2. const는 변수를 재할당 불가능하게 만든다.

const로 정의된 변수는 재할당이 불가능하다. 반대로 let, var로 정의된 변수는 재할당 할 수 있다. 재할당 불가능한 변수는 프로그램의 복잡도를 상당히 낮춰주기 때문에 되도록이면 재할당 불가능한 변수를 사용하는 게 좋다. 이미 존재하는 속성값을 수정하거나 새로운 속성값을 추가하는 것 모두 가능하다. 객체의 내부 속성값도 수정 불가능하게 만들고 싶다면 immer, immutable.js 등의 외부 패키지를 활용하는게 좋다. 이러한 외부 패키지는 객체를 수정하려 할 때 기존 객체는 변경하지 않고 새로운 객체를 생성한다. 새로운 객체를 생성하는 편의 기능은 필요 없고 단지 수정만 할 수 있도록 차단하고 싶다면, 이런 자바스크립트 내장 함수를 이용하면 된다.

  • Object.preventExtenstions
  • Object.seal
  • Object.freeze

그리고 const로 정의 했다면 객체를 참조하는 변수 자체를 변경하는 것은 불가능 !

 

 

 

+ Recent posts