모던 자바스크립트

들어가며

이 아티클은 객체지향 자바스크립트 3/e 를 토대로, 자바스크립트 기본에 대한 복습 및 개인적인 잡지식을 정리 한 아티클 입니다.

역시나 이번에도 개인 스타일 및 편의상 음슴체로 작성하겠습니다.

자바스크립트

자바스크립트란?

오라클(썬)의 Java 와는 무관한 언어. 무관한데 이름이 비슷한 이유는, 원래는 LiveScript 라는 이름으로 시작이 되었으나, 당시 Java 가 핫한 언어였기 때문에 인기에 묻어가고자 이름을 정정했다는 썰이 학계에서는 정설로 얘기되고 있음.

웹브라우저의 웹 페이지를 구성하는 핵심 요소 중 하나이며, HTML 이 컨텐츠를 담당하고, CSS 가 꾸밈(심미적)을 담당한다면, Javascript 는 이벤트와 애니메이션을 담당하고 있음.

과거에는 브라우저 한정의 스크립트 언어였지만, 표준화(ECMA Script)를 거치고 언어다워지기 시작하더니, NodeJS 의 등장과 함께 현재는 플랫폼을 아우르는 (서버, 브라우저, 데스크탑, 모바일) 멀티 플랫폼 언어로 최고 인기 언어로 자리 잡음.

자바스크립트의 구성

자바스크립트에는 세 가지의 핵심적인 용어로 구성되어 있음.

자바스크립트와 브라우저 전쟁

넷스케이프(현 모질라)와 마소간의 제1차 브라우저 전쟁에 대한 이야기, 얘기를 하기에는 길어서 위키 링크로 대체. 나름 재밌으니 읽어볼 것을 추천.

현재의 자바스크립트

ES5 와 ES6

ECMA Script

과거의 자바스크립트는 브라우저 마다 API 다 다르고, 심지어 동작 결과도 다른 경우도 많았었음. 이 때문에 개발자들이 고통에 시달리자, 97년 쯔음 자바스크립트 표준화 운동을 시작하게 됨. 다만 악의 축인 MS 의 브라우저 독식 (윈도우 OS에 IE 기본 내장)을 포함해 비협조적인 벤더(특히 MS)들의 반발로 거의 무산화 됨(Jquery 의 탄생 이유..)..

2차 브라우저 전쟁이라고 일컫는 2009년 쯤, 구글이 브라우저 시장에서 승리함에 따라 ECMA 표준에 대해 긍정적이었던 구글의 크롬브라우저를 선두로 표준화 되기 시작함.

ES5

2009 년 12월에 공식적으로 승인.

대부분의 브라우저 및 서버에서 구현. 흥미로운 기능은 strict mode 의 도입. 엄격 모드는 언어의 하위집합으로 사용되지 않는 기능이 동작하지 않게해줌.

“use strict”;

태그나 함수의 머릿말에 위 ‘스트릭트 모드의 사용(use strict)’ 선언을 통해 사용이 가능

ES6

2009년에서 6년 만인.. 2015년 6월에 승인. 그래서 ECMA Script 2015 라고도 부름.

promise, 제네레이터

ES6의 스펙을 모든 플랫폼에서 완벽히 구현하지는 못했음. 이런 이유로 ES6 원본 코드를 브라우저에서 실행시키거나 서버에서 실행시키면 몇몇 코드의 구문을 해석하지 못해서 에러를 내고 app은 멈추는 상황을 볼 수 있음.

이를 위해서 트랜스파일러가 등장했다. 트랜스파일러는 ES6 syntax 코드를 모든 브라우저에서 동작하는 ES5 코드로 변환하는 개념인데, 컴파일과 유사하다해서 트랜스파일이라고 부르고, 이를 처리해주는 녀석을 트랜스파일러라고 부른다.(또는 컴파일러)

현재 대표적인 트랜스파일러로는 바벨(Babel)이 있음.

바벨

바벨은 빌드 시스템, 프레임워크 및 언어에서부터 탬플릿 엔진까지 광범위하게 사용되고 있음.

객체지향 프로그래밍

객체지향의 기본적인 개념들

객체

OOP에서 객체는 어떠한 대상을 일컫는 개념이다. 프로그래밍에서 ‘인스턴스(instance)’ 라고 부르기도 한다. 예를 들어 네로라 불리는 검은 고양이가 있다면 네로는 고양이 객체이고, name 이 ‘네로’, color 가 ‘검정’ 의 속성을 가지고 있다. 또한 고양이의 특징인 ‘울기(야오옹~)’, ‘뛰어내리기’ , ‘핥퀴기’ 와 같은 동작을 수행할 수 있다. 이러한 동작을 객체의 메소드라고 부른다.

개인적인 팁이지만, 주변 사람들을 보면 메소드와 함수를 혼동해하는 사람이 많은 걸 보아왔다. 메소드와 함수는 생긴 모양새는 같은 의미를 가진다. 하지만 두 개념이 비슷해보여도 궁극적인 차이는 메소드는 객체에 포함되어야만 존재하는 기능의 역활이고, 함수는 객체 없이도 동작할 수 있는 기능이다.

클래스

객체지향프로그래밍에서 클래스는 객체라는 데이터의 포맷(자료구조)을 말하며, 이는 결국 객체 시그니쳐(청사진(blueprint) 또는 레시피(recipe))이며, 자료구조라 말할 수 있다. 관점에 따라서는 공장의 틀 같은 존재나 탬플릿과 같은 것으로 설명하기도 한다. 위의 네로 고양이를 예로 들면 ‘네로라는 이름의 객체 인스턴스는 고양이 유형(타입)’ 라 말할 수 있다.

캡슐화

캡슐화(encapulatoion)는 객체가 데이터의 성질(객체의 속성에 값이 저장됨)을 가짐으로써 생겨난 개념이다. 캡슐화의 궁극적인 의미는 접근 제어, 정보 숨기기(information hiding)에 의미를 가진다.

집합

여러 객체를 하나의 객체로 결합하는 걸 구성(composition)이라고 부른다, 고양이라는 개념에 대해 살펴보면, 눈, 코, 입, 꼬리, 다리 등 여러가지 속성들로 이루어짐을 생각해볼 수 있다. 자바스크립트의 객체는 속성의 집합으로 되어 있다. 자바스크립트의 속성을 동적으로 추가할 수 있는 이유는 여기에 있다. 속성이 시그니쳐로 선언되어 있지 않고, 집합으로 되어있기 때문에 가능하다.

const human = {name : 'jhun', gender : 'man'};
console.log(human.name);
// >> 'jhun'
console.log(human['name']);
// >> 'jhun'

위 코드를 보면 name 과 gender 라는 속성이 있는 객체의 name 속성의 값을 출력하는 것을 알수 있다. 방법으로 2가지를 지원하는 데, 아래방법을 보면 배열속에서 ‘name’ 이라는 키로 어떠한 값을 찾는 듯한 구문을 볼수가 있다.

human['name'];

상속

자바스크립트의 상속은 재미있다. 자바스크립트를 프로토타입 언어라고 부르는 경우가 있는데, 이 프로토타입이라는 것이 자바스크립트에서 상속으로 쓰여진다.

자바스크립트는 모든 것이 객체로 되어있다. 자바스크립트의 객체는 모두 prototype 이라는 특별한 속성을 가진다.

프로토타입이라는 것은 어떠한 것이 완성되기 이전의 테스트 모델과 같은 개념이다. 자바스크립트의 prototype은 나 자신이 어디로부터 유래했는지를 의미하는 것을 의미한다. 즉 상속의 개념과 유사하다.

객체는 기본적으로 Object 라는 자바스크립트 기본객체형을 prototype으로 가진다.

다형성

다형성이란 어떠한 속성이 여러가지 모습으로 나타낼수 있음을 말한다. 자바스크립트의 다형성은 프로토타입 체이닝을 이해해야 한다. 예를 들어 어떠한 객체의 메소드를 호출할 때 일어나는 일은 아래와 같다.

  1. 객체.echo() 입력.. echo 라는 이름의 속성을 탐색시작

  2. 객체 자신에 echo 라는 이름의 속성이 있는가?

  3. 없다면, 객체의 상위 프로토타입에 echo 라는 이름의 속성이 있는가?

위처럼 echo 라는 속성을 자기 자신이 처리하지 못할 경우, 상위 프로토타입에게 가능한지를 확인한다.

이걸 응용하면, 다형성을 표현할수가 있다.

const Human = function() { this.speack = () => console.log('hi');};
const jhun = new Human();
jhun.speack();
// hi
jhun.speack = () => console.log(123);
jhun.speack();
// 123

위 코드를 보면 jhun 이라는 객체는 Human 자료형의 인스턴스이다. Human 에는 speack 이라는 속성이 있고 이는 메소드이다.

spack() 이라는 구문을 통해 이 메소드를 실행시키면, Human 프로토타입의 spaeck 메소드가 실행된다. 재밌는 것은 jhun 인스턴스 자체에 speack 이라는 속성을 익명함수로 할당해버린다. 이는 자바의 오버라이딩과 유사하다. 이 상태에서 speack 메소드를 호출하면, Human 프로토타입 까지 가기 전에, jhun 객체 내에 선언된 speack 를 실행하게 되어서 결과는 123이 출력된다.

이러한 특징을 이용해서 스칼라의 특질(trait) 처럼 어떠한 기능만을 객체에 할당하는 믹스인 기법을 사용할수도 있다. 믹스인은 별도의 포스트에서 다루려고 한다.

문법

변수

변수 선언은 기본적인 것임으로 패스.

$변수, _변수

변수 이름에 있는 $ 문자는, 일반적으로 함수 이름을 표현할 때 가끔 쓰는 네이밍 표기법이라고 한다.

const $myFunction = function(){...}

변수 접두어가 _ 로 시작하면 private 을 암묵적으로 내포한다. 이는 자바스크립트의 접근지시어(public, private) 개념이 없기 때문에 유래된 표기법이다.

세미콜론

표현식의 마지막은 항상 세미콜론으로 끝낸다. 자바스크립트에는 세미콜론 자동 삽입 매커니즘이 있다. 하지만, 미표기 시에 착각이 일어나 개발 코드에 버그를 만들게 할 수도 있음으로 대부분 표기하기를 권장한다.

typeof 연산자

변수 나 값의 유형을 알고 싶을 때, typeof 연사를 사용할 수 있다.

typeof 로 알 수 있는 유형은 아래와 같다.

const some ='123';
typeof some 
// >> "string"
const n5 = 0xff;
n5
// >> 255
typeof n5
// >> "number"

지수 리터럴

1e1 은 숫자 1 뒤에 0이 하나 있는 걸 나타낸다. 2e+3 은 숫자 2 뒤에 3개의 0이 있음을 나타낸다.3e-3은 0.003 이다.

1e1
// >> 10
2e+3
// >> 2000
3e-3
// >> 0.003

무한대

const a = 6 / 0;
a;
// >> Infinity

NaN

NaN 은 알 수 없는 숫자타입을 의미한다.

const a = 10 * "f;
a;
// >> NaN

문자열 반환 변태성

let s = '1';
s++;
// >> 2

탬플릿 리터럴

ES6 에 생긴 문법으로, 작은 따옴표 대신 백틱(back-tick) console.log(`Hello : ${name}`) 을 써서 사용한다. 또한 값을 주입해주는 자리표시자 (place holder) syntax는 중괄호와 달러기호를 사용해서 ${expression} 로 작성한다.

const name = 'jhun';
console.log(`Hello ${name}`);
// >> Hello jhun

const $someFunc = (a,b) => {return a+b};
console.log(`${$someFunc(1,2)}`);
// >> 3

지연 평가

첫 번째 피연산자가 평가됬을 때 true라 판단이 된 후에, 뒤에 어떤 값이 나오더라도 의미가 없을 경우에는 지연 평가(lazy evaluation) 으로 처리 되에 후술 된 평가 로직이 동작하지(short-circuiting) 않는다.


let val = 5;
true || (val = 10);
// >> true
val;
// >> 5;
true && (val = 11);
// >> 11;
val;
// >> 11;

위 특징을 응용해서 아래와 같은 것도 가능하다.

var myNum1 = 5;
myNum1 = myNum1 || 10;
myNum1;
// >> 5;

var myNum2 = myNum2 || 10;
myNum2;
// >> 10;

그렇지만 ES6 에서는 이 꼼수가 막혔다.

let myNum1 = 5;
myNum1 = myNum1 || 10;
myNum1;
// >> 5;

let myNum2 = myNum2 || 10;
myNum2;
// >> error;

undefined 와 null

let i = 1 + undefined;
i;
// >> NaN

let ii= 1 + null;
ii;
// >> 1;

Symbol

ES6 에는 새로운 원시 타입인 Symbol 이 생겼다.

let atom = Symbol();
atom;
// >> Symbol();

배열

배열은 어느 언어독립적으로 비슷하다. 단, 문자열 배열 접근은 ES6 에 와서야 동작하게 되었다.

const s= 'one';
s[0];
// >> o
s[1];
// >> n

루프

자바스크립트에서는 네 가지 유형의 루프가 있다.

let i = 0;
while(i<10){
    i++;
}

함수

자바스크립트의 꽃인 함수이다.

자바스크립트의 함수를 터득한다는 것은 아래 자바스크립트의 테크닉을 사용하게 되는 걸 의미한다.

함수의 매개변수

function sum(a,b){return a + b};

sum(1,2);
// >> 3;

<!--  필요하게 많이 들어간 매개변수는 무시된다. -->
sum(1,2,3,4,5);
// >> 3;

자바스크립트 함수에는 자동으로 생성되는 특별한 값이 arguments 라는 내장 키워드가 있다.


function args(){
    return arguments;
}
args();
// >> [];
args(1,2,3,'hello');
// >> Arguments(4) [1,2,3,'hello'] ....


위 특징을 응용해서 아래와 같은 함수도 만들 수 있다.


function sumOfAll(){
    let result = 0;
    for(let i in arguments){
        if(typeof arguments[i] === 'number')
        {
        result += arguments[i];
        }
    }
    return result;
}

sumOfAll(1,2,3)
// >> 6

sumOfAll(1,2,3,'HAHA')
// >> 6

default 매개변수

함수 매개변수에 디폴트 값을 지정할 수 있다. 또한 default 매개변수는 다른 매개변수를 참조할 수도 있다.


function echo(name = 'jhun',name2 = name){
     console.log(`Yahoo~ ${name} ~ ${name2}`);
}

echo();
// Yahoo~ jhun ~ jhun
echo('haha');
// Yahoo~ haha ~ haha;

좋아보이지만 주의할 점이 아래와 같다. 내부 변수에서 재정의를 하여 매개변수를 덮으려고 해도, default 매개 변수가 외부의 변수에 의해 가려지게 되면(shadowed) 의도 되로 동작하지 않는다.

var val = 'out';

function print(arg = val){
    var val = 'in';
    console.log(arg);
}
print();
// >> out
print(123);
// >> 123;

<!-- ES6.. -->

let val = 'out';

function print(arg = val){
	let val = 'in';
	console.log(arg);
}
print();
// >> out
print(123);
// >> 123

나머지 연산자

function echo(...arg){
	for(let i in arg){
		console.log(arg[i]);
    }
}


echo("haha","hoho")
// >> haha
// >> hoho

재밌는 것은 나머지 매개변수로 받는 녀석은 배열로 들어온다는 점이 재밌다. 또한 나머지 매개변수는 꼭 마지막 매개변수로 선언이 되어야만 한다.

function echo(a1, ...arg){
	console.log(a1);
	console.log(arg);
}
echo('h1','h2','h3','h4');
// >> h1
// >> (3) ["h2", "h3", "h4"]

스프레드 연산자

나머지 연산자와 생긴 것은 같지만 기능이 다르다. 스프레드 연산자는 배열이나 객체의 값으로 넣어질 때 배열 안의 item 들을 하나하나 split 해서 뽑아주는 녀석이다. 코드를 직접 보자


const arg1 = ['wed','thu'];
const arg2 = ['sat','sun'];
const week = ['mon','tue', ...arg1,'fri',...arg2];
week;
// >> (7) ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]

사전 정의 된 함수

자바스크립트 엔진에는 사전 정의되어 있는 내장 함수가 몇 가지 있다.

변수 호이스팅

var a = 123;
function f(){
    console.log(a);
    // >> undefined
    var a= 1;
    console.log(a);
    // >> 1;
}
f();

이 함수를 실행할 경우 기대하는 바는, 첫번째 alert 에서 123이 두번째 alert에서는 1 이 나올 것이라 기대 한다. 하지만 정작 결과는 undefined 이다. 이것은 호이스팅이라는 자바스크립트의 특징 중 하나를 보여주는 사례이다.

호이스팅은 자바스크립트의 함수는 함수가 실행될 때 함수 내부의 모든 지역변수들을 최상단 으로 끌어올려 선언하는 특징을 가지고 있다. 위 f() 함수를 호이스팅 관점으로 보면 아래와 같다.

var a = 123;
function f(){
    var a;
    console.log(a);
    // >> undefined
    a= 1;
    console.log(a);
    // >> 1;
}
f();

var a=1 으로 아래에서 선언 되던 a 라는 지역 변수가 가장 최상단으로 끌어올려졌다. 끌어 올려지면서 a가 명시 되면서, 값이 선언되지 않은 undefined 가 출력 된다.

블록 스코프

위의 호이스팅 문제 때문에 ES6 에서는 블록 범위(scope) 라는 기능을 추가 했다. 블록 스코프를 사용하기 위해서는 var 키워드 대신 let , const 키워드로 변수를 선언해야 한다. let, const 로 선언 하게 되면 블록 단위로 호이스팅 된다.

let a = 123;
function f(){
    console.log(a);
    // >> a is not defiend
    let a= 1;
    console.log(a);
    // >> 1;
}
f();

블록 스코프는 자바나 C++ 과 같이 { } 블록 구문의 동작과 똑같이 동작하게 된다. 자바스크립트 만의 특징인 호이스팅도 일어나지 않는다. 또한 let 키워드로 선언 된 변수 명이 이미 있을 경우 재 선언할 수 없다.

함수는 데이터다.

자바스크립트에서의 함수는 데이터이다. 책에서는 이 개념을 아주 중요하게 설명하고 있다.

함수 리터럴

함수를 정의하는 방식 중에 함수 리터럴 표기법(function literal notation) 이라는 것이 있다.

var f = function () {
    return 1;
    }

위의 코드에서 function () {...} 에 해당하는 것은 함수 표현식(function expression)이다. 함수 표현식은 선택사항으로 이름을 줄 수 있다, 예를 들면 function hello() {...} 이런 식으로 말이다. 이 경우에는 NPE(named function expression)이라 해서 이름을 가지는 함수 표현식이 된다.

함수 표현식과 리터럴 표기법의 차이점은 호출법에서 차이가 있다. 함수 표현식은 변수 호이스팅처럼 전역으로 자동으로 끌어올려져 어디서든 호출할 수 있지만, 리터럴 표기법은 선언 되기 이전에는 호출할 수 없다.


some();
// >> hi
f();
// f is not difined;

function some(){
    console.log('hi');
}

var f = function(){
    console.log('hi');
}

익명 함수

함수 표현식에서 선택사항인 이름을 주지 않고 선언한 함수를 익명 함수(anoymous) 라고 부른다.

익명 함수는 아래의 특징을 가진다.


function somePlus(a,b){
    return a + b;
}

function sum1(){
    return 1+2;
}

function sum2(){
    return 3;
}

somePlus(sum1(),sum2());
// >> 6

<!-- 익명즉시함수로  자리에서 바로 함수를 작성해서 실행 -->
somePlus((function() {return 7)()}, sum2();
// >> 10;

<!-- 애로우함수(람다)로도 작성이 가능  -->
somePlus((() => {return 8})(), sum2();
// >> 11;

위를 응용해서 콜백함수로 사용하면 아래와 같다.



function multiplyWithSum(a,b,callback){

    return callback(a) + callback(b);

}

multiplyWithSum(2,3,(x) => {return x*2} )
// >> 10

즉시 실행 함수

즉시 실행 함수는 함수가 정의 된 자리에서 바로 실행되어 result 값을 가지고 있게 되는 경우이다.

내부 비공개 함수

비공개 함수는 특정 함수 내부에서만 사용되는 함수를 말한다. 비공개 함수를 사용하면 얻는 이점은 아래와 같다.


function outer(param){
    function inner(innerParam){
        return innerParam * 2;
    }
    return inner(param);
}
outer(7);
// >> 14;

// 함수 리터럴로 작성하면 아래와 같다

const outer = function(param) {
    const inner = function (innerParam){
        return innerParam * 2;
    }
    return inner(param);
}
outer(7);
// >> 14;

// 람다로 작성하면 아래와 같다

const outer = (param) => 
{ 
    const inner = (innerParam) => { return innerParam * 2;};
    return inner(param);
}
outer(7);
// >> 14;

사용자 재정의 함수

함수는 함수를 반환할 수 있다.

const functionMom = () => {return () => {return 1;}};

functionMom();
// >> () => {return 1;}

const child = functionMom();
child();
// >> 1;

// 만약 함수를 반환하지 않고 값을 바로 리턴받고 싶다면 '즉시실행 함수'로 호출하면 된다. child 는 함수를 호출하는 반면, executeChild 는 변수를 호출하는 것을 유의.

const executeChild = (functionMoM())();
executeChild;
// >> 1;
executeChild();
// >> executeChild is not a function

함수는 스스로 내부에서 자신을 재작성할 수도 있다.

function a(){
	console.log('a');
	a = function() {	
		console.log('b');
    }
}
a();
// >> a;
a();
// >> b;

함수의 반환과 함수의 재작성을 응용하면 이런 식으로 운용이 가능해진다.

function a(f){
 
    if(typeof f === 'function'){
        worker = f;
    }

    function worker(){
        console.log('hello');
    }

    return worker;
}

const t1 = a();
t1();
// >> hello;
const t2 = a(() => {console.log('yaho')});
t2();
// >> yaho;

클로저

클로저는 자바스크립트의 scope 를 응용한 개념이다.

범위 체인

자바스크립트에는 함수 범위가 있다. 함수에 정의 된 변수는 함수 밖에서는 볼 수 없지만, 함수 코드 블록에 정의 된 변수는 블록 외부에서도 볼 수 있다.


let a= 1;
function f(){
    let b = 1;
    return a;
}
f();
// >> 1
b;
// >> b is not defined

이 특징은 아래와 같다.

함수 안에서 블록 밖 외부 변수에 접근할 수 있는 것을 scope chain(범위 체인) 이라고 일컫는다.

scope chain 의 극적인 걸 보여주기 위한 변태스런 코드는 아래와 같다.


const global_val = 1;
function outer(){
    let outer_var = 2;
    function inner(){
        let inner_var = 3;
        return inner_var + outer_var + global_val;
    }
    return inner();
}
outer();
// >> 6;

클로저는 이러한 scope 의 특징을 활용해, scope 문제로 접근하지 못하는 것을 ‘곁다리 함수’ 를 통해 접근하는 것을 말한다. 여기서 곁다리란 이놈 저놈 다리를 걸치고 있다는 의미로 말한다.

클로저 1

let a = 'global var';

const f = function(){
    let b = 'local var';
    a = () => {return b;} ;
}
b;
// b is not defined
(f())();
// local var;

클로저 2

위의 예제가 직관적이었다면, 이번에는 조금 복잡하다.


let cc; // 자리표시자
const $f = function(){
    let b = 'local var';
    let inner = function(){
        return b;
    }
    cc = inner;
};

b;
// b is not defined

$f();
// 여기서 중요한건 $f() 함수를 실행한 후에야 cc 에 inner 가 할당 된다.
cc();
// >> local var

정의와 클로저 3


const functionFac = ($f) => {
    let _f;
    if(typeof $f === 'function'){
        _f = $f;
    }else{
        _f = () => {console.log('nyaa');}
    }
    return _f;
}

const a1 = functionFac();
a1();
// >> nyaa

const a3 = functionFac((x) => {return x+x;});
a3(12);
// >> 24

루프의 클로저

function f() {
    let arr = [],i;
    for (i = 0; i < 3; i++) {
        arr[i] = function (){
            return i;
        }
    }
    return arr;
}

const c1 = f();
<!-- 
for  안에서 3번을 걸쳐 함수 처리의 결과값을 기대한다. 문제는 함수의 호출은 생성된 환경에 대한 참조만 유지되기 때문에, 3번을 걸쳐 함수가 수행된 뒤에서야 값이 반환됨으로 최종 수행값인 3 대한 값을 첫번째 호출, 두번째 호출, 세번째 호출에 반환한다. -->
console.log(c1[1]())
// >> 3


function f2() {
    let arr = [],i;
    for (i = 0; i < 3; i++) {
        
<!-- 특정 값을 받아서 단순히 return 해주는 익명함수를 만든다, 그리고  익명함수를 즉시실행 함수로 실행을 시키는 , 여기에 인자값으로 i의 값을 넣어준다. -->
        arr[i] = ((x) => {
            return x;
        })(i);
    }
    return arr;
}

const c2 = f2();
console.log(c2[1]);
// >> 1

위의 f2 함수 내부를 보면 람다로 작성한 익명함수가 있다. 함수의 호출에는 참조 값만 기억되기 때문에 3번의 호출이 일어난 후의 값이 저장되어서, 호출이 일어날 때마다의 즉시 값을 받아올 수가 없다. 이걸 우회하고자 중간함수(middle function) 이라는 함수를 내부에 만들어 호출이 될 때마다 i의 값을 중간함수의 지역변수인 x에 담게하고 이를 즉시 반환하게 해서 원하는 바를 동작할 수 있게 되어진다.

아래와 같이 중간함수를 외부에 미리 만들어두고 쓰는 방법으로 깔끔하게 처리할 수도있다.

function binder(x){
    return x;
}



function f3(){
    let arr =[];
    for(let i=0; i< 3; i++){
        arr[i] = binder(i);
    }
    return arr;
}

const c3 = f3();
console.log(c3[1]);
// >> 1

게터와 세터

클로저를 활용하면 아래와 같은 getter와 setter 를 통해서 내부 변수의 접근 제어를 할 수 있다.



let getVal,setVal;

(function(){
    let secret = 0;

    getVal = function(){
        return secret;
    }

    setVal = function(val){
        if (typeof val === 'number') secret = val;
    }

})()

console.log(getVal());
// >> 0

setVal(7);

console.log(getVal());

// >> 7

secret;
// >> secret is not defined

이터레이터

클로저를 통해 아래와 같은 반복자를 만들 수도 있다.



function setup(x){
    let i = 0;
    return function() {
      if(x.length <= i){
            i = 0;
        }
        return x[i++];
    }
}

const next = setup(['a','b','c','d']);

console.log(next());
// >> a
console.log(next());
// >> b
console.log(next());
// >> c
console.log(next());
// >> d
console.log(next());
// >> a
console.log(next());
// >> b
console.log(next());
// >> c

IIFE 대블록

IIFE 대블록은 즉시 호출 함수(Immediately Invoked Function Expressions)를 말한다. IIFE 는 블록 범위 내에는 고유의 scope 를 가지게 되어서 외부에서 접근이 불가능해진다.


(function(){
    let secret = 'haha';
})();

console.log(secret);
// >> secret is not defined

객체

객체

객체에는 속성(property)가 들어있다고 표현한다. 함수는 데이터일 뿐이므로, 객체의 속성은 함수를 가르킬 수 있다.


    let dog = {
        name : 'Benji',
        talk : function(){
            alert('woof, woof');
        }
    }

배열

배열에는 요소(element)가 들어 있다고 표현한다.

해시와 연관 배열

일부 프로그래밍 언어에는 다음 둘 사이에 차이가 있다.

자바스크립트는 배열을 사용해 인덱스 배열과 객체를 표시해서 연관배열을 나타낸다. 자바스크립트에서 해시를 원한다면 객체를 사용하면 된다.

객체의 속성 접근

자바스크립트에서 객체 속성에 접근하는 방법은 두 가지가 있음.

    dog['name']

아래는 개인적인 이야기인데, 도트 표기법이 읽고 쓰기 용이해서 자주 쓰는 편이다. 다만, 속성의 이름이 유효한 변수 이름이 아닐 경우에는 도트 표기법 한정으로 syntax 에러를 내기도 하는 게 있어서 주의해야한다 -_-;

예를 들면 아래와 같다.

    let dog = {'name' : 'lemji', 'bow-wow' : function (){alert('wow');}};

    dog.bow-wow;
    // >> wow is not defined

    dog['bow-wow'];
    // >> ƒ (){alert('wow');}

객체는 포함 된 다른 객체의 데이터에도 접근이 가능하다.


    let dog = 
    {'name' : 'lemji', 
    'bow-wow' : function (){alert('wow');}, 
    child : {name : 'cuti' }
    }
    
    dog.child.name;
    // >> cuti

    dog['child']['name'];
    // >> cuti

대괄호 표기법은 호출 속성을 변수로 지정할 수 있기에, 동적으로도 접근이 가능하다.


    const key = 'name';
    
    let dog = 
    {'name' : 'lemji', 
    'bow-wow' : function (){alert('wow');}, 
    child : {name : 'cuti' }
    }

    dog[key];
    // >> lemji



객체의 메소드 호출

메소드와 함수에 대해 혼동해 하는 사람이 많은 데, 메소드는 객체에 의존적이라는 점에 차이점이 있다. 다르게 표현하면 객체가 없으면 존재할 수 없다. 클래스 기반 언어 에서의 1등시민은 클래스이기 떄문에 어떠한 기능을 수행하기 위해서는 그 기능을 수행하는 객체가 먼저 선언(또는 호출)이 되어야 기능을 동작시킬 수 있다.


public class Man{
    public void greeting(){
        ...
    }
}

public class mainClass{

    public static void main(){
        Man man = new Man();
        man.greeting();
        ...
    }

}

반면 함수는 함수 기반 언어에서의 1등 시민이 함수 그 자체가 되기 때문에 객체고 뭐고 아무것도 필요없이 함수 자체를 호출해서 기능을 수행할 수 있다.


    function greeting(){
        ...
    };

    greeting();

또한 클래스 언어는 객체를 찍어내는 ‘틀’ 이라는 개념이기에, 메소드 간의 복사(공유)가 어렵다. 반면 함수 기반 언어에서는 함수를 여러 객체에게 복사하거나 던지거나 지지고 볶고가 자유롭다.

그래서 객체 기반 언어에서 메소드와 함수를 ‘기능’ 이라는 의미가 같다고 해서 이를 혼용해서 얘기하는 사람이 있으면 조심스레 피하자.. -_-;

this 값 사용

객체 안의 메소드는 객체의 속성(필드, 또는 멤버라 불리는)이나 메소드에 접근할 때 특별한 키워드인 this 를 통해 자유롭게 접근할 수 있다.


    let dog = {
        name : 'venji',
        bow : function(){
            return 'wow~~'+this.name;
        }

    }
    dog.bow();
    // >> wow~~venji

생성자 함수

객체를 생성하는 방법에 생성자(constructor) 함수를 사용하는 방법도 있다. 속성이 선언 되지 않았음에도 this 키워드로 생성되면서 만들어지는 게 부자연스럽지만, 자바와 매우 유사하다.


function Dog(name){
    this.name = name;
    this.bow = function(){
        return 'wow~~~'+this.name;
    }
}

    let dog1 = Dog('venji');

    dog1.name;
    // >> venji
    dog1.bow();
    // >> wow~~~venji;




규약에 따라 생성자 함수는 문자의 어두를 대문자로 해야한다. (마치 객체 언어처럼) 또한 생성자 함수는 new 연산자를 통해야만 의도되로 동작한다.

function Dog(name){
    this.name = name;
    this.bow = function(){
        return 'wow~~~'+this.name;
    }
}

let dog1 = Dog('venji');
dog1.bow();
// cannot read property 'bow' of undefiend;
name;
// >> venji


재밌는 것은 new 키워드 없이 Dog 함수를 호출 했을 때에, 내부의 this 가 가르키는 것은 전역 객체라는 것이다. 이런 이유로 name 이라는 전역 변수를 호출하면 ‘venji’ 가 값으로 들어가있는 걸 확인할 수 있다..

전역 객체

호스트 환경이 웹 브라우저이 경우, 전역 객체는 window 로 호출이 된다. 이 말은 웹 브라우저의 웹 페이지 안에서의 자바스크립트 코딩은 window 안에서 이리 뛰고 저리 뛸 수 있는 플레이그라운드가 된다는 소리이다.

전역 객체의 접근은 생성자 함수 바깥에서 this 키워드를 사용하거나 window 의 속성으로 접근하면 된다.


var globalVar = 123;
// let 으로 선언 시에는 window의 속성으로 할당 되지 않는다.
// let globalVar = 123;

globalVar;
// >> 123;

window.globalVar;
// >> 123;

window['globalVar'];
// >> 123;


재밌는 것은 var 키워드의 전역 변수 선언만 window 객체의 속성으로 할당이 된다. 이유는

Both are still global, but vars are stored in the window object, while lets are stored in a declarative environment that you can’t see (just like you can’t access a variable in a function scope from outside of the function). When declared globally, both statements are pretty much identical in use.

let 은 ECMA 사양에 따라, window 의 속성에 들어가지 않도록 제안을 했다는 내용을 검색으로찾아볼 수 있었다.

생상자 속성

객체가 생성되면 특별한 constructor 속성이 내부적으로 만들어진다. 특정 객체의 constructor 속성은그 객체를 생성하는 생성자 함수를 참조하고 있다. 이를 응용하면 아래와 같은 같은 타입의 객체 생성을 아래의 코드처럼도 할 수 있다.

function Dog(name){
    this.name = name;
    this.bow = function(){
        return 'wow~~~'+this.name;
    }
}
let dog1 = new Dog('venji');
dog1.name;
// >> venji

let dog2 = new dog1.constructor('qqq');
dog2.name;
// >> qqq;

instanceof 연산자

객체의 생성 정보를 알아 볼 수 있는, instanceof 연산자를 쓰면 객체가 어떻게 생성되었는지 알 수 있다.

function Dog(name){
    this.name = name;
    this.bow = function(){
        return 'wow~~~'+this.name;
    }
}

let d1 = {};

let d2 = new Dog('ven');

d1 instanceof Object;
// >> true;


// Dog() 라 쓰지 않고 Dog 라고 쓰는 것은 함수 이름으로 객체 원형을 찾기 때문이다.
d1 instanceof Dog;
// >> false;

d2 instanceof Dog;
// >> true;

d2 instanceof Object;
// >> true;

typeof 와의 차이점은 typeof 는 6가지의 대표적인 객체 자료형을 알아본다면, instanceof 는 객체의 생성 원형을 알아보기 때문에 객체에만 쓸 수 있다는 점이 차이점이 있다.

객체 생성의 주의점

function SomeClass(){
    this.aaa= 1;
    return {bbb:2};
}

let s1 = new SomeClass();
s1.aaa;
// >> a is undefined;
s1.bbb;
// >> 2;
aaa;
// aaa is not defined;


해괴망측한 결과가 나왔다. 생성자 함수 내부에 return 이 있고, 이의 return 이 Object 라면 this.aaa 키워드는 무시가 된다. 이것은 this 가 생성자 내부에서 가장 맨처음에 선언이 되고 마지막에 할당되기 때문이다.


function SomeClass(){
    // var this = {};
    this.aaa = 1;
    // {aaa : 1};
    //  this = {aaa : 1};
    return {bbb:2};
    // this = {bbb:2};
}

객체 리터럴

객체를 선언하는 것에는 약식 구문을 사용할 수 있다. ES6 에서는 아래와 같이 간략히 할 수 있다. 이전에는 key 선언을 무조건 정적으로만 했었지만, ES6에 와서는 동적으로 가능해졌다.


let obj = {
    prop : 1,
    modifier : function(){
        console.log(this.prop);
    }
}

// ES6
let obj2 = {
    prop : 2,
    modifier(){
        console.log(this.prop);
    }
}

// 동적 속성 key 선언

const keyName = 'haha_';

let obj3 = {
    prop : 3,
    [keyName + "modifier"] : function(){
        console.log(this.prop);
    }
}

obj3.haha_modifier();
// >> 3

객체 복사

Object.assign 이라는 헬퍼 메소드를 사용하면, 얇은 카피를 할 수 있다. 얇은 카피란 A 와 B 가 있을 시 B가 A를 완전 대체하지 않고 덮어쓰는 걸 말한다. 반면 완전 카피는, B가 A를 완전 대체할 정도로 복사되는 경우를 말한다.


let a = {name : 'jj'};
const someObj = {age : 25}
Object.assign(a, someObj);
a.name;
// >> jj
a.age;
// >> 25;
a.age = 10;
a.age;
// >> 10;
someObj.age;
// >> 25;
someObj.name;
// >> undefiede;


디스트럭처링

ES6 는 디스트럭처링(destructuring) 이라 하여, 객체의 속성을 외부 변수에 분할할당할 수가 있다. 아래 코드를 통해 설명하겠다.


let config = {
    server : 'localhost',
    port : 8080,
    timeout : 900
}

let {server,port} = config;
console.log(`${server}, ${port}`);
// >> localhost , 8080

let {timeout : t} = config;
console.log(`${t}`);
// >> 900

let exist_server = '127.0.0.1';
let exist_port = 9999;

console.log(`${exist_server}, ${exist_port}`);
({server : exist_server, port : exist_port} = config);

console.log(`${exist_server}, ${exist_port}`);

변수 server 와 port 는 config의 속성인 config.server 와 config.port 와 이름이 같아, 디스트럭처링을 통해 속성의 값을 할당 받았다. 또한 t 변수의 경우는 속성 timeout 와 변수 네이밍이 다른데도, config 의 timeout 속성을 선언을 해서 할당받도록 하였다.

더 놀라운 것은 exist_ 로 시작하는 변수들에는 자신들의 값이 이미 있지만, ({ } = ) 구문을 통해서 디스트럭처링을 할 수가 있다. 이미 값이 할당 된 변수에 디스트럭처링을 적용하기 위해서는 () 겉에 괄호를 싸줘야 한다.

디스트럭처링 고급

어떠한 인자를 받는 함수의 경우에 아래 처럼 디스트럭처링으로 값을 넣어줄 수 있다.


let config = {
    server : 'localhost',
    port : 8080
}

function startServer(config){
    console.log(config);
}

startServer({server, port, timeout = 999}= config);
// >> { server: 'localhost', port: 8080, timeout: 900 };

재밌는 것은 startServer에 인자로 넘어간 속성들 중에 timeout 은 config 에 존재하지 않는 속성이다. 디스트럭처링을 통해 할당하지 못할 속성(원래 존재 하지 않는 속성)에 default 값을 선언해서 undefiend 가 발생하지 않게도 할 수 있다.

디스트럭처링은 배열에서도 적용이 가능하다.

const days = ['thursday','friday','saturday','sunday'];
let [,,sat,sun] = days;
console.log(`${sat},${sun}`);
// >> saturday,sunday

let [thurs,,,] = days;
console.log(`${thurs}`);
// >> thursday

let [x,...y] = days;
console.log(`${x},${y[0]},${y[1]}`);
// >> thursday,friday,saturday

배열에 디스트럭처링을 사용할 때는, 배열의 index 를 유의해서 할당시켜야 한다. 또한 나머지 연산자(...) 를 통해서도 할당이 가능하며, 이 경우에는 값이 배열로 할당 된다.

내장 객체

Object() 생성자 함수를 위에서 잠시 살펴 봤다. Object() 는 내장된 생성자 중 하나이다.

내장 객체는 3가지 그룹으로 나눌 수 있다.

Object

Object 는 모든 자바스크립트 객체의 부모 객체이다. 생성한 모든 객체는 Object 를 자동 상속한다.


let o = {};
let oo = new Object();

빈 객체를 생성하는 방법을 2가지 방법으로 보여준다. 빈 객체를 생성하더라도 이 객체는 Object를 상속 받기 때문에 이미 포함되는 몇 가지 속성과 함수가 있다.


let o = {};
console.log(`${o.constructor}`);

// >> function Object() { [native code] }

console.log(`${o.toString()}`);
// >> [object Object]

console.log(`${o.valueOf()}`);

// >> [object Object]

아무리 복잡한 객체더라도 Object 객체를 상속하므로 toString() 같은 메소드와 constructor 같은 속성을 제공 받는다.

Array

배열을 생성하는 방법은 아래 2가지 방법이 있다.


let a = new Array();
a[0] = 1;
a[1] = 2;
console.log(`${a}`);
// >> 1,2

let aa = [];
aa[0] = 1;
aa[1] = 2;
console.log(`${aa}`);
// >> 1,2

let aaa = new Array(1,2,'3');
console.log(`${aaa}`);
// >> 1,2,3

let aaaa = new Array(5);
console.log(`${aaaa}`);
// >> ,,,,

console.log(typeof a);
// >> object
console.log(typeof aa);
// >> object
console.log(typeof aaa);
// >> object
console.log(typeof aaaa);
// >> object

배열은 객체이지만, 특별한 유형으로 취급된다.


let a = [8,5,7,'test'];
console.log(`${a}`);
// >> 8,5,7,test


// push(val) 는 배열의 가장 끝에 새로운 요소를 추가한다.
a.push('haha');
console.log(`${a}`);
// >> 8,5,7,test, haha


// pop(); 은 배열의 가장 끝에 있는 요소를 삭제한다.
a.pop();
console.log(`${a}`);
// >> 8,5,7,test


// sort() 는 배열의 요소들을 정렬한다.
a.sort();
console.log(`${a}`);
// >> 5,7,8,test


// join(val) 은 배열의 요소들에 덧붙여서 새로운 배열을 반환한다.
let b = a.join('_, ');
console.log(`${b}`);
// >> 5_, 7_, 8_, test


// slice(x,y) 는 특정 배열의 시작 위치에서 끝 위치까지 요소를 복사해서 반환한다.
let c = a.slice(1,3);
console.log(`${c}`);
// >> 7,8

// splice(x,y,val..) 는 특정 배열의 시작 위치에서 끝 위치까지 요소를 제거하고, 뒤에 들어오는 인자를 요소로 채워넣는다.
console.log(`${a}`);
// >> 5,7,8,test
a.splice(1,2,100,101,102);
console.log(`${a}`);
// >> 8,100,101,102,test



es6 의 추가된 내장 메소드

ES6 이전에는 객체와 배열에 대한 처리가 어려워 lodash 와 같은 유틸리티 라이브러리를 사용하곤 했다고 한다. 하지만 ES6 로 오면서 해당 기능들이 기본적으로 내장하게 되었음으로 사용성이 좀 더 용이해졌다.


// Array.from(arr) 은 인자로 들어오는 배열을 얇은 카피 한 배열을 반환해준다.
let man = {name: 'man', old: 15};
const arr = [1, 2, 3, man];
console.log(`${arr}`);
// >> 1,2,3,[object Object]

const arr2 = Array.from(arr);
console.log(`${arr2}`);
// >> 1,2,3,[object Object]

man.name = 'hihi';
console.log(`${arr[3].name}`);
// >> hihi
console.log(`${arr2[3].name}`);
// >> hihi

Array.of()

배열을 기본 문법으로 생성할 시에 아래와 같은 문제에 직면하기도 한다.


// 숫자 2 라는 단일 요소를 가진 배열을 만들고 싶음
let arr = new Array(2); 
console.log(`${arr}`);
// >> [, ,]
console.log(`${arr.length}`);
// >> 2

의도한 것과는 달리 2개의 빈 요소를 가진 배열이 생성이 되어버린다. 그래서 나온 것이 Array.of() 이다.

let arr = new Array(2);
console.log(`${arr.length}`);
// >> 2
let arr2 = Array.of(2);
console.log(`${arr2.length}`);
// >> 1

Array.prototype


const arr = ['a','b','c'];

for(const index of arr.keys()){
    console.log(index);
}

// 아래 Array.prototype.values() 메소드는 
for(const value of arr.values()){
    console.log(value);
}

for(const [index,value] of arr.entries()){
    console.log(index,value)
}

// find(val)
console.log(arr.find(x => x === 'b'));
// >> b;


// findIndex()
console.log(arr.findIndex(x => x === 'b'));
// >> 1;

Function

함수는 특별한 데이터 유형이면서도, 실제로 객체이다.

함수를 선언하는 방법은 아래 3가지 방법이 있다.


// 함수 선언
function sum(a,b){
    return a + b;
}

// 함수 표현식
const sum = function(a,b){ return a + b};

// Function() 내장 생성자 함수 호출
const sum = new Function('a','b','return a + b');

new Function() 내장 생성자를 호출해서 생성하는 경우는 eval() 과 같이 소스 코드를 평가하는 작업을 먼저하게 됨으로, 성능적인 측면에서 좋지 않다.

Function() 생성자는 eval() setTimeout() 과 같이 자바스크립트 코드를 문자로 전달하여 평가하게 하는 코드는 이득보다는 손해가 많으므로 사용하지 않는 것이 좋다.

함수 객체의 속성

함수는 아래와 같은 속성이 있다, 재밌는 점은 length 속성인데 이는 매개변수의 갯수를 뜻 한다.


function myFunc(a,b,c){
    return a+b+c;

}

myFunc.length;
// >> 3

myFunc.constructor;
// >> [function Function() {[native code]}]
prototype 속성

함수 객체에서 가장 널리 사용되는 속성 하나는 prototype 속성이다.


let ninja = { name : 'sake', say : function (){return `i am ninja boy ${this.name}`;}};

function NinjaFac(){

};

NinjaFac.prototype = ninja;

let n1 = new NinjaFac();
n1.name;
// >> sake

n1.say();
// >>i am ninja boy sake

함수 객체의 메소드

Function 객체는 최상위 부모 객체 Object 의 자손이다. 그러므로 toString() 같은 디폴트 메소드를 사용할 수 있다.


function NinjaFac(){

};

NinjaFac.toString();
// >> "function NinjaFac(){ }"

call 과 apply

함수 객체는 call() 과 apply() 메소드를 가진다. 이런 메소드를 사용하면 함수를 호출할 때 인수를 전달할 수 있다.

또한 이 메소드를 사용하면 다른 객체의 메소드를 빌려서 자신의 메소드 인 것 처럼 호출 할 수 있다.


let ninja = { name : 'sake', say : function (arg){return `wow! ${arg}, i am ninja boy ${this.name}`;}};

ninja.say('bow');
// >> wow! bow, i am ninja boy sake

let n1 = {name : 'coyote'};

ninja.say.call(n1,'great!');
// >> "wow! great!, i am ninja boy coyote"


ninja.say.apply(n1,['great']);
// >> "wow! great!, i am ninja boy coyote"

ninja.say.call('ww');
// >> "wow! undefined, i am ninja boy undefined"

ninja.say.call(null,'ww');
// >> "wow! ww, i am ninja boy "

call 을 사용하게 되면 this 값에 대한 참조가, call 의 첫번째 인자로 넘어오는 객체의 this 를 가르키게 된다. 만약 첫번째 인자인 객체가 null 일 경우에는 전역 객체를 this 로 가르키게 된다.

apply() 메소드는 call() 과 같은 방식으로 동작하지만, 매개변수가 배열로 전달된다는 차이점이 있다.

인수 객체 재검토

function f(){
return arguments;
}

f(1,2,3);
// >> [1,2,3];

f(1,2,3).length;
// >> 3

f(1,2,3).slice();
// >> slice is not a function

arguments 는 배열같아 보인다. length 도 지원하고 있지만, 배열의 내장 메소드인 sort(), slice()와 같은 것이 없어 배열과 유사한 오브젝트일 뿐 인 것을 알 수 있다.

하지만 prototype 을 통해서 없던 sort()나 slice()를 적용 할 수 가 있어진다.


function f(){
    const args = Array.prototype.slice.call(arguments);
    // const args = [].slice.call(arguments);
    return args.reverse();
}
f(1,2,3,4);
// >> [4,3,2,1];

Array.prototype.slice.call() 이나, [].slice 를 통해서(이 경우는 prototype 이 생략 됨) slice()의 기능을 빌려올 수가 있다.

화살표 함수에서의 어휘적 this

함수에는 메소드가 호출된 객체를 참조하는 특별한 변수 this가 있따. this의 값이 함수 호출을 기반으로 동적으로 주어지기 때문에, 종조 동적인 this라고도 불린다. 함수는 어휘적(lexical) 또는 동적(dynamic) 이라는 두 가지 범위에서 실행된다. 어휘 범위는 함수 범위를 둘러싼 범위이며, 동적 범위는 함수를 호출하는 범위(일반적으로객체)이다.

자바스크립트에서 전통적인 함수는 여러가지 역활을 수행한다. 바로 메소드가 아닌 기능으로서의 함수(서브 루틴 또는 함수라고 읽음)이나 메소드(객체의 일부) 또는 생성자이다.


let greeter = {
    default : 'hello',
    greet : function(names){
        names.forEach(function(name){
            console.log(this.default + name);
            // >> default is undefined
        
        

        })
    }
}
console.log(greeter.greet(['world','heaven']));
// >> undefinedworld
// >> undefinedheaven

greeter 의 메소드인 greet 의 this에 forEach의 인자로 들어가는 서브 루틴에서 접근할 수가 없다. 그래서 this.default 가 undefined 로 나온다.

이것은 클로저를 통해서 greet 메소드 내부를 접근할 수가 처리하는 해킹으로 가능할 수 있다.

let greeter = {
    default : 'hello',
    greet : function(names){
        const that = this;
        names.forEach(function(name){
            console.log(that.default + name);
    

        })
    }
}
console.log(greeter.greet(['world','heaven']));
// >> hellodworld
// >> helloheaven

이 경우 합리적으로 greet 메소드의 this 인 greeter 내부에 접근할 수가 있어진다. 다만 이렇게 되면 일일히 that 의 흐름을 눈으로 따라가는 불편함이 생긴다. 콜백지옥이라고 불리우는 코드에서는 흐름을 쉽게 따라가기 어렵다.

ES6 의 람다(애로우 함수) 에서는 서브 루틴로 사용 되는 애로우 함수에 어휘적인 this 를 가지게 되어서 위의 코드 해킹 없이도 this를 호출할 수 있다!


let greeter = {
    default : 'hello',
    greet : function(names){
        names.forEach((name) => {console.log(this.default + name);})
    }
}
console.log(greeter.greet(['world','heaven']));
// >> hellodworld
// >> helloheaven

Boolean

Boolean 과 Number, String 은 원시 데이터 유형을 래핑하는 object이다.


let b = new Boolean();
b;
// >>  Boolean{false}
b.valueOf();
// >> false

let b2 = Boolean();
b2;
// >> false


new 키워드를 통해 생성하게 되면, object로 생성이 된다. 반면 new 키워드 없이 할 경우 부울 원시 타입으로 생성이 된다.

아래 처럼 검증 로직으로 활용할 수도 있다.


let c = Boolean('hello');
c;
// >> true

let cc = Boolean('');
cc;
// >> false

if(!Boolean('')){
    alert('값이 없습니다');
}

Boolean 객체는 특별한 static 메소드를 제공하지 않으므로 자주 사용되지 않는다.

Number

Number.MAX_SAFE_INTEGER
// 9007199254740991
Number.MAX_VALUE
// 1.7976931348623157e+308
Number.MIN_SAFE_INTEGER
// -9007199254740991
Number.MIN_VALUE
// 5e-324
Number.POSITIVE_INFINITY
// Infinity
Number.NEGATIVE_INFINITY
// -Infinity


let n1 = new Number(123.456);
n1.toFixed(1);
// 123.5
n1.toFixed();
// 123
n1;
// Number{123.456}

String

String 에는 괜찮은 static 메소드들이 있다.


let primitive = 'Hello';
typeof primitive;
// >> 'string'

let objPri = new String('Hello');
typeof objPri;
// >> 'object'

String은 new 키워드로 생성시에 Object 로 만들어진다. String 은 문자 배열의 조합과 같다. String 은 각 문자에 대한 인덱싱된 속성을 가지고 있다.

let primitive = 'Hello';
primitive[0];
// >> 'H'

let objPri = new String('Hello');
objPri.length;
// >> 5;
objPri[0];
// >> 'H'

objPri;
// >> 'Hello'

'Hello'[0];
// >> 'H'
'Hello'.length;
// >> 5


objPri 와 같은 Object 형은 toString() 이나 valueOf() 를 호출해야지 실제 문자열을 가져올 수 있다. 다만, 예외적으로 백그라운드에서 자동으로 호출되므로 이 작업을 굳이 할 필요는 없다.

재밌는 점은 변수에 문자열을 할당하지 않은 ‘Hello’ 의 경우에서 보면 알 수 있듯이, typeof string 인 string 타입의 원시값에는 객체 메소드인 toString() 이나 length 가 없지만 실제로 동작할 수 있는 걸 알 수 있다.

Boolean('');
// >> false
Boolean(new String(''));
// >> true

string 원시 타입과 String 객체의 결정적인 차이는 원시타입은 ‘’ 의 경우 null 취급되는 것을 알 수 있고, new String() 으로 생성된 String 객체는 객체 자체가 생성 되어있음으로 null 이 아니어서 true 로 반환되는 걸 알 수 있다.

'Hello'.indexOf('Hello');
// >> 0
'Hello'.toLowerCase();
// >> hello

'Hello'.slice(1,5);
// >> ello
'Hello'.substring(1,5);
// >> ello

'Hello'.concat('world');
// >> Helloworld

Math

Math 는 new 키워드가 없다. 즉, 일반적인 함수가 아니다. Math 는 수학 연산의 여러 메소드와 속성을 제공하는 내장 전역 객체다.

Math.random();

Math.random() * 100;

Math.round(Math.random());


...


Date

Date() 는 날짜 객체를 생성하는 함수.


new Date(2015,0,1,17,05,03,120);
// >> Thu Jan 01 2015 17:05:03

new Date(2015,1,30);
// >> Mon Mar 02 2015 00:00:00

let d = Date.parse(2018-09-01);
new Date(d);
// >> Sat Sep 01 2018 00:00:00

주의할 점은 월은 0부터 시작한다는 점이다. 또한 1월 30일은 존재하지 않은 일로, 이 경우 다음 달인 2월 1일로 출력이 되므로, 변태가 아닌 이상 편리하게 사용할 수 있다.

RegExp

언어별로 정규 표현식 구문의 구현이 조금씩 다른데, 자바스크립트는 펄(Perl) 방식의 5구문을 사용한다.

정규식을 만드는 방법은 2가지가 있다.


let re1 = new RegExp("[0-9]");

let re1_2 = new RegExp("[0-9]","gmi);

let re2  = /[0-9]/;

정규표현식에는 속성이 있다.

    
    let re = /[0-9]/g;    
    re.test('123');
    // >> true
    re.exec('123');
    // >> [ '2', index: 1, input: '123' ]

    '123'.match(re);
    // >> [ '1', '2', '3' ]

<!-- search  최초로 매칭되는 문자의 index를 반환한다. -->
    '123'.search(re);
    // 0
    'a23'.search(re);
    // 1
    'abc'.search(re);
    // -1

<!-- g 옵션과의 차이를 확인 -->
    'a2b3'.replace(/[0-9]/g,'1');
    // a1b1

    'a2b3'.replace(/[0-9]/,'1');
    // a1b3

replace 는 콜백함수로를 받을 수 있다.


function replaceCallBack(match){
    return "_"+match+"_";
}

'a1b2d3'.replace(/[0-9]/g,replaceCallBack);
// >> a_1_b_2_d_3_

에러 처리

자바스크립트도 다른 언어들처럼 try~ catch~ finally 를 지원한다.

try{
    console.log("i'm normal_1");
    hello();
    console.log("i'm normal_2");
}catch(e){
    console.error("i'm error")
    console.log(e.name);
    // >> RegerenceError
    console.log(e.message);
    // >> hello is not defined
    
}finally {
    console.log("i'm finally");
}

// 결과..

// i'm normal_1
// i'm finally
// i'm error
// ReferenceError: hello is not defined
//     at Object.<anonymous> (C:\Users\....\IdeaProjects\some-node-code\app.js:3:5)
//     at Module._compile (module.js:649:14)
//     at Object.Module._extensions..js (module.js:663:10)
//     at Module.load (module.js:565:32)
//     at tryModuleLoad (module.js:505:12)
//     at Function.Module._load (module.js:497:3)
//     at Function.Module.runMain (module.js:693:10)
//     at startup (bootstrap_node.js:191:16)
//     at bootstrap_node.js:612:3
// Debugger attached.
// Waiting for the debugger to disconnect...

주의 할 점이 있다면 e.name 이다. 호스트(브라우저 등)에 따라 찍히는 name이 일관적이지 않을 수 있다. 최신 브라우저의 경우 ReferenceError 로 일정하게 값이 나오지만, 구형 브라우저의 경우 이와 다른 값이 나올 수 있다.

throw 도 지원한다. 재밌는 것은 e.name 이 Error 로 나온다는 것.



try{
    let nullC = null;
    if(nullC === null){
        throw new Error('What');
    }else{
        console.log('good');
    }
}catch(e){
    console.log(e.name);
    // >> Error
    console.log(e.message);
    // >> What
    console.error(e);
    
}finally {
    console.log('end');
}

요약

와 같은 내장 생성자들이 몇 있다.

Promise

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise