서평 - 코어자바스크립트

코어자바스크립트란?

  • 자바스크립트로 개발한다면 무조건 봐야 한다고 소문이 자자한 책.
 

코어 자바스크립트 - 예스24

자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아니

www.yes24.com

  • 입문자가 학습하기에는 난이도가 있지만, 중급자로 넘어가기 위해서 반드시 알아야 하는 핵심 내용이 들어있는 책.
  • 대학교 학부 시절, 이 책을 처음 접했을 때는 이해하기 어려운 개념이 많았고, 잘 읽히지 않아 조용히 책을 다시 덮었던 기억이 있다.

 

이 책을 다시 읽기로 마음 먹은 계기는?

  • 현업에서 계속해서 개발을 하면서 항상 뭔가 2% 부족한 부분이 있다고 느끼게 되었고, 이 문제가 기초 개념이 탄탄하지 못함에서 비롯된 것이라는 깨닫게 되어 다시 이 책을 읽게 되었다.

1. 데이터 타입

1-1) 데이터 타입의 종류

  • 기본형(primitive type)
    • 할당이나 연산시 복제, 값이 담긴 주솟값을 바로 복제
    • 즉, 값을 그대로 할당
    • number(숫자), string(문자열), boolean(불리언), null, undefined, Symbol(ES6)
  • 참조형(reference type)
    • 할당이나 연산시 참조, 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제
    • 즉, 값이 저장된 주소를 할당
    • object(객체), array(배열), 함수, 날짜, Map(ES6), WeakMap (ES6), Set (ES6), WeakSet (ES6)

1-2) 데이터 타입에 관한 배경지식

  • 비트 : 하나의 메모리 조각
  • 바이트:  표현 가능한 개수에 어느정도 제약이 따르더라도 크게 문제가 되지 않을 적절한 공간 묶기, 1바이트는 8비
  • JS에서 숫자의 경우 정수형인지 부동소수형인지를 구분하지 않고 64비트, 즉 8바이트를 확보 
  • 문자열의 경우 메모리 용량이 매우 가변적(영어는 1바이트, 한글은 2바이트 등)
  • 각 비트는 고유한 식별자(메모리 주솟값)를 통해 위치 확인 가능, 바이트 역시 시작하는 비트의 식별자로 위치를 파악할 수 있다.
  • 모든 데이터는 바이트 단위의 식별자, 더 정확하게는 메모리 주소값(memory address) 을 통해 서로 구분하고 연결할 수 있다
  • 변수(variable) : 변할 수 있는 데이터(숫자, 문자열, 객체, 배열 등)
  • 식별자(identifier) : 어떤 데이터를 식별하는 데 사용하는 이름( = 변수명)

1-3) 변수 선언과 데이터 할당

변수 선언:

var a; //  변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다.

데이터 할당:

var a;          // 변수 a 선언
a = 'abc';      // 변수 a에 데이터 할당

var a = 'abc';  // 변수 선언과 할당을 한 문장으로 표현
  • 실제로 해당 ‘a’ 변수 데이터에 문자열 ‘abc’를 직접 저장하지는 않는다. 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 ‘abc’를 저장하고, 그 주소를 변수 영역에 저장한다.

ex) 

변수 영역

주소 ··· 1002 1003 1004 1005 ···
데이터 ···   이름: a
값: @5004
    ···

 

데이터 영역

주소 ··· 5002 5003 5004 5005 ···
데이터       'abc'    
1) 변수 영역에서 빈 공간 (@1003)을 확보한다.
2) 확보한 공간의 식별자를 a로 지정한다.
3) 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
4) 변수 영역에서 a라는 식별자를 검색한다.(@1003).
5) 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.
  • 변수 영역에 값을 직접 대입하지 않고, 한단계를 더 거치는 이유?
    • 데이터 변환을 자유롭게 할 수 있게 함과 동시에, 메모리를 더욱 효율적으로 관리
    • 변수와 데이터를 별도의 공간에 나누어 저장해야 효율적으로 문자열 데이터의 변환을 처리가능

 

1-4) 기본형 데이터와 참조형 데이터

