ES5 간단히 보기

Hello자바스크립트 책에는 지인짜로 자바스크립트라는 언어가 기본적으로 쓰이는 정말 간단한 문법만 소개를 해놔서, 이것만 보고 바로 React, ES6를 본다는 것은 현실적으로 난이도가 있을 것 같아.

그래서 나도 잘 모르지만… ES5를 내 나름대로 요약해서 중요한 것만 심플하게 보고 갈 수 있도록 준비해보려해. 부족한 부분이 있거나 잘 모르겠는 부분있으면 내가 알고 있는한 잘 알려줄 수 있도록 하고, 내가 모른다면 이후에 알아와서 잘 알려줄 수 있도록 노력할게!

원시타입과 객체

자바스크립트에는 원시 타입과 객체 타입이 있다.

원시 타입

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol

객체 타입

원시 타입을 제외한 나머지는 모두 객체이다.

즉, Object(객체), Function도 객체이다. 여기서 함수도 객체라는 말은 매우 중요!!

파이썬을 해봤다면 알 수도 있는 개념인데, 함수도 객체가 될 수 있다는 의미는 일급 객체라는 말과 똑같다. 일급 객체란, 함수에 파라미터로 함수가 들어갈 수 있고, 함수를 리턴할 수도 있다는 뜻이다. 그리고 객체 안이나 배열 내부에서도 함수, 객체를 넣는 것이 가능하다.

원시 타입과 객체 타입의 차이

원시 타입은 변수에 할당이 될 때, 그 값 그대로 변수에 저장이 된다. 이를 call-by-value라고 한다. 반면, 객체는 변수에 할당되면 해당 객체가 저장되어 있는 메모리 주소를 변수에 할당받게 되는데, 그래서 객체를 할당 받은 변수는 객체의 메모리 주소를 참조하고 있다고 얘기한다. 이를 call-by-reference라고 한다.

호이스팅

호이스팅이란, es5까지 사용되었던 키워드 varfunction에만 적용되는 말이라 할 수 있겠다. 아 다시, 블록 스코프가 아닌 함수 스코프에만 호이스팅 개념이 적용된다고 할 수 있겠다.

호이스팅이란, 모든 선언문이 해당 스코프의 맨 앞으로 옮겨진 것처럼 동작하는 특성을 얘기한다. 자바스크립트는 모든 선언문이 선언되기 이전에 참조 가능하도록 되어있다.

함수 호이스팅

함수 선언문으로 정의된 함수의 경우, 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO에 저장한다. 즉, 함수의 경우, 초기화, 선언, 할당이 한번에 이루어져 함수 선언문의 경우엔 함수 선언 위치와는 관계없이 소스 내 어디서든지 호출할 수 있다.

var res = square(5);

function square(number) {
  return number * number;
}

변수 호이스팅

console.log(a); // undefined
var a = 5;
console.log(a); // 5

키워드 var를 사용한 경우, 변수 호이스팅이라는 것이 발생하는데, 변수 호이스팅은 var 키워드로 사용된 변수가 자바스크립트 엔진이 스크립트를 로딩시키는 시점에 바로 초기화하는데, 이때 함수 호이스팅과 차이점은 VO에 변수를 바로 할당하지 않고, runtime에서 해석되고 실행되도록 되어있다.

그래서 변수 호이스팅의 경우, 초기화 및 변수명 선언만 먼저 되고 할당은 자바스크립트 엔진이 스크립트를 읽면서 진행하기 때문에 변수 선언 및 할당을 한 그 위치에서 할당이 된다.

여기서 함수 표현식이라는 개념도 짚고 가자. 함수 표현식이란 익명 함수 선언문을 변수로 할당하여 사용하는 것이다.

// 함수 표현식
var square = function(number) {
  return number * number;
};

이 아이는 함수지만, 함수를 변수에 할당하였으므로 변수 호이스팅에 적용된다. 그래서 초기화, 변수 선언만 먼저 되고, 자바스크립트 엔진이 스크립트를 읽을 때 해당 위치에서 동시에 할당이 되어 그 뒤에서부터 사용할 수 있게 된다.

함수를 생성할 때, 두 가지 방법 모두 사용해도 무관하지만, 웬만해서는 스코프 위치를 헷갈리게 하지 않도록 함수 표현식의 방법으로 함수를 만들 것을 권장한다.

스코프

var x = 'global';

function foo () {
  var x = 'function scope';
  console.log(x);
}

foo(); // ?
console.log(x); // ?

스코프와 변수의 구분

  • 전역 스코프 : 코드 어디에서든지 참조할 수 있다. (window)
  • 지역 스코프 : 함수 코드 블록이 만든 스코프이다. 함수 자신 및 하위 함수에서만 참조할 수 있다.
  • 전역 변수 : 전역(window)에서 선언된 변수이고, 어디에서든 참조할 수 있다.
  • 지역 변수 : 함수 내에서 선언된 변수이며, 해당 함수와 그 하위 함수에서만 참조할 수 있다.

자바스크립트의 스코프

보통 스코프는 블록 레벨 스코프를 따른다. 블록 레벨 스코프란 단순히 코드 블록 내에서 독립적인 스코프가 생긴다는 것을 의미한다.

하지만 자바스크립트는 함수 레벨 스코프를 따른다. 함수 레벨 스코프란, 함수 코드 블록 내에서 독립적인 스코프가 생긴다는 것을 의미한다.

ES6에서 나온 let, const를 사용하는 경우, 이는 블록 레벨 스코프를 따른다.

if (true) {
  var x = 5;
}
console.log(x); // 5

위 같은 경우, 변수 x는 코드 블록 내에서 선언되었지만, 자바스크립트는 함수 레벨 스코프를 따르므로 함수 밖에서 선언된 변수는 전역 스코프를 가지게 된다. 그래서 x는 전역 변수이다.

var x = 'global';

function foo() {
  var x = 'local';
  console.log(x);
}

foo();          // local
console.log(x); // global

여기서 전역 변수로 x를 선언하였지만, 함수 내부에서 한 번 더 중복 선언하였으므로, 함수를 실행하여 찍힌 console의 경우엔 지역 변수 x가 우선적으로 검색되어 local이 뜬다. 그리고 함수 실행이 끝난 후 찍히는 console은 함수의 스코프가 끝났으므로 지역 변수가 검색되지 않고, 전역으로 검색되어 global이 뜬다. (실행 컨텍스트의 스코프 체인으로 인한 참조 검색)

자꾸 전역변수를 예로 보여줘서 착각할 수 있는 부분은, 전역 변수 사용은 무조건 피하는 것이 좋다. 변수는 무조건 함수 내부에서만 선언, 할당하여 사용하도록 하는 것을 권장한다. 전역참조가 된다는 것은 좋지 않다.

렉시컬 스코프

var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?

위의 문제의 포인트는 bar의 상위 스코프가 무엇이냐에 따라 나뉜다. 상위 스코프를 결정하는 기준을 두 가지가 있다.

  1. 함수를 어디서 호출하였는가.
  2. 함수를 어디서 선언하였는가.

함수 호출 방식으로 스코프를 결정하는 것을 동적 스코프라고 하고, 선언 방식으로 스코프를 따르는 경우 렉시컬 스코프(정적)라고 한다. 자바스크립트는 다른 언어들과 같이 렉시컬 스코프를 따른다.

렉시컬 스코프는 함수를 어디에 선언하였는지에 따라 결정된다. 그래서 함수를 어디서 실행했느냐와는 관계없이 bar 함수를 선언한 시점으로 상위 스코프가 결정된다.

Screen Shot 2020-02-09 at 6 04 00 PM

var x = 1;

function foo() {
  var x = 10;
  function bar() {
    console.log(x);
  }
  bar();
}

foo(); // ?
bar(); // ?

위의 경우, bar 함수가 foo 함수 내부에서 선언되었기 때문에 foo 함수의 실행 결과는 10이지만, foo()를 빠져나온 bar()함수의 실행은 전역 스코프에 있는 x만 참조할 수 있기 때문에 1이 된다.

Screen Shot 2020-02-09 at 6 15 42 PM

참고