(JavaScript) ES6 문법

duo2208·2022년 6월 5일
0

Language

목록 보기
2/3
post-thumbnail

상수/변수 선언


자바 스크립트에서 변수 생성은 선언 → 초기화 → 할당 단계를 거친다.

  • 선언 : 변수명을 등록하여 자바스크립트 엔진에 변수의 존재를 알린다.
  • 초기화 : 변수를 위한 메모리 공간을 확보한다. 이 단계에서 변수는 undefined 로 초기화 된다.
  • 할당 : undefined로 초기화된 변수에 실제 값을 할당한다.

변수를 선언하는 키워드로는 var, const, let 가 있다.

var/const/let


›› var

  • 재선언 가능.
  • 선언단계와 초기화 단계가 한번에 이루어진다.
""" 변수의 재선언 """
var name = 'javascript'
console.log(name)		// output >> javascript

var name = 'react'
console.log(name) 		// output >> react

›› const

  • 객체와 함께 사용할 때를 제외하고는 변경이 불가능한 변수. 상수로 사용.
  • 반드시 선언과 초기화를 동시에 진행해야 한다.
  • 재선언 불가능.
  • 재할당 불가능. (원시값은 불가능하지만, 객체는 가능하다.)
  • 내부 속성값은 수정 가능.
""" 원시값의 재할당 """
const name = 'python'
name = 'javascript'		// output >> Uncaught TypeError: Assignment to constant variable.

""" 객체의 재할당 """
const name2 = {
  eng: 'django',
}
name2.eng = 'spring'
console.log(name2)		// output >> { eng: 'spring' }  

›› let

  • Lexical Variable Scoping 을 지원한다.
  • 선언단계와 초기화 단계가 분리되어 실행된다.
  • 재선언 불가능.
  • 재할당 가능.
""" 원시값의 재할당 """
let name = 'python'
name = 'javascript'		
console.log(name)	// output: javascript

💡 ES6부터는 constlet 을 사용하도록 권장하고 있다.

  • 재선언이 가능한 var을 사용하면 필요할 때마다 변수를 유연하게 사용할 수 있다는 장점이 있지만, 코드가 늘어날 수록 기존 변수의 존재를 잊고 값을 재할당하여 예기지 못한 값을 반환시킬 수 있다.
  • 따라서 재할당이 필요한 경우에 한정해 let 을 사용하고,
  • 기본적으로는 const를 자주 사용한다. const를 사용하면 의도치 않은 재할당을 방지해 주므로 보다 안전하다.

나아가 자바스크립트의 hoistingscope 개념을 살펴보면 왜 이 둘을 사용해야 좀 더 자세히 알 수 있다.



호이스팅(hoisting)?


자바스크립트 엔진은 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 호이스팅(hoisting) 이라는 특징을 가지고 있다. 주로 "변수의 선언과 초기화를 분리한 후, 선언만이 코드의 최상단으로 옮겨진 것 같은 = [변수 선언의 끌어올림]" 으로 이해하곤 한다.

›› var

  • var 로 선언한 변수의 경우 hoisting시 undefined 로 출력된다.
  • 다만, 선언 없이 초기화만 존재하는 경우는 hoisting 이 존재하지 않는다. 따라서 ReferenceError 예외가 발생한다.
""" 1. 선언 및 초기화를 먼저 하고, 변수를 사용할 때  """
var num = 6			// 선언 및 초기화
console.log(num)	// output >> 6
""" 2. 변수를 먼저 사용하고, 그 후에 선언 및 초기화가 나타날 때 """
console.log(num)	// output >> undefined
var num = 6			// 선언 및 초기화
console.log(num)	// output >> undefined
var num = 6				// 선언 및 초기화
console.log(num)	// output >> 6

변수 선언부 var num 은 끌어올리지만 대입부 num = 6 는 끌어올리지 않으므로 결과값이 다르다.

""" 3. 선언없이 초기화만 존재하는 경우 """
console.log(num)	// output >> ReferenceError
num = 6				// 초기화

›› const

const로 선언한 변수도 hoisting 대상이지만, 다른 방식으로 작동한다.

const선언 단계와 초기화가 동시에 이루어져야만 런타임에서 실행될 수 있다는걸 기억하자. 따라서 hoisting 으로 인해 변수 선언이 되더라도 초기화가 진행되지 않았으므로 에러를 접할 수 밖에 없다.

""" 선언 되고 나서야, 해당 변수를 사용할 수 있다. """
console.log(num)	// output >> ReferenceError: Cannot access 'num' before initialization
const num = 6		// 선언 및 초기화

›› let

let으로 선언한 변수도 hoisting 대상이지만, 다른 방식으로 작동한다.

let으로 선언한 변수는 초기화하기 전에는 읽거나 쓸 수 없는 시간상 사각지대 (Temporal Dead Zone, TDZ)가 존재한다. 따라서 초기화 전에 접근을 시도하면 ReferenceError가 발생한다.

""" 시간상 사각지대가 존재한다. """
console.log(num)	//  output >> Uncaught ReferenceError: num is not defined
let num = 6

정리하자면,

  • var로 선언된 변수는, hoisting 되어 선언과 초기화가 동시에 이뤄지기 때문에 undefined를 반환한다. 이러한 hoisting 이 안티 패턴이 되어 오류 예측을 어렵게 하는 경우가 있다.
  • let 또는 const 로 변수를 선언하는 경우 hoisting 이 발생하기는 하지만, 값을 참조할 수 없기 때문에 동작하지 않는 것처럼 보인다.
  • var의 hoisting 안티 패턴을 피하기 위해 letconst를 사용한다.



스코프(scope)?


자바스크립트에서 함수는 선언되는 동시에(lexical) 자신만의 scope(범위)를 가진다. 그래서 자바스크립트에서 함수가 유효한 범위를 가질때 lexcial scope라고 한다.

scope에 따라 비슷해 보이는 코드도 전혀 다른 동작을 할 수도 있다. 아래 예시 코드의 경우 var/let 사용여부에 따라 alert 메시지가 달라진다.

  var div;
  var container = document.getElemetsByTagName('body')[0]:
  for(let i=0; i<5; i++) {
      div = document.createElement('div');
      div.onclick = function() {
          alert("clicked : #" + i);
      };
      div.innerHTML = "#" + i;
      container.appendChild(div);
  }



비구조화 할당 (destructuring)


  • 객체와 배열로 부터 속성을 쉽게 꺼낼 수 있는 문법이다. 코드의 가독성이 보다 좋아진다.

배열 비구조화