불변값

  • 변수와 상수를 구분하는 성질은 ‘변경 가능성’
    • 바꿀 수 있으면 → 변수
    • 바꿀 수 없으면 → 상수
  • 변수와 상수를 구분짓는 변경 가능성의 대상은 변수 영역 메모리
    • 한 번 데이터 할당이 이루어진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부
  • 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리
    • 기본형 데이터(숫자, 문자열, boolean, null, undefined, Symbol)은 모두 불변값
var a = 'abc';
a = a + 'def';
  • 변수 a에 문자열 abc를 할당했다가 뒤에 def를 추가하면 기존의 abc가 abcdef로 바뀌는 것이 아니라 새로운 문자열 abcdef를 만들어 그 주소를 변수 a에 저장
  • 문자열 값도 한 번 만든 값을 바꿀 수 없고, 숫자 값도 다른 값으로 변경 불가능
  • 변경은 새로 만드는 동작을 통해서만 가능

가변값

  • 참조형 데이터(객체, 배열, 함수, 날짜, 정규표현식, Map, WeakMap, Set, WeakSet)는 기본적인 성질은 가변값인 경우가 많다.
  • 설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있음

1-5) call by value VS call by reference - 함수 호출

1. call by value(값에 의한 호출)

원시 값을 다른 변수로 복사할 때는 값을 새로 생성한 다음 새로운 변수에 복사를 한다.

그러므로 새로운 변수의 값을 변경해도 원래 변수의 값은 변경되지 않는다.

var num1 = 5; 
var num2 = num1; 

var num2 = 7; 
console.log(num1); // 5

 

2. call by reference(참조에 의한 호출)

참조 값을 변수에서 다른 변수로 복사하면 원래 변수에 들어있던 값이 다른 변수에 복사되기는 마찬가지다.

그러나 차이는 그 값이 객체 자체가아니라 힙(Heap)에 저장된 객체를 카리키는 포인터(=참조, 주소)라는 점이다.

주소값을 직접 참조를 하기 때문에 원래 값이 변경될 수 있다.

var blog1 = new Object(); 
blog1.name = 'oppa'; 

var blog2 = blog1; 
blog2.name = 'cooding'; 

console.log(blog1.name); // 'coding'

 

1-6) undefined와 null

null 과 undefined 는 등록, 저장 여부

1. undefined

  • 비어있음을 뜻하는 것으로 Data 자체가 존재하지 않음을 의미
  • 어떤 변수에 값이 존재하지 않을 경우를 의미
  • 값은 값이지만 값으로써 의미 없는 특별한 값이 등록되어 있는 것
  • 미리 선언된 전역변수(전역 객체의 프로퍼티)임

2. null

  • 비어있음을 명시적으로 나타낼 수 있는 방법
  • 프로그래밍 언어에서 null은 변수에 값이 없다는 것을 의도적으로 명시(의도적 부재)할 때 사용함
  • 등록이 되어있지 않기 때문에 초기화도 정의되지도 않은 것
  • 선언,등록을 하는 키워드

동등연산자(==)를 사용하면 두 값이 같다고 간주하기 때문에 이를 구별하기 위해서는 엄격한 일치연산자(===)를 사용해야 함

 

2.실행 컨텍스트

  • 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
  • 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장
  • 동일한 환경 - 하나의 실행 컨텍스트를 구성할 수 있는 방법
    • 전역공간, eval()함수, 함수 등이 호출될 때

2-1) 실행 컨텍스트가 담고 있는 정보

  • Variable Environment
    • environmentRecord (snapshot) : 현재 컨텍스트 내의 식별자들에 대한 정보
    • outerEnvironmentReference (snapshot) : 외부 환경 정보
  • Lexical Environment
    • environmentRecord : 현재 컨텍스트 내의 식별자들에 대한 정보
    • outerEnvironmentReference : 외부 환경 정보
    • Variable Environment와 동일하지만 변경사항이 실시간으로 반영됨
  • Variable Environment에 정보를 먼저 담고, 이를 그대로 복사해서 Lexical Environment를 만들고 이후 Lexical Environment를 주로 사용
  • ThisBinding : 식별자가 바라봐야 할 대상 객체
  • environmentRecord
    • 매개변수명선언된 함수 자체변수명 등이 담김
    • 컨텍스트 내부를 처음부터 까지 탐색하며 순서대로 수집

