본문 바로가기

JavaScript

ES6+ 자바스크립트에서 내가 소홀했던 내용들

간단하게 서론

방학이 되어서 그 동안 원리는 모른 채 긴가민가 하며 사용하던 javascript의 내용들, 외면해왔던 편리한 새로운 기능들을 다루어보면서 javascript의 기본기와 내공을 쌓고자 정리합니다.

ECMA Script가 대체 뭐야

채용사이트 보면 의외로 단순 javascript가 아닌 ECMAScript, ES6 등에 대해 논하는 기업들이 많더라.

ECMA Script는 표준이고, Javascript는 ECMA Script의 가장 주된 구현체 중 하나.

ES6, ES2015, ES2016 이외에도 다양한 ES XXX이 존재함.

ECMASciprt 2015 = ES6 이 때 js의 새로운 기능이 마니 추가됨

왜 그 많은 버전 중에 ES6가 제일 많이 들려오는 것 같을까?

  • 주로 ES6(2015) 버전에서 많은 기능이 추가되어서 ES6 에 중점을 두는 듯

    generator, arrow function 등등.

arrow function

js 의 원래의 함수는 자신을 호출하는 객체를 가리키는 dynamic this를 갖지만, arrow function은 상위 스코프(lexical scope)를 가리키는 lexical this를 갖는다.

// Lexical this
// 출력결과 : Bob knows John, Brian
var bob = {
  _name: "Bob",
  _friends: ["John, Brian"],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
}

쉽게 말하자면 대부분의 경우 arrow function 은 일반적인 함수에 .bind(this)를 한 것과 같은 역할을 수행한다.

this._friends.forEach(
	function(f) {
		console.log(this._name + " knows " + f);
	}.bind(this)
);

arrow function 에서는 this 가 고정되어있기 때문에, global 하게 호출하여도 같은 this를 갖는다.


class Obj {
    name = "OBJECT";
    sayThis = function() {
        console.log(this.name);
    };
    arrowThis = () => {
        console.log(this.name);
    };
}
let obj = new Obj();
let globallySayThis = obj.sayThis;
let globallyArrowThis = obj.arrowThis;
obj.sayThis();
gloabllySayThis();  //여기선 this 가 바뀐다. chrome 에선 window, nodejs 에선 undefined
obj.arrowThis();
globallyArrowThis();//arrow function의 this는 lexical this이므로 globally  invoked 해도 this 유지

Class

ES6에서 Class 가 도입되었는데, 이는 js가 원래 갖고있던 prototype 기반의 객체지향 패턴을 좀 더 쉽게 이용하도록 해주는 요소이다.

클래스는 prototype 기반의 상속, super 호출, instance, static method, 생성자 등의 기능을 지원한다

아예 getter와 setter 자체도 존재

Object.defineProperty() method를 이용해 Object를 정의한 후에도 getter나 setter를 추가할 수 있다.(하지만 js에서 js가 지원하는 getter 와 setter를 그리 많이는 이용하지 않는 것 같음. )

클래스 선언은 함수 선언과 달리 호이스팅이 이루어지지 않는다.

//호이스팅이 안 되므로 불가능
//let you = new Parent("You");
class Parent {
    age = 23;
    constructor(name) {
        this._name = name;
    }
    get name() {
        return "My name : " + this._name;
    }
    set name(name) {
        this._name = name;
        console.log("name을 변경하였습니다.");
    }
}
let me = new Parent("Dev");
me.name = "Dev Park";  //setter 호출, output: name을 변경하였습니다.
console.log(me.name);  //getter 호출, output: My name : Dev Park

클래스의 내부는 strict 모드이다

여느 객체지향 언어들처럼 상속을 구현한 뒤 그 기능을 이용하려면 부모 클래스의 생성자를 호출해야한다.( 암묵적으로 호출되진 않음)

Class 와 function, prototype

classfunctionprototype 을 이용해 구현하는 자바스크립트 특유의 상속을 좀 더 쉽게 정의할 수 있도록 도와주는 요소이다.