let [name] = ["Evan", 38, "Seoul"]
let [name, age, region, height] = ["Evan, 38, "Seoul"]		// height에 undefined 할당
let [name, age, region, height=180] = ["Evan, 38, "Seoul"]	// height에 default값 180 할당

functuion get_default_height() {
	console.log("get_default_height() 호출")
    return 180;
}
// default값 할당이 필요할 때, 함수가 호출된다
let [name, age, region, get_default_height()] = ["Evan", 38, "Seoul]	

객체 비구조화

const evan = {
  name: "Evan",
  age: 38,
  region: "Seoul"
};

""" better case """
// 객체에서 필요한 값들만 뽑아낸다. height의 경우는 undefined.
const {age, name, height} = evan;	

""" avoid case """
const age = evan.age;
const name = evan.name;
const height = evan.height;
const user = [
  {name: "Steve", age: 19, region: "Hawkins"},
  {name: "Nancy", age: 19, region: "Hawkins"},
];

""" better case """
for (const {name, age} of people) {
  	console.log(name, age);
}

""" avoid case """
for (const person of people) {
 	console.log(person.name, person.age); 
}



전개 연산자 (spread operator)


  • 배열, 문자열, 객체 등 반복 가능한 객체 (iterable object)를 개별 요소로 전개 하는 문법이다.

React에서는 수많은 값들을 불변객체(immutable variable)로서 처리한다. 불변이라 함은 어떤 값을 직접적으로 변경하지 않고 새로운 값을 만들어내는 것이다.

spreat operator 연산의 결과는 원본 구조를 유지하되 새로운 객체를 반환하기 때문에 React의 불변성을 유지한다. 그래서 React에서 state 의 특정 부분만 변화시키거나, 최초의 상태를 유지하며 데이터를 추가하는 등의 경우에 사용한다고 한다.

""" 배열 리터럴의 전개 """
let evan = {
	name: "Evan",
    age: 38
    region: "Seoul"
}

let steve = {
	...evan,		// 배열 복사. steve는 ["Evan, 38, "Seoul"]
    name: "Steve"	// 속성명이 중복될 경우 마지막 값이 남는다. steve는 ["Steve", 38, "Seoul"]
};



화살표 함수 (arrow function)


  • return 을 사용하지 않아도 계산한 값을 자동으로 반환한다.
  • 인자가 1개인 경우는 소괄호를 생략할 수 있다.
// function case 1
function f1(x, y) {
  return x + y;
}

// function case 2
const fn2 = function(x, y) {
  return x + y;
};

// function case 3 : arrow function 을 가장 많이 사용한다.
const fn3 = (x, y) => x + y;
  • thisarguments 를 바인딩하지 않는다.

general function의 경우, 함수를 선언할 때 함수가 어떻게 호출되었는지에 따라 this 에 바인딩할 객체가 동적으로 결정된다.

ES6의 arrow function는 기존과는 다른 방식을 가진다. 함수를 선언할 때 this 에 바인딩할 객체가 정적으로 결정되는 것이다.

var evan = {
  name: "Evan",
  
  """ general function case : 상위 스코프와 같은 this """
  print1: function() {
    console.log(`[print1-1] name: ${this.name}`);
    (function() {	// / function 내에서 새로운 this를 만든다
      console.log(`print1-2] name: ${this.name}`);	// 1-1과 다른 this를 가리킨다.
    })();
  },
  
  """ general function case : 상위 스코프와 다른 this """
  print2: function() {
    console.log(`[print2-1] name: ${this.name}`);
    var me = this;	// function 생성전에 this를 전역 변수에 미리 지정한다
    (function() {
      console.log(`print2-2] name: ${me.name}`);	// 2-1과 같은 this를 가리킨다
    })();
  },
    
  """ arrow function case : 상위 스코프와 같은 this """
  print3: function() {
    console.log(`[print3-1] name: ${this.name}`);
    (() => {
      console.log(`print3-2] name: ${this.name}`);	// 3-2와 같은 this를 가리킨다
    })();
  }
  
 evan.print1();	// output >> print[1-1] name: Evan, print[1-2] name : undefined
 evan.print2();	// output >> print[2-1] name: Evan, print[2-2] name : Evan
 evan.print3();	// output >> print[3-1] name: Evan, print[3-2] name : Evan
  



callback, Promise, async/await


자바스크립트에서 비동기(asynchronous)처리 하는방법은 3가지가 있다.

1. callback

  • 콜백 함수는 다른 함수에 매개변수로 넘겨준 함수를 말한다.
  • 어떤 특별한 문법적 구조를 지닌 것이 아닌 호출방식에 의한 구분이다.
  • 보통 return값 없이 콜백 함수 인자로 넘겨 실행하는 방식으로 자주 사용된다.
    ex) setTimeout 함수를 이용한 특정 작업 예약
// node.js 에서 에러와 파일목록을 찾는 예제
const fs = require('fs');

fs.readdir('.', function(err, files) {
  if(err) {
  	console.log("Error finding files: ' + err) 
  }
  else {
  	console.log(files)              
  }
});

// fs.readdir이 끝나기 전에 수행된다.
console.log("ENDED");

그러나 callback 함수가 반복되어 코드의 깊이 수준이 감당하기 힘들 정도로 깊어지는 콜백 지옥(callback hell) 인해 최근에는 지양되고 있는 처리 방법이다.

따라서 ES6에 도입된 Promise와 ES8에 도입된 async/await를 사용하여 가독성을 챙긴다.

2. Promise

  • Promise 내부에 코드를 작성하여, 정상적처리시 resolve, 비정상처리시 reject를 지정한다.
  • 해당 promise를 할당 받은 변수에서 .then(), .catch(), .finally() 등으로 결과값을 처리할 수도 있다.
const fs = require('fs');
const fsPromises = fs.promises;

fsPromises.readdir('.'):
	.then(files => {
    	console.log(files);
    })
	.catch(err => console.error(err));

// fs.Promises.readdir이 끝나기 전에 수행된다.
console.log("ENDED");

3. async/await

  • 내부적으로 Promise를 사용한다.
  • asyncawait는 반드시 같이 쓰여야 한다.
  • await 는 바로 다음을 처리하는 것이 아니라,
    결과 값을 얻을 때 까지 기다렸다가 다음단계를 처리하겠다는 의미이다.
  • 비동기 코드를 동기 형태로 만들어 가독성을 높인다.
const fs = require('fs');
const fsPromises = fs.promisies

async function fn() {
  try {
    	let files = await fsPromises.readdir('.');
    	console.log(files)
  }
  catch(err) {
  		console.error(err);
  }
}

fn();	// async 함수 이므로, 완료 전에 로직이 동작한다.
console.log("ENDED")



고차 함수 (high order function)


python의 데코레이터 문법과 비슷한 개념이라고 한다.

  • 함수를 인자로 받거나, 함수를 결과로 반환하는 함수를 말한다.
""" js의 high order function """
function base_10(fn) {
  function wrap(x, y) {
    return fn(x, y) + 10;
  }
  return wrap;
}

function mysum(x, y) {
  return x + y;
}

mysum = base_10(mysum);
cosole.log(mysum(1, 2));	// output >> 13
""" js의 high order function """
const base_10 = fn => (x, y) => fn(x, y) + 10;

let mysum = (x, y) => x + y;
mysum = base_10(mysum);	
console.log(mysum(1, 2));	// output >> 13
""" python의 데코레이터 """
def base_10(fn):
	def wrap(x, y):
    	return fn(x, y) + 10
    return wrap
    
def mysum(x, y):
	return x + y
    
mysum = base_10(mysum)
print(mysum(1, 2))		# output >> 13

📌 참고 출처

0개의 댓글