2019-07-05 ES6(1) - const,let, 템플릿문자열, 객체리터럴 확장

var의 특징

  1. 함수레벨 스코프
    • 함수의 코드 블록만 스코프로 인정.
    • for 문의 변수 선언문에서 선언한 변수를 for문의 코드 블록 외부에서 참조가능….
  2. var키워드 생략가능 : 암묵적 전역변수 양산가능
  3. 변수 중복 선언 허용 : 의도치않은 변수값 변경
  4. 변수 호이스팅 : 변수선언 이전에 참조가능.

대부분의 문제는 전역변수로 인해 발생한다. 전역변수는 유효범위(scope)가 넓어서 어떻게 사용되는지 파악하기 힘들다. ES6에서는 let, const의 도입을 했다.

let

  • 대부분의 언어는 블록레벨 스코프를 따르지만 JS는 함수레벨 스코프를 사용했다.

함수레벨 스코프 : 함수 내에 선언된 변수는 함수내에서만 유효. 함수 외부에서 선언된 변수는 모두 전역변수이다. 블랙레벨 스코프 : 모든 코드블록(함수,if문, for문) 내에서 선언된 변수는 코드블록 내에서만 유효. 코드블록 외부에서 참조불가. 코드블록 내부에서 선언된 변수는 지역변수이다.

// Functional Scope
var foo = 123; // 전역 변수
console.log(foo); // 123
{
  var foo = 456; // 전역 변수
}
console.log(foo); // 456


// Block Scope
let foo = 123; // 전역 변수

{
  let foo = 456; // 지역 변수
  let bar = 456; // 지역 변수
}
console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined
  • let은 블록스코프라 bar가 지역변수로 취급이 된다. 따라서 error가 발생.

변수 중복 금지

var foo = 123;
var foo = 456;  // 중복 선언 허용

let bar = 123;
let bar = 456;  // Uncaught SyntaxError: Identifier 'bar' has already been declared
  • var는 변수 중복이 되지만 let은 안된다.

호이스팅

console.log(foo);
var foo;

console.log(bar); // Error : Uncaught ReferenceError : bar is not defined
let bar;
  • JS는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)를 호이스팅한다. 하지만 let키워드로 선언된 변수는 선언문 이전에 참조하면 에러가 발생한다.

변수생성단계

선언단계 : 변수를 실행컨텍스트에 등록한다. 변수객체는 스코프가 참조하는 대상이 된다. 초기화단계 : 변수객체(Variable Object)에 등록된 변수를 위한 메모리를 확보. 이때 변수는 undefined로 초기화 할당단계 : undefined로 초기화된 변수에 실제값을 할당한다.

  • var로 선언된 변수는 선언, 초기화단계가 한번에 이루어진다. 그렇기때문에 선언문 이전에 변수를 접근하여도 스코프에 변수가 존재해서 undefined라서 에러가 발생하지 않는다. 이후 변수할당문에 도달하면 값이 할당된다. 이를 변수호이스팅
/ 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(foo); // undefined

var foo;
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

var 변수 초기화 과정

  • let은 선언과 초기화가 분리되서 진행된다. 스코프에 변수를 등록하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다. 초기화 이전에 변수에 접근하면 참조에러가 발생.

let 변수 초기화 과정

let foo = 1;
{
	console.log(foo) // ReferenceError
    let foo = 2; // local variable
}

전역객체와 let

  • 전역객체(Global Object)는 모든 객체의 최상위 객체이다. 브라우저에서는 Window, Server-Side(Node.js)에서는 global객체를 의미한다.
  • var키워드로 선언된 변수를 전역 변수로 사용하면 전역 객체의 프로퍼티가 된다.
var foo = 123;
console.log(window.foo); // 123

let bar = 123;
console.log(window.bar) // undefined;
  • let 전역변수는 전역 객체의 프로퍼티가 아니다. window.foo처럼 접근할 수 없다.
  • 번외 nodejs global.foo, global.bar는 undefined이다.

const

  • cosnt는 상수로 보통사용하지만 다른 용도로도 사용한다.
  • let은 재할당이 되지만 const는 재할당이 안된다.
  • const는 반드시 할당과 선언이 같이 이루어져야한다.
const FOO = 123;
FOO = 456; // TypeError: Assignment to constant variable.

const FOO; // SyntaxError: Missing initializer in const declaration

const와 객체

  • const변수 타입이 객체인 경우, 객체에 대한 참조를 변경 못한다. 재할당은 불가능하지만, 할당된 객체의 내용(프로퍼티의 추가, 삭제, 프로퍼티값의 변경)은 변경할 수 있다.
const user = { name: 'Lee' };

// const 변수는 재할당이 금지된다.
// user = {}; // TypeError: Assignment to constant variable.

// 객체의 내용은 변경할 수 있다.
user.name = 'Kim';

console.log(user); // { name: 'Kim' }
  • 객체 내용이 변경되도, 주소값은 변하지 않는다.
  • 객체 타입 변수선언에는 const가 좋다.

템플릿 리터럴

  • 템플릿리터럴은 백틱(`)을 쓴다.
  • 문자열에서 공백을 표현하기 위해 \를 사용하는데, 템플릿 리터럴 내의 모든 white-space는 있는 그대로 적용이 된다.
const template = `<ul class="nav-items">
  <li><a href="#home">Home</a></li>
  <li><a href="#news">News</a></li>
  <li><a href="#contact">Contact</a></li>
  <li><a href="#about">About</a></li>
</ul>`;

console.log(template);
  • +연산자를 사용하지 않아도 새로운 문자열 삽입이 된다.
const first = 'Ung-mo';
const last = 'Lee';

// ES5: 문자열 연결
console.log('My name is ' + first + ' ' + last + '.');
// "My name is Ung-mo Lee."

// ES6: String Interpolation
console.log(`My name is ${first} ${last}.`);
// "My name is Ung-mo Lee."

객체리터럴 확장

//ES5
var x = 1, y = 2;
var obj = {
  x: x,
  y: y
};
console.log(obj); // { x: 1, y: 2 }

// ES6
let x = 1, y = 2;
const obj = {x, y};
console.log(obj);

// 객체 리터럴 var로도 적용되는지 확인
var a = 11, b = 22;
var obj2 = { a, b};
console.log(obj2);
// result
{ x: 1, y: 2 }
{ a: 11, b: 22
  • ES6에서 프로퍼티 값으로 변수를 사용하는 경우, 프로퍼티 이름을 생략할 수 있다. 이때 프로퍼티 이름은 변수의 이름으로 자동생성된다.

프로퍼티키 동적 생성

  • 문자열 or 문자열로 변환 가능한 값을 반환하는 표현식을 사용해 프로퍼티 키를 동적으로 사용할 수 있다. 단 표현식을 대괄호로 묶어야한다.
// ES5
var prefix = 'prop';
var i = 0;
var obj = {};
obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;

// ES6
const prefix = 'prop';
let i = 0;

const obj = {
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i
};
console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}

메소드 축약표현

// ES5
var obj = {
  name: 'Lee',
  sayHi: function() {
    console.log('Hi! ' + this.name);
  }
};

obj.sayHi(); // Hi! Lee

// ES6
const obj = {
  name: 'Lee',
  // 메소드 축약 표현
  sayHi() {
    console.log('Hi! ' + this.name);
  }
};

obj.sayHi(); // Hi! Lee
  • ES6에서는 메소드를 선언할 때, function 키워드를 생략한 축약 표현을 사용할 수 있다.

__proto__ 프로퍼티에 의한 상속

  • es5에서 객체 리터럴을 상속하기 위해서는 Object.create()함수를 사용했다. 이를 프로토타입 패턴 상속이라 한다.
// ES5
var parent = {
  name: 'parent',
  sayHi: function() {
    console.log('Hi! ' + this.name);
  }
};

// 프로토타입 패턴 상속
var child = Object.create(parent);
child.name = 'child';

parent.sayHi(); // Hi! parent
child.sayHi();  // Hi! child
  • es6에서는 객체 리터럴 내부에서 __proto__를 직접 설정할 수 있다. 객체리터럴에 의해 생성된 객체의 __proto__프로퍼티에 다른 객체를 직접 바인딩하여 상속을 표현할 수 있다.
// ES6
const parent = {
  name: 'parent',
  sayHi() {
    console.log('Hi! ' + this.name);
  }
};

const child = {
  // child 객체의 프로토타입 객체에 parent 객체를 바인딩하여 상속을 구현한다.
  __proto__: parent,
  name: 'child'
};

parent.sayHi(); // Hi! parent
child.sayHi();  // Hi! child

오늘의 느낀점

JS가 깊어짐에 따라 축약이나 새로운 문법 등 다양한 어려움에 부딪치고 있다. 깊이 원리를 이해하기 위해 기초를 계속 보고 있다. 조금씩 조금씩 꾸준히 나아가자. 불안하더라도 묵묵히 나아가자 Keep Pushing :)

Written on July 5, 2019