class는 원래의 object, function, prototype을 통한 객체의 생성과 상속에 관한 내용에 추가로 다양한 기능을 내재시킨 syntax와 같다. 따라서 class라는 자료형이 있는 것도 아니고, typeof 어떤 클래스를 입력 시에 그 자료형은 function 임을 알 수 있다.

class Parent {
    constructor() {
        console.log("Constructor ...");
    }
}
let p = new Parent();
console.log(p instanceof Parent);//true
console.log(p instanceof Object);//true
console.log(Parent instanceof Function);//true
console.log(typeof p);//object
console.log(typeof Parent);//function

(typeof 와 instanceof 는 https://unikys.tistory.com/260 , stackoverflow 참고.

주로 instanceof는 custom type, typeof는 간단한 built in types)

this

var, let, const 등을 변수 선언시 붙여주지 않으면, global variable의 프로퍼티로 처리된다.

browser runtime 에서는 window 가 global variable이자 global this이고 node runtime에서는 global이 global variable이고, module.exports가 global this임.

non-strict mode 에서는 변수 형태에 대한 정의 없이 변수를 선언할 경우 global variable의 property로 변수가 선언되는 반면 strict mode 에서는 그러한 작업은 수행되지 못하도록 한다. strict mode는 그 이외에도 다양한 오류를 발생시킬 수 있을 만한 행동들을 제한해준다.

일반 function은 자신을 호출하는 대상을 this 로 인식하고, arrow function은 자신이 정의될 당시의 상위 스코프를 this로 인식한다.

사실 자주 헷갈리는 내용이고 실제 코딩에서도 많이 중요한 내용이기에 감히 이 종합적인 글에서 정리하기 보다는 따로 자세히 정리된 글을 보는 것이 훨씬 나을 것 같아 자세히 적진 않겠음.

let, var, const

var 는 초기에 사용하던 변수 선언 방식. function-scoped 였고.

hoisting이 이루어졌고, 변수 재 선언이 가능했으며, global object의 property로서 선언됨.

function-scope가 많은 혼란을 가져왔고, 그것을 개선한 것이 let

let 은 block scope, 변수 재선언을 막아주고, global object의 property로 선언되는 것을 방지함.

constlet의 가장 큰 차이는 immutable 한가이다. const 는 값을 한 번 할당하면 이후 변경할 수 없다. ( 객체의 내부 데이터의 경우 변경 가능. 마치 파이썬의 튜플 속 리스트 같은 느낌.)

유용한 algorithm functions

다양한 loop은 w3school에 잘 정리되어있다.

for in 은 property를 인자로 받는 loop. array의 경우 property는 index

for of 는 iterable한 객체의 요소를 인자로 받음.
findfilter와 유사하지만 단 하나만 리턴. find 못하면 undefined 리턴

let obj = { first: "Su", last: "Park", age: 23 };
for (prop in obj) {
    console.log(prop);  //output:first last age
}
for (key of ["a", "b", "c"]) {
    console.log(key);  //output:a b c
}
let l = [1, 2, 3, 4];
console.log(l.map(num => num ** 2));  //output:[1, 4, 9, 16]
//l 자체가 바뀌진 않음. 함수의 리턴값이 작업을 거친 array
console.log(l.filter(num => num % 2));  //output:[1, 3]
console.log(l.some(num => num % 2)); //output: true
console.log(l.every(num => num % 2)); //output: false
l.forEach((n, i) => {
    console.log(n, i);  //output: value, index
});
let sum = l.reduce((prev, current, currentIndex, arr) => {
    return prev + current;
});
console.log(sum);//output:10

default parameter, spread, rest parameter, Destructuring

default parameter는 함수의 parameter에 default value가 지정된 파라미터. default value는 유사배열인 Arguments에 포함되지 않는다.

((a, b = 10) => {
    console.log(a + b);
})(5);

rest parameter 는 원래의 parameter보다 초과되는 parameter들을 배열로 이용할 수 있도록 해줌,

...param 이런 식.

((a, ...b) => {
    console.log(
        b.reduce((prev, current) => {
            return prev + current;
        })
    );
})(null, 1, 2, 3, 4, 5);

spread 연산자는 iterable을 개별 요소로 분해해준다.

console.log([...[1, 2, 3], ...["a", "b", "c"]]);

흔히 배열을 이어붙일 때도 쓰인다.

예를 들어 arr1=[1,2,3], arr2=[4,5,6]의 경우 [arr1, arr2]는 [ [1,2,3], [4,5,6] ] 이지만 [...arr1, ...arr2]는 [1,2,3,4,5,6]이다.

Destructuring 은 rest parameter와 비슷한데, 얘는 함수의 parameter 정의 시에 이용하는 것이 아니라, 변수에 값을 할당 시 이용된다.

배열을 Destructuring 할 때에는 [] 이용, 배열이 아닌 객체를 destructuring 할 때에는 {} 이용.

let obj = { first: "Su", last: "Park", age: 23 };
let { first, ...rest } = obj;
console.log(first, rest);  //output: Su {last:"Park", age:23}

let newObj = { univ: "computer", ...obj };
console.log(newObj);  //output: merged object

destructuring이나 spread 연산자를 이용하면 쉽게 객체를 복사하거나 병합할 수 있다. 주로 React 에서 state 관해서 많이 사용했던 것 같다.

Promise를 다루는 방법들

Promise는 비동기 함수 속에서 pending 상태의 리턴값을 갖다가 Promisefulfilled 되거나 rejected 될 경우 await이나 then 등으로 값을 리턴해주도록 하는 녀석이고, 이를 이용해 callback-hell을 해결할 수 있다.

Promise를 처음 접했을 때 조금 당황스러웠던 것은 내가 할 작업을 다 완료한 뒤 return 하는 개념이 아니라 return new Promise() 내부에 내가 할 작업을 넣어주는 경우가 많았기 때문이다.

function wait() {
  //내가 할 작업을 return 문 앞에 집어넣어야함.
  //실질적으로 resolve나 reject가 return 같은 느낌.
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("I awaited.");
            resolve("DONE");
        }, 3000);
    });
}

wait().then(res => {
    console.log(res);
    console.log("END");
});

Promise.all()이라는 작업을 통해 모든 작업이 resolvereject 된 뒤에 Promise 를 array 형태로 리턴해주는 방법도 있다.

function wait() {
    let promises = [
        new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log("I awaited.");
                resolve("1DONE");
            }, 3000);
        }),
        new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log("I awaited.");
                resolve("2DONE");
            }, 2000);
        })
    ];
    return Promise.all(promises);
}

wait().then(res => {
    console.log(res);
    console.log("END");
});

Symbol

Symboljs 에 존재하는 7번째 타입이다.

5개였던 primitive type 과 1개의 Object tpye에 이은 primitive type이다.

주로 이름 충돌이 없는 property key로 이용하기 위해 사용하고 for 문에서 key를 숨기고 싶은 key값을 지정할 때에도 이용한다.

Symbol로 생성되는 Symbol들은 고유하다. Symbol.for 은 전달받은 key를 바탕으로 Symbol Registry에서 해당하는 key의 Symbol이 존재하면 그 Symbol 값을 반환하고, 존재하지 않으면 새로운 Symbol을 생성한다. 같은 key를 바탕으로 Symbol.for 에서 반환된 Symbol들은 같은 Symbol이다.

let obj = {};
let a = Symbol("hello");
let b = Symbol("hello");
obj[a] = "a";
obj[b] = "b";
console.log(obj);
//{ [Symbol(hello)]: 'a', [Symbol(hello)]: 'b' }

let c = Symbol.for("hello");
let d = Symbol.for("hello");
obj[c] = "c";
obj[d] = "d";
console.log(obj);
//{ [Symbol(hello)]: 'a', [Symbol(hello)]: 'b', [Symbol(hello)]: 'd' }
//c와 d가 같은 symbol이기 때문에 obj[c]="c" 는 d에 의해 덮어씌워졌다.

특이하게도 iterator를 이용할 때 Symbol을 이용하면 간편하게 이용할 수 있는데, 사용법 또한 특이하다.

Array[Symbol.iterator]() 과 같은 형식으로 이용할 수 있다.

Symbol.iterator 라는 key를 통해 얻는 함수를 호출하여 해당하는 iterable한 요소의 iterator 을 획득하는 것이다.

let arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
let next;
while ((next = iter.next()) && !next.done) {
    console.log(next.value);
}

Generator

generator functioniterable 프로토콜과 iterator 프로토콜을 따르는 객체이다.

( 즉 foo()foo()[Symbol.iterator]() 모두 가능 )

function* name(params){ ... } 의 형태로 정의하고, yield 전까지 함수를 수행한 뒤( return 처럼 동작 ) 후 next()를 통해 다음 yield 문까지 다시 함수를 수행하도록 할 수 있다.

function* foo() {
    console.log("the first invoking");
    yield [1, 2, 3, 4];
    console.log("the second invoing");
    yield "abcd";
    console.log("the Third invoking");
    yield { name: "foo" };
}
let gen = foo(); //iterator을 반환한다, 여기서는 foo내의 아무런 console.log도 호출 안 함.
//let gen = foo()[Symbol.iterator]()와 동일
console.log(gen.next());//yield [1,2,3,4] 이후의 console.log 수행, value로 "abcd"를 가짐.
console.log(gen.next());
console.log(gen.next());

사실 Generator을 많이 사용할 지는 모르겠다... 약간 C에서 goto문 쓰지 말라는 느낌.그러므로 자세한 내용은 그냥 링크 참조...(https://wonism.github.io/javascript-generator/)

Babel, webpack

Babel과 Webpack을 이용한 ES6 환경구축

크롬, 사파리, 파이어폭스 등의 에버그린 브라우저(사용자에게 재설치 및 업데이트 요구 없이도 업데이트를 수행하는 모던 브라우저)는 ES6 의 대부분의 기능을 구현한다. 하지만 IE 등의 구형 브라우저는 ES6를 제대로 구현해내지 못한다.

모던 브라우저에서는 ES6 모듈을 사용할 수 있지만 몇몇 이유로 인해 Webpack등의 모듈 번들러를 사용하는 것이 일반적이다.

nodejs 가 따르는 CommonJS 방식은 module 을 이용하지만, browser는 CommonJS 방식을 따르지 않기 때문에, importrequire 문 같은 것은 babel 만을 이용한 뒤 script tagimport 하더라도 이용할 수 없다. 따라서 다른 모듈을 import 할 필요 없이, 의존 관계에 있는 모듈들을 하나의 파일로 만들어주는 모듈 번들러인 webpack 을 이용하면 된다.

트랜스파일러 - 트랜스파일링이란 어떠한 언어로 작성된 소스코드를 다른 소스 코드로 변환하는 것. Babel 은 이런 트랜스파일링 작업을 해주는 대표적인 트랜스 파일러이다.

예를 들어 ES6 나 ES7 코드를 ES5 코드로 트랜스파일링 할 수 있다.

webpack, 모듈 번들러 - 웹팩은 의존성있는 모듈들을 바탕으로 정적인 자산을 생성한다.

예를 들어 React 를 바탕으로 만든 React app을 하나의 정적 파일로 만들어 내는 것. 흔히 "말다", "만다" 라고 표현하더라. "Webpack으로 말아서 정적페이지 호스팅해요."

CommonJS - 브라우저가 아닌 런타임에서 javascript의 module 을 이용하는 규칙을 설립하는 프로젝트. 예를 들어 브라우저 안의 js는 import 나 require을 사용할 수 없지만, node에서는 사용 가능함.

다루지 않은 개념

decorator - 아직까지 typescript → javascript 로 트랜스파일링 할 때 빼고는 js에서 decorator을 이용하는 경우는 못 봄.

generator - 다루긴했지만, 그닥 효용성을 모르겠음.

참고하였고, 잘 정리되어있었던 links

What is ES6 and others

Learn ES6 from Babel

javascript 클래스 소개

rest, spread, destructuring

let vs var