본문으로 바로가기

이 글은 [인프런] Javascript ES6+ 제대로 알아보기 - 초급(정재남)를 토대로 작성되었음을 알려드립니다.


es2015 이후 새로운 매개 변수와 함수 그리고 그에 따른 기타 관련 내용에 대한 문법을 알아보도록 하겠습니다.

 

 

매개변수 기본값, 나머지 매개변수, 펼치기(전개) 연산자

우선 새로운 매개 변수와 연산자에 대해 알아보고 이후 새로운 함수에 대해 살펴보도록 하겠습니다.

 

default parameter(매개변수 기본값)

default parameter 는 함수의 인자로 넘어오는 것에 기본값을 정의해 줄 수 있는 것입니다.

아래의 기존  ES5 와  ES6 가 어떻게 달라졌는지 예제를 통해 살펴보도록 하자.

js [ES5]
const f = function (x, y, z) {
    x = x ? x : 4; // x 가 있다면 x 를 할당하고 없으면 4을 할당하라.

    // 위 삼항 연산자를 더 간단히 표현한 것으로 y 가 true 면 y 에 넘어온 값을 할당하고 그렇지 않다면 5 를 할당하라.
    y = y || 5;

    if (!z) { // z 가 false 에 해당하는 값이라면 true 로 바꿔서 z 에 6 을 할당하라.
        z = 6
    }

    console.log(x, y, z)
};
f(1);

f(0, null);

위 코드처럼  ES5 에서는 인자에 대한 판단을 늘 함수 내부에서 정의해 사용해 왔습니다.

그런데 f(1) 은 의도대로 로그값이 출력되었지만  f(0, null); 의도한 대로 출력되지 않은 것을 확인할 수 있습니다.
0 은  false 로 형변환되어 4 가 출력되고  null 역시  false 로 형변환되어  4, 5, 6 이 출력된 것입니다.
그렇기 때문에 위와 같이 판단하는 코드는 정확성이 떨어지고 이를 판단하기 위해 더 많은 코드를 작성하게 될 것입니다.
예를 들어  x !== undefined ? x : 4; 혹은  typeof y !== 'undefined' || 5; 와 같이  undefined 일 경우에만 기본값을 할당해 주기를 바라는 의도에서 코드를 작성하려면 해야할 일이 많다고 할 수 있습니다.

Not Good !!!    Not Right !!!    I have a lot of work to do !!!

js
const f = function (x, y, z) {
  x = (x !== undefined) ? x : 3;
  y = (typeof x !== "undefined") ? y : 4;

  console.log(x, y);
}

f(0, null);

 

ES6 에서 위 코드를  default parameter 를 사용하면 다음과 같습니다.

js
const f = function (x = 4, y = 5, z = 6) {
    console.log( x, y, z );
};

f( 0, null );

위와 비슷한 예제로 다음의 값을 예상해 보기 바랍니다.

js
 const f = function (a = 1, b = 2, c = 3, d = 4, e = 5, f = 6) {
    console.log(a, b, c, d, e, f);
};

f(7, 0, "", false, null)

 

 

default parameter 에 대해 좀더 자세히 알아보기

1)  undefined 혹은 누락된 파라미터에 대해서만  default parameter 가 동작을 합니다.

 

2)  그렇다면  은 어떻게 될까?

js
const f = function (x = 1, y = 3 + x) {
    console.log(x, y);
};

f();

로그를 확인해 보면 에러없이 출력되는데 이러한  이 가능한 이유는  let 을 선언한 것과 동일한 효과를 나타낸다라는 말과 같습니다.

만약에 아래와 같은 함수가 있다고 가정해 보자.

js
function fn(a, b, c) { }

fn(1, 2, 3);

 

위 코드는 자바스크립트 엔진이 내부적으로 다음과 같이 처리하게 됩니다.

1.
function fn(a, b, c) {
    var a = 1;
    var b = 2;
    var c = 3;
}

fn(1, 2, 3);

이렇게  fn(1, 2, 3); 처럼 호출하면 실행 컨텍스트가 열리면서 인자 하나하나를 변수로 선언해서 변수에 값을 할당하는 것까지의 과정을 내부에서 처리하게 됩니다.  그렇기 때문에 함수의 인자로 넘어온 매개변수는 함수 내부 변수로서만 존재하게 됩니다.

 

위 코드에서  default parameter 를 할당하는 순간 다음과 같은 과정이 내부적으로 처리됩니다.

js
function fn(a = 1, b = 2, c = 3) { }

fn(1, 2, 3);
js
function fn(a = 1, b = 2, c = 3) {
    let a = (a !== undefined) ? a : 1;
    let b = (b !== undefined) ? b : 2;
    let c = (c !== undefined) ? c : 3;
}

fn( 1, 2, 3 );

 

위와 같은 메커니즘을 이해했다면 다음의 코드를 살펴보자.

js
function fn(a = 1, b = a + 1, c = 3) {
    console.log( a, b, c );
}

fn( 1, undefined, 3 );

위 코드는 잘 동작하지만 아래와 같이  b = a + 1 대신에  b = c + 1 로 변경한다면 어떻게 될까?

js
 function fn(a = 1, b = c + 1, c = 3) {
    console.log( a, b, c );
}

fn( 1, undefined, 3 );

로그를 확인하면  Cannot access 'c' before initialization 인 참조 에러가 발생합니다.
즉,  let 과 같이 동작하기 때문에  c = 3 이  b = c + 1 뒤에 선언되어 있으므로  TDZ 로 인해 초기화되지 않은 변수에 접근할 수 없는 것입니다. 다시 말해, 순서가 중요하다고 할 수 있습니다.

 

이렇게 위와 같은 이유로  이 올 수 있기 때문에 다음과 같은 작성도 가능합니다.

js
const getDefault = function () {
    console.log('getDefault Called.');

    return 10;
};

const sum = function (x, y = getDefault()) {
    console.log(x + y);
};

sum(1, 2);
sum(1);

위 코드를 다음과 같이 좀더 활용해 볼 수도 있습니다.

js
const checkValid = function () {
    console.error('There are no arguments.');
    return 0;
};

const sum = function (x = checkValid(), y = checkValid()) {
    console.log(x + y);
};

sum(1, 2);
sum();

 

3)  let 을 선언한 것과 동일한 효과를 나타낸다.

js
const f = function (x = 1, y = 2 + x) {
    let z = y + 3;
    x = 4;
    console.log(x, y, z);
};
f();

 

4) TDZ(Temporal Dead Zone)

js
const multiply = function (x, y = x * 2) {
    console.log(x * y);
};
multiply(2, 3);
multiply(2);
js
const multiply = function (x = y * 3, y) {
    console.log(x, y);
};
multiply(2, 3);
multiply(undefined, 2);

 

5) 기본값으로 할당하고자 하는 값이 변수일 경우의 주의사항

js
let a = 10;
let b = 20;

function f(aa = a, b = b) { // 기본값으로 할당하고자 하는 값인 a 가 할당할 aa 와 달라야 한다. a = a(X)
    console.log( aa, b );
}

f( 1, 2 ); // 인자를 전달하고 있기 때문에 디폴트값인 a 를 쳐다보지도 않음.
f( undefined, 2 ); // 첫 인자가 undefined 이기 때문에 디폴트값을 바라보고 에러없이 출력됨.
f( 1 ); // TDZ 로 인해 b is not defined 가 출력
f();

f(1); 을 호출하면  b is not defined 가 출력되는 것은 다음과 같은 이유때문입니다.

js
let b = b;

// 자바스크립트 엔진은 다음과 같은 동작을 수행한다.
let b; // 메모리 주소에 할당하기 전

// b 는 메모리에 참조하기도 전인 상태로
// 즉, 오른쪽의 b 는 아직 참조할 주소가 없는 녀석을 들고 올려고 하니 TDZ 에 걸리는 것임
b = b;

 

6)  arguments 에도 영향을 줄까?

먼저 arguments(유사배열 객체)에 대해 알아보자.

arguments 는 유사배열 객체(array-like object)라고 합니다.
이것은 객체이면서, 각 프로퍼티의 키가 인덱스이고,  length 라는 프로퍼티가 있는 객체를 말합니다.
ES5 때부터 있는 것으로 함수의 인자들을 유사배열 객체로 만들어 주며,  arguments 는 함수의 인자로 받고자 하는 갯수와 상관없이 실제로 넘어온 인자들의 유사배열입니다. 즉, 넘겨준 인자들을 모두 받을 수 있는 객체입니다.

js
const arg = function () {
    console.log( arguments );
};

arg( 1, 2, 3 );
js
 const arg = function () {
    console.log( arguments ); // { 0: 1, 1: 2, 2: 3, length: 3, callee: , ....}

    // 유사배열객체는 실제로 배열이 아니기 때문에 배열 메소드를 사용할 수 없다.
    // # pop() : 맨 마지막 요소를 뽑아주는 메소드
    console.log( arguments.pop() ); // 에러 발생

    // 배열 메소드를 사용하기 위해 배열 메소드를 빌려서 사용
    console.log( Array.prototype.pop.call( arguments ) );

    // 또 다른 방법으로 넘겨온 인자를 배열로 만든 후에 배열 메소드인 pop 을 사용
    const args = Array.prototype.slice.call(arguments);
    console.log( args );
    console.log( args.pop() );

};

arg( 1, 2, 3 );

사실 이제는  arguments 객체를 알아야 할 필요성이 없지만 다음에 다룰  rest parameter 가  arguments 와 유사함과 동시에 이를 대체하기 위한 것이기에 간단히 짚어보았으며, 계속해서 이런 유사배열객체인  arguments 에도 디폴트 파라미터에 영향을 줄 것인가에 대해 다음 코드를 통해 알아보도록 하겠습니다.

js
const a = function(a = 1, b = 2, c = 3) {
    console.log(arguments);
    console.log(a, b, c);
};

a();
a(4);
a(4, 5);
a(4, undefined, 6);
a(4, 5, 6);

a(); 를 호출하면 인자로 넘겨준 값은 없지만 디폴트 파라미터가 정의되어 있기 때문에 과연  arguments 에도 기본값이 정의되어 있을까를 확인해 보았더디 그 어떤 값도 확인할 수 없습니다.
즉,  arguments 는 실제로 값을 넘겨준 것에만 종속되어 있는 것입니다.
이 내용에 대해 깊이 알아야 필요도 활용할 일도 거의 없기 때문에 이러한 객체가 있다라는 정도로만 알고 넘어가 보자.

 

 

 

rest parameter(나머지 매개변수)

rest 는 사전적 의미로 나머지로서  ~~를 제외한 나머지라는 의미로 해석할 수 있습니다.

먼저 기존  ES5 시절의 코드를 살펴보자.

js
function foo (a, b) {
    a = 1;
    arguments[0] = 2;
    console.log(a, arguments[0]);
}

foo(10, 20);

위 로그를 확인해 보면  arguments 객체가 변수 a 에 영향을 준 것을 확인할 수 있습니다.
다시 말해,  arguments 는 인자에는 영향을 준다는 것입니다.

이렇게  arguments 객체는 위에서 알아본 바와 같이 불확실성이 높고 유사배열 객체이기 때문에 실제 배열의 메소드를 사용하기 위해서는 프로토타입에 있는  call,  apply 를 사용해야 하는 등 난이도 높은 코드를 요구하기에 불편한 점이 많습니다.

그래서 이러한 에로사항을 해소해 주기 위해 나온 것이  rest parameter 라고 불리는 녀석입니다.

 

다음의 코드를 먼저 살펴보도록 하겠습니다.

js
function f (x, y) {
    var rest = Array.prototype.slice.call(arguments, 2);
    console.log(rest);
}

f(1, 2, true, null, undefined, 10);

위 코드에서 함수는 본래 받으려고 하는 인자가 2개뿐 인데도 불구하고 값을 여러 개 넘겨줬을 때 이러한 인자들을 제어하기 위해서는 기존에는 위에서 알아본 arguments 객체를 사용해야 했지만 아래 코드와 같이 여러 인자를 넘겨받을 수 있습니다.

js
function f (x, y, ...z) {
    console.log(z);
}

f(1, 2, true, null, undefined, 10);

위 코드를 확인해 보면  x,  y 를 제외한 나머지 매개변수들을 모두 넘겨받는 것을 확인할 수 있고 이 넘겨받은 타입은 순수 배열 객체라는 점입니다.
즉, 넘겨 받은 나머지 인자들을 취합하여 배열로 만들어주는 것입니다.

그리고  arguments 를 대체하려면 다음과 같이 작성할 수 있습니다.

js
function f (...z) {
    console.log(z);
}

f(1, 2, true, null, undefined, 10);

여기서 주의할 사항은 나머지는 맨 마지막이기 때문에 매개변수를 정의할 위치도 맨 마지막이어야 하며, 오직 한 번만 사용할 수 있습니다.

js
function fn(a, b, c, ...z) {
    console.log( z );
}

fn( 1, 2 );
js
function fn(a, ...z) {
    console.log( z );
}

fn( 1, 2 );

 

 

Detail Info

1)  ...[매개변수명]

2)  오직 한 번, 매개 변수의 가장 마지막에서만 사용

js
const f = function (_first, ...rest, _last) {
  console.log(_first, _last)
}
f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

 

3)  객체의  setter 에서 사용할 수 없다.

js
let person = {
    name: 'name',
    age : 30,
    get personInfo(){
        return this.name + ' ' + this.age
    },
    set personInfo (...val) {
        this.name = val[0];
        this.age  = val[1];
    }
};
console.log(person.personInfo);

 

게터와 세터

js
const obj = {
    _a : 'a',
    get a () { return this._a; },
    set a (v) { this._a = v; }
};

obj.a = 10;
console.log( obj.a );
console.log( obj );

obj.a = 10; 을 하면 세터가 동작하여  _a 에 10 을 할당하게 되고,  console.log( obj.a ); 출력하라고 하면 게터가 동작하여  _a 값을 출력합니다.
그리고  obj 에는  {_a: 10} 를 확인할 수 있습니다.

세터는 하나의 프로퍼티에 대해서 전달받은 하나의 프로퍼티 값만을 지정하는 역할을 합니다.
그리고 하나의 프로퍼티 키인 여기서는  _a 에서 하나의  value 밖에 할당할 수 밖에 없는 구조입니다.
배열이라고 해도 배열 전체를 하나(통)로 할당할 수 밖에 없습니다. 즉, 하나의 키에는 하나의 값만 할당할 수 있습니다.
그렇기 때문에 세터에는 값을 여러 개를 정의할 수 없으므로 나머지 매개변수를 사용할 수 없는 것입니다.

설령 세터에  set a (x, y) { this._a = v; } 이와 같이 두 개를 지정한다고 해도 본래부터 하나의 인자만 받도록 설계되어 있기 때문에 에러가 발생합니다.

 

4)  arguments를 대체한다.

js
function argsAlternate (...args) {
    console.log(args.length, arguments.length);
    console.log(args[0], arguments[0]);
    console.log(args[args.length - 1], arguments[arguments.length - 1]);
    args[1] = 10;
    arguments[1] = 20;
    console.log(args[1], arguments[1]);
}

argsAlternate(1, 2, 3, 4);

 

 

 

 

spread operator(펼치기 연산자, 전개 연산자)

펼치기 연산자, 전개 연산자란 용어는 ES6 를 소개하는 책에서도 제각각이며, 아직까지 국내에서는 정식 명칭이 정의되지 않았기 때문에 의사 소통을 원할하게 하기 위해서라도 원문 그대로 사용하실 것을 권장합니다.

 

먼저 다음의 코드를 살펴보도록 하겠습니다.

js
var birds = ['eagle', 'pigeon'];
var mammals = ['rabbit', 'cat'];

var animals = birds.concat('whale').concat(mammals);

console.log(animals)

기존 ES5 해서  concat 메소드를 활용하여 하나의 배열로 만들었다면,  ES6 에서는 다음과 같이 할 수 있습니다.

js
var birds = ['eagle', 'pigeon'];
var mammals = ['rabbit', 'cat'];

const animals = [...birds, 'whale', ...mammals];

console.log(animals);

로그를 확인해 보면  concat 을 사용한 결과와 동일합니다. 즉, 이  spread operator 는 펼쳐주는 연산자입니다.

 

Detail Info

1)  배열의 각 인자를 펼친 효과

js
const values = [20, 10, 30, 50, 40];
console.log(20, 10, 30, 50, 40);
console.log(...values);

콤마(,)로 구성되어 있는 것들은 모두 펼치기 연산자를 사용할 수 있습니다.

 

아래에서 펼치기 연산자를 활용한 응용 예제를 살펴보도록 하자.

js
const values = [20, 10, 30, 50, 40];

console.log(Math.max(20, 10, 30, 50, 40));
console.log(Math.max.apply(null, values));
console.log(Math.max(...values));

Math 객체의  max 메소드는 인자의 갯수를 한정짓지 않고 많이 받을 수 있습니다. 많이 사용하는 방법이 숫자로 구성된 배열을 가지고  max 메소드를 사용하곤 하는데 기존  ES5 에서는  apply 메소드를 사용하여 값을 얻었지만  ES6 에선 위와 같이  ...values 펼쳐주기만 하면 되기 때문에 apply 같은 메소드를 사용하지 않고 간결하게 작성할 수 있습니다.

 

2)  앞뒤로 다른 값들을 함께 사용할 수도 있다.

js
const values = [3, 4, 5, 6, 7, 8];

// 인자의 갯수를 상관하지 않고 총합을 구하기 위해 나머지 매개변수를 사용
const sum = function (...args) {
  return args.reduce(function (p, c) {
    return p + c;
  })
};

console.log(sum(1, 2, ...values, 9, 10)); // 값을 펼쳐서 주는 입장

 

rest parameter 와 spread operator 의 차이점

rest parameter 는 오직 인자의 맨 마지막에서만 사용할 수 있으며, 인자는  받는 입장인 반면에
spread operator 는 어떤 갖고 있는 정보를 펼쳐서 어떤 값은 이거다..라고 펼쳐서 주는 입장이기 때문에 사용하는 위치가 자유롭습니다.

  • getter : 나머지 / 받는 입장
  • setter : 펼치기 / 주는 입장

 

3)  iterable 한 모든 데이터는 펼칠 수 있다.

iterable 은  "반복할 수 있는" 이라는 사전적 의미를 가지고 있는데, 의미 그대로 반복할 수 있는 데이터를 펼칠 수 있다라고 볼 수 있습니다.

다시 말해, 배열처럼 인덱스가 있고 인덱스마다 값들이 할당이 되서 그것들을 순회하면서 하나하나 처리할 수 있는 것을 iterable 하다라고 합니다.

js
const str = 'Hello!';

console.log( str[0] ); // H
console.log( str[2] ); // l

// ES5
console.log( str.split( '' ) );

// ES6
console.log( [...str] ); // 펼친 것을 배열로 받음
console.log( ...str );

 

4)  push,  unshift,  concat 등의 기능을 대체할 수 있다.

js
let originalArr = [2, 3];
const preArr    = [-2, -1];
const sufArr    = [6, 7];

var a = originalArr.unshift(1); // 원본 배열
console.log( a, originalArr ); // 3, [1, 2, 3]

// originalArr = originalArr.unshift(1)
// 할당을 하면 원본 배열이 망가짐(?), 즉 배열이 아닌 length 가 출력

originalArr.push(4); // 원본 배열
console.log( originalArr ); // [1, 2, 3, 4]

originalArr = [0, ...originalArr, 5]; // 새로운 배열
console.log(originalArr);

const concatArr = preArr.concat(originalArr, sufArr);
const restArr = [...preArr, ...originalArr, ...sufArr];
console.log(concatArr, restArr);

 

5)  "새로운" 배열이다.

js
let originalArray = [1, 2];
let copiedArray = [...originalArray];
console.log(originalArray === copiedArray);

originalArray.push(3);
console.log(originalArray);
console.log(copiedArray);

 

6)  "얕은 복사"만을 수행한다.

js
let originalArray = [{
    first: 'Hello,',
    second: 'World!'
}, {
    first: 'Welcome',
    second: 'ES6!'
}];

let copiedArray = [...originalArray];
console.log( copiedArray === originalArray );
console.log(originalArray[0].first);

copiedArray[0].first = "Hi,";
console.log(originalArray[0].first);

// 원본과 카피배열은 같은 곳을 참조한다.
// 즉, 참조가 바뀌어 있지 않은 것(얕은 복사)이다.
console.log( originalArray[0].first );
console.log( copiedArray[0].first );

 

 

tc39 propersals

tc39 propersals는  ECMAScript 를 제정하는 위원회입니다.
ECMAScript 라는 단체에 소규모의 위원회들이 있는데  39 넘버의 이름을 가지고 있는 위원회가 자바스크립트에 대한 표준 규약을 정의하는 집단입니다.
즉,  tc39 라고 불리는 위원회가  ECMAScript 의 다음 버전에는 어떠한 내용들이 추가되는 지를 정하는 단체입니다.
보통 ES6es2015 라고도 부릅니다. 그런데 ES3 는 199x년대, ES5 는 2009년대에 릴리즈되었는데 tc39 가 자바스크립트를 2015년에 대대적으로 업데이트하면서 매년 업데이트(ES6(2015), ES7(2016. 6.), ES8(2017. 6), ES9(2018. 6.), ES10(2019. 6))를 하기로 하면서 이 6과 15, 7과 16... 숫자의 매칭이 되지가 않자 오랜 기간의 침묵을 깨고 대대적인 업데이트를 한 ES6는 es2015 라고 둘다 통용되는 명칭으로 하고 그 이후부터 매년 업데이트되는 버전의 정식 명칭은 es2016, es2017, es2018, es2019 라고 부르기로 정하였습니다.
es2015(ES6) 는 프로그래밍 언어(language)로서의 자격을 갖추면서 엄청난 변화를 가져왔지만 매년 큰 변화를 줄 수는 없기 때문에 차츰차츰 조금씩 추가하기로 결정했습니다.

그리고 추가하려는 내용들을 전세계 사용자들에게 제안을 받고 다음과 같은 단계를 거치게 됩니다.

  • stage 0  :  사용자들에게 "이 기능을 추가해주세요" 제안을 받는다.
  • stage 1  :  검토
  • stage 2  :  구체화
  • stage 3  :  도입 예정
  • stage 4  :  도입 확정 -> 확정이 되면 대개 그 다음해에 기능이 추가된다고 볼 수 있다.

stage 4는  Finished Proposals 단계로 도입이 확정되면 그 다음해나 다음다음해에 기능이 추가되지만 그 이전 단계인 1단계부터 3단계까지는 언제 추가될지 미정이지만 언제가는 될 가능성이 높은 기능들입니다.

 

 

 

 

Jaehee's WebClub

 


댓글을 달아 주세요