2-2) 호이스팅

  • 식별자들을 최상단으로 끌어올리는 것을 뜻함
  • 편의상 끌어올리는 것으로 간주하는 가상의 개념이며 실제로 끌어올리지는 않음

2-3) 함수 선언문과 함수 표현식

  • 함수 선언문 : function 정의부만 존재하고 별도의 할당 명령이 없는 것, 함수 전체 호이스팅
  • 함수 표현식 : 정의한 function을 별도의 변수에 할당하는 것, 변수로 선언된 선언부만 호이스팅 되었고, 할당부는 원래 자리에 있음
console.log(sum(1, 2));//3
console.log(multiply(3, 4));// Uncaught TypeError: multiply is not a function

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

// 함수 표현식
var multiply = function (a, b) {
  return a * b;
};

호이스팅을 마친 상태

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

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) {
  return a * b;
};

2-4) 스코프(scope)

식별자(변수)에 대한 유효범위

'식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 **스코프 체인(scope chain)**이라고 함.

그리고 이것을 가능하게 하는 것이 LexicalEnvironment의 두 번째 수집 자료인  outerEnvironmentReference.

outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조.

여러 스코프에서 동일한 식별자를 선언한 경우, 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능

 

3.this 

  • this는 함수와 객체(메서드)를 구분하는 거의 유일한 기능
  • this는 함수를 호출할 때 결정됨

아래 5가지 규칙은 명시적 binding이 없는 한 늘 성립함

1) 전역공간에서의 this

1-1) 전역공간에서의 this는 전역객체(브라우저에서는 window, Node.js에서는 global)를 참조

  • 전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당함
var test = 20;
console.log(test); // 20
console.log(window.test); // 20
console.log(this.test); // 20

2) 메서드로서 호출할 때는 메서드 내부에서의 this

  • 함수
    • 그 자체로 독립적인 기능 수행
  • 메서드
    • 자신을 호출한 대상 객체에 관한 동작 수행

2-1) 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조

var func = function(x){
	console.log(this,x)
}

func(1); // window, 1

var obj = {
  method: func
};

obj.methid(2); // method, 2 

3) 함수로서 호출할 때 함수 내부에서의 this

3-1) 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조. 메서드의 내부함수에서도 같음

// 모든 함수들은 해당 함수가 가리키고 있는 this를 출력
var obj1 = {
  outer: function() {
    console.log(this); // {outer: ƒ}, 객체 obj1의 프로퍼티로 호출
        
    var innerFunc = function() {
      console.log(this); // Window, 함수로서 호출
    };
    innerFunc();
        
    var obj2 = {
      innerMethod: innerFunc // {innerMethod: ƒ}, 객체 obj2의 프로퍼티로 호출
    };
    obj2.innerMethod();
  }
};

obj1.outer();
  • ES6: this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수(arrow function)를 새로 도입함
var obj ={
	outer: function(){ 
		console.log(this); //{outer: f}
		var innterFunc = () => {
			console.log(this); //{outer: f}
		};
		innerFunc();
	}
};
obj.outer();

4) 콜백 함수 호출 시 그 함수 내부에서의 this

  • 콜백함수
    • 함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수A를 콜백함수라 함
    • 이때 함수 A는 함수 B의 내부로직에 따라 실행되며,this역시 함수B 내부로직에서 정한 규칙에 따라 값이 결정됨

4-1) 콜백 함수 내부에서의this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조

document.body.innerHTML += `<button id="a">클릭</button>`;
document.body.querySelector("#a")
  .addEventListener('click', function (e) {
    console.log(this); // <button id="a">클릭</button>
  });

5) 생성자 함수 내부에서의 this

  • 생성자
    • 프로그래밍적으로 구체적인 인스턴스를 만들기 위한 일종의 틀을 말함
    • new 명령어와 함께 변수를 호출

5-1) 생성자 함수에서의 this는 생성될 인스턴스를 참조

var Cat = function (name, age) {
  this.name = name;
  this.age = age;
}

var choco = new Cat("초코", 5);
var nabi = new Cat("나비", 3);

console.log(choco); // Cat {name: '초코', age: 5}
console.log(nabi); // Cat {name: '나비', age: 3}

명시적으로 this를 바인딩하는 방법

  • 위 규칙(5가지) 상황에 따라 달라지는 this)에 부합하지 않는 경우에는 다음 내용을 바탕으로 this를 예측할 수 있음

1) call, apply메서드는 this를 명시적으로 지정하면서 함수 또는 메서드를 호출

  • call 메서드
    • 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어
    • 임의의 객체를 this로 지정할 수 있음
    var func = function (a,b,c) {
      console.log(this, a, b, c);
    };
    
    func(1,2,3); // Window 1 2 3
    func.call({x:1},2,3,4); // 1 2 3 4
    
  • apply 메서드
    • call 메서드와 기능적으로 동일
    • 두 번째 인자를 배열로 받아, 요소들을 함수의 매개변수로 지정한다는 차이점이 있음
    var func = function (x,y,z) {
      console.log(this, x, y, z);
    };
    
    func.apply({x:1},[2,3,4]); // {x:1} 2 3 4
    
    var obj = {
      a: 1,
      method: function (x, y) {
        console.log(this.a, x, y);
      }
    };
    obj.method.apply({a:7},[2,3]); // 7 2 3
    

cf) call, apply 메서드 활용

  • 유사배열객체에 call, apply 메서드를 이용하면 배열 메서드를 활용할 수 있게 됨
  • ES6에서는 이를 위해 Array.from(순회 가능한 모든 종류의 데이터 타입을 배열로 전환) 메서드를 도입

2) bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만듬

  • ES5에서 추가된 기능
  • call 메서드와 비슷하지만 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드
var func = function (x,y,z) {
  console.log(this, x, y, z);
};

func(1,2,3); // Window 1 2 3

var bindFunc1 = func.bind({ x: 1});
bindFunc1(5,6,7,8); // {x:1} 5 6 7 8

3) 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 함

  • forEach, Set, Map 등
var report = {
  sum: 0,
  count: 0,
  add: function () {
    var args = Array.prototype.slice.call(arguments);
    args.forEach(function (entry) {
      this.sum += entry;
      ++this.count;
    }, this);
  },
  average: function () {
    return this.sum / this.count;
  },
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average());

 

4.콜백 함수

  • 다른 코드의 인자로 넘겨주는 함수. 즉, 함수에 파라미터로 들어가는 함수
  • 순차적으로 코드를 실행하고 싶을 때 사용
  • 콜백함수가 필요한 함수들에만 콜백 함수 사용이 가능(setInterval, addEvnetListner, setTimeout, map, forEach 등)
  • 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행됨
// ex) 0.3초마다 콜백함수가 자동으로 실행
var count = 0;
var 콜백함수 = function(){
	console.log(count); // count값 출력
	if(++count > 4) clearInterval(timer); //count를 1만큼 증가시킨 다음, 그 값이 4보다 크면 반복실행 종료
};
var timer = setInterval(콜백함수, 300)

4-1) 인자

콜백함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있음

4-2) 콜백함수에서의 this

  • 기본적으로는 this가 전역객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조

4-3) 콜백 지옥과 비동기 제어

자바스크립트는 기본적으로 동기적으로 동작하지만 싱글 스레드 언어이기 때문에 수많은 비동기적 핸들링이 함께함

1) 동기

  1. 현재 실행 중인 코드 가 완료된 후에야 다음 코드 실행
  2. 즉시 처리가 가능한 대부분의 코드

2) 비동기

  1. 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감
  2. 별도의 요청, 실행 대기, 보류 등과 관련된 코드

콜백 지옥

setTimeout(functionm(name){
	var coffeeList = name;
	console.log(coffeeList);

	setTimeout(functionm(name){
		var coffeeList += ',' + name;
		console.log(coffeeList);

		setTimeout(functionm(name){
		....
			setTimeout(functionm(name){
				....
			}, ....)
		}, ....)
	}, 500, '카페라떼');
}, 500, '카페모카');

4-4) 콜백지옥 해결방법

1. 기명함수로 전환

var coffeeList = "";

var addEspresso = function (name) {
  coffeeList = name;
  setTimeout(addAmericano, 500, "아메리카노");
};

var addAmericano = function (name) {
  coffeeList += ", " + name;
  setTimeout(addMocha, 500, "카페모카");
};
....

2. ES5 - Promise, Generator

// Promise: 비동기 작업이 완료될 때 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적인 표현이 가능
function addCoffee(name) {
	return function(prevName) {
	  return new Promise(resolve => setTimeout(() => {
			var newNAme = prevName ?(prevName + ','+name):name;
			resolve(newName)
		},500);
	}
} //Promise 사용시 작업이 끝났음을 알려주는 resolve를 인자로 받아들임.

addCoffee('에스프레소')
  .then(addCoffee('아메리카노'))
  .then(addCoffee('카페모카'))
  .then(addCoffee('카페라떼'))
// Generator
// *를 붙인 Generator함수를 실행하면 next메서드를 가진 Iterator가 반환된다. 
// next 메서드를 호출하면 Generator 내부에서 가장 먼저 등장하는 yield에서 함수 실행을 멈춘다.
var addCoffee = function(prevName, name) {
	setTimeout(function() {
    coffeeMaker.next(prevName ? prevName+','+name : name);
  }, 500);
};

var coffeeGenerator = function*() {
	var espresso = yield addCoffee('', '에스프레소');
  console.log(mocha);
	var americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  var mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  var latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
  
};

var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

3. ES2017 - async, await

var addCoffee = function(name) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(name);
    }, 500);
  });
};

var coffeeMaker = async function() {
  var coffeeList = '';
  var _addCoffee = async function(name) {
    coffeeList += (coffeeList ? ',' : '') + await addCoffee(name);
  };

  await _addCoffee('카페모카');
  console.log(coffeeList);
  await _addCoffee('카페라떼');
  console.log(coffeeList);
  await _addCoffee('아메리카노');
  console.log(coffeeList);
};

coffeeMaker();

 

5.클로저

클로저는 이 책에서 가장 헷갈리고 이해가 잘 가지 않는 개념 중 하나였다.

  • 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성

5-1) 대표 예제

  • outer는 inner함수 자체를 반환하는 함수이다.
  • outer 함수의 실행 컨텍스트가 종료될 때, outer2 변수는 inner함수를 참조하게 된다.
  • 따라서 outer2를 통해 outer의 실행 컨텍스트가 종료된 후에도 inner함수를 호출할 수 있다.
var outer = function() {
  var a = 1;
  var inner = function() {
    return ++a;
  };
  return inner;
};

var outer2 = outer(); //outer 종료, outer2는 inner 참조
console.log(outer2()); //2
console.log(outer2()); //3

5-3) inner 실행(outer2 호출) 시점에 outer는 이미 실행이 종료된 상태인데 어떻게 a에 접근할 수 있는 것일까?

  • 이는 가비지 컬렉터의 동작 방식 때문이다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않는다.
  • outer의 실행이 종료 되더라도 내부 함수인 inner를 참조하는 변수 outer2가 있기 때문에 outer는 가비지 컬렉터의 수집 대상에서 제외된다.
  • outer2에 의해 inner 함수의 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer함수의 LexicalEnviornment를 필요로 할 것이기 때문이다.
  • 따라서 inner함수가 a에 접근할 수 있는 것이다.

5-4) 따라서 클로저를 다시 정의해보자면

  • 클로저는 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말한다.
  • 내부함수를 외부로 전달하는 방법에는 함수를 return하는 경우뿐 아니라 콜백으로 전달하는 경우도 포함
  • 본질이 메모리를 계속 차지하는 개념이므로 더는 사용하지 않게 된 클로저에 대해서는 메모리를 차지하지 않도록 관리해줄 필요가 있음

5-5) 클로저의 활용

  1. 상태유지
  • 현재 상태를 기억하고 있다가 상태가 변경되면 그것을 최신상태로 유지하는 것
<body>
<button onclick="toggleText()">Toggle Text</button>
<p id="toggleText">This text is toggled!</p>

<script>
  function toggleButton() {
    let showText = false;
		// 1.클로저를 반환
    return function () {
      const toggleTextElement = document.getElementById('toggleText');
      // 3. 상태 변경
			showText = !showText;

      if (showText) {
        toggleTextElement.style.display = 'block';
      } else {
        toggleTextElement.style.display = 'none';
      }
    };
  }
	// 2. 이벤트 프로퍼티에 클로저를 할당
  const toggleText = toggleButton();
</script>

</body>
</html>

 

2. 접근권한제어(정보은닉)

  • 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 것
  • 자바스크립트는 기본적으로 변수 자체에 이러한 접근 권한(public, private, protected)을 직접 부여하도록 설계되어 있지 않다.
  • 클로저를 사용해 함수 차원에서 public한 값과 private한 값을 구분하는 것이 가능하다.
    • 외부에 제공하고자 하는 정보들을 모아서 return하고 내부에서만 사용할 정보들은 return하지 않는 것으로 접근 권한 제어가 가능
const outer = function () {
   let a = 1; 
   const inner = function () {
      return ++a;
   };
   return inner; // (1) 접근권한 표현
};

const outer2 = outer();
console.log(outer2());
console.log(outer2());

 

3. 부분적용함수

    • n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억 시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수
var add = function () {
	var result = 0;
	for (var i = 0l i < arguments.length; i++) {
		result += arguments[i];
	}
	return result;
};
var addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(5, 6, 7, 8, 9, 10);  // 55

 

 

4. 커링함수

  • 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것
  • 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 함
  • 중간 과정상의 함수를 실행한 결과는 그다음 인자를 받기 위해 대기만 할 뿐으로 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않음 (부분 적용 함수는 여러 개의 인자를 전달할 수 있고, 실행 결과를 재실행할 때 원본 함수가 무조건 실행)
var curry3 = function (func) {
  return function (a) {
   	return function (b) {
      return func(a, b);
    };
  };
};

var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8));  // 10
console.log(getMaxWith10(25));  // 25

var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8));  // 8
console.log(getMinWith10(25));  // 10

 

6.프로토타입

  • 자바스크립트는 프로토타입 기반 언어
  • 어떤 객체를 원형(prototype)으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻음

1) 프로토타입의 개념과 이해

1-1) constructor, prototype, instance

  • new 연산자로 Constructor를 호출하면 인스턴스가 만들어지는데, 이 인스턴스의 프로퍼티인 __proto__는 Constructor의 prototype을 참조함
  • 생성자 함수의 prototype에 어떤 메서드가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티를 사용할 수 있음
  • prototype은 객체이며, 이를 참조하는 __proto__ 역시 객체임

코드를 통해 알아보자.

let Person = function(name) {
  this.name = name;
};

Person.prototype.getName = function() {
  return this._name;
};

let suzi = new Person('Suzi');
suzi.__proto__.getName(); // (1) undefined

Person.prototype === suzi.__proto__ // true
  • Person이라는 생성자 함수의 prototype에 getName이라는 메서드를 지정함
  • Person의 인스턴스인 suzi는 __proto__를 통해 getName을 호출할 수 있음
  • (1)에서 undefined가 출력되는 이유는 함수를 메서드로서 호출하면 바로 앞의 객체가 곧 this가 되기 때문이다. __proto__ 객체에는 name 프로터티가 없기 때문에 undefined가 반환됨
  • this를 인스턴스로 사용하고 싶다면 __proto__는 생략 가능한 프로퍼티이므로, 아래 코드 처럼 suzi.getName()처럼 바로 호출할 수 있고 이때 Suzi가 정상적으로 출력됨
  • 이런 점 때문에 생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 됨
suzi.__proto__.getName
=> suzi(.__proto__).getName
=> suzi.getName

내장 생성자 함수 Array를 통해 확인해 보자.

let arr = [1, 2];
arr.forEach(function() {}); // (0)
Array.isArray(arr); // (0) true
arr.isArray(); // (x) TypeError: arr.isArray is not a function
  • 이 인스턴스의 __proto__ 는 Array.prototype을 참조하는데, __proto__ 가 생략 가능하도록 설계돼 있기 때문에 인스턴스가 push, pop, forEach, map, concat, every, filter, find 등의 메서드를 마치 자신의 것처럼 호출할 수 있음
  • Array의 prototype 프로퍼티 내부에 있지 않은 from(), isArray(), of(), arguments, length, name 등의 메서드들은 인스턴스가 직접 호출할 수 없을 것임

1-2) constructor 프로퍼티

  • 생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 프로퍼티가 있음 (인스턴스의 __proto__도 마찬가지)
  • 이 프로퍼티는 원래의 생성자 함수(자기 자신)를 참조
  • 인스턴스로부터 그 원형이 무엇인지 알 수 있는 수단(상속 흉내가 가능해진 측면)

다음 각 줄은 모두 동일한 대상(생성자)을 가리킴

[Constructor]
[Constructor].prototype.constructor
[instance].constructor
[instance].__proto__.constructor
Object.getPrototypeOf([instance]).constructor

2) 프로토타입 체인

2-1) 메서드 오버라이드

  • 만약 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 가진다면 메서드 오버라이드가 발생할 수 있음
  • 메서드 오버라이드는 말 그대로 메서드 위에 메서드를 덮어씌웠다는 표현

2-2) 프로토타입 체인

  • 프로토타입 체인
    • 어떤 __proto__ 프로퍼티 내부에서 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것
  • 프로토타입 체이닝
    • 프로토타입 체인을 따라 검색하는 것(= __proto__ 안에 다시 __proto__ 를 찾아가는 과정)
    • 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있음
    • 접근방식은 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며, 원하는 값을 찾으면 검색을 중단

2-3) 객체 전용 메서드의 예외사항

  • 어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하게 됨
  • 객체에서만 사용할 메서드는 프로토타입 객체 안에 정의할 수가 없음
  • 예외적으로, Object.create를 이용하면 Object.prototype의 메서드에 접근할 수 없는 경우가 있음

2-4) 다중 프로토타입 체인

  • 프로토타입 체인은 반드시 2단계(객체,나머지)로만 이뤄지는 것이 아니라 무한대의 단계 생성 가능

 

7.클래스

자바스크립트는 프로토타입 기반 언어라서 클래스 및 상속 개념은 존재하지 않지만 프로토타입을 기반으로 클래스와 비슷하게 동작하게끔 하는 다양한 기법들이 도입돼 왔음

1) 클래스와 인스턴스의 개념이해

  • 클래스
    • 어떤 사물의 공통 속성을 모아 정의한 추상적인 개념
    • 상위클래스(superclass)
      • ex) 음식
    • 하위클래스(subclass): 상위클래스의 조건을 충족하면서 더욱 구체적인 조건이 추가된 것
      • ex) 과일
  • 인스턴스
    • 클래스의 속성을 지니는 구체적인 사례

2) 자바스크립트의 클래스

  • 프로토타입 메서드(prototype method)
    • 클래스의 prototype 내부에 정의된 메서드
    • 인스턴스가 마치 자신의 것처럼 호출할 수 있음
  • 스태틱 메서드(static method)
    • 클래스(생성자 함수)에 직접 정의한 메서드
    • 인스턴스가 직접 호출할 수 없고 클래스(생성자 함수)에 의해서만 호출할 수 있음
// 생성자
var Rectangle = fucntion (width, height) {
	this.width = width;
    this.height = height;

};

// 프로토타입 메서드
Rectangle.prototype.getArea = function () {
	return this.width * this.height;
};

// 스태틱 메서드
Rectangle.isRectangle = function (instance) {
	return instance instanceof Rectangle && instance.width > 0 && instance.height > 0;
};

var rect1 = new Rectangle(3,4)
cosnole.log(rect1.getArea()); // 12
console.log(rect1.isRectangle(rect1)) // Error
console.log(Rectangle.isRectangle(rect1)) // true

3) 클래스 상속

  • ES5까지의 자바스크립트 커뮤니티에서는 클래스 상속을 다른 객체지향 언어에 익숙한 개발자들에게 최대한 친숙한 형태로 흉내내는 것이 주요한 관심사
  • ES5에서는 기본적으로 자바스크립트에서 클래스 상속을 구현했다는 것은 프로토타입 체이닝을 잘 연결한 것

클래스 상속을 흉내 내기 위한 3가지 방법

  • 3가지 방법 모두 Constructor 프로퍼티가 원래의 생성자 함수를 바라보도록 조정해야됨
  1. SubClass.prototype에 SuperClass의 인스턴스를 할당한 다음 프로퍼티 모두 삭제하기
  2. 빈 함수(Bridge)를 활용하기
  3. Object.create를 이용하기

4) ES6의 클래스 및 클래스 상속

ES5와 ES6의 클래스 문법 비교

var ES5 = function (name) {
	this.name = name;
};
ES5.staticMehod = function () {
	return this.name + 'staticMethod';
};
ES5.prototype.method = function () {
	return this.name + 'method';
};
var es5Instance = new ES5('es5'));
console.log(ES5.staticMethod()); // es5 staticMethod
console.log(es5Instace.method()); // es5 method

-----------------------------------------------------------------------------------

var ES6 = class {
	constructor (name) {
    	this.name = name;
    }
    static staticMethod() {
    	return this.name + 'staticMethod';
    }
    method () {
    	return this.name + 'method';;
    }
};
var es6Instance = new ES6('es6');
console.log(ES6.staticMethod()); // es6 staticMethod
console.log(es6Instace.method()); // es6 method

기본적으로 생성자 함수와 클래스 문법 모두 스태틱 메서드, 프로토타입 메서드에 동일하게 작동하고 있음을 확인할 수 있다.

4-2) 클래스 상속

var Rectangle = class {
	constructor (width, height) {
    	this.width = width;
        this.height = height;
    }
    getArea() {
    	return this.width * this.height;;
    }
};
var Square = class extends Rectangle {
	constructor (width) {
    	super(width, width);
    }
    getArea() {
    	console.log('size is : ', super.getArea());
    }
};

10번째 줄: Squre를 Rectangle 클래스를 상속받은 SubClass로 만들기 위해 class 명령어 뒤에 단순히 extends Rectangle 이라는 내용을 추가

12번째 줄: constructor 내부에서는 super라는 키워드를 함수처럼 사용해서 SuperClass의 constructor를 실행

15번째 줄: constructor 메서드를 제외한 다른 메서드에서는 super 키워드를 마치 객체처럼 사용할 수 있는데, 이때 객체는 SuperClass.prototype을 바라보지만 호출한 메서드의 this는 super가 아니라 원래의 this를 그대로 따름

 

총평

이 책을 읽고 나니 콜백 함수, this, 클로저와 같은 혼란스러운 개념들이 명확해졌고, 추가적으로 내 지식을 정리하는 데에도 큰 도움을 받았다. 그러나 개념을 완벽히 이해하지 못할 때는 추가적인 검색이 필요할 수 있다. 나는 이 책에서 나오는 개념이 잘 이해가 가지 않을 때는 MDN문서를 참고하여 개념을 보충하기도 했다.

 

경력이 쌓이면서 다시 읽어보니, 책이 이해하기 쉬워진 느낌이었다. 시간이 흐름에 따라 내가 어떻게 성장해나가고 있는지를 실감하게 된 것이다. 또한, 이 책은 개발자로서 필수적인 핵심 개념을 정확히 이해하고자 하는 욕구를 충족시켜주었다.

 

이 책은 초보자보다는 어느 정도의 개발 경험이 있는 독자들에게 더 큰 도움이 될 것으로 보인다. 즉, 전반적으로 자바스크립트에 대해 심화 학습을 원하는 독자들에게 좋은 선택이 될 것 같다. 책의 예제를 통해 자바스크립트에 대한 깊은 이해를 얻을 수 있으며, 실무에서 헷갈렸던 개념을 보충하고 활용하는데 도움이 될 것 같다.