[책 요약] 코어 자바스크립트

Junghyun Park·2021년 4월 14일
0

목차 별 내용 정리


데이터 타입

1. 종류

(1) primitive(원시형) : number, string, boolean, null, undefined ...
(2) reference(참조형) : object, Array, Function, Date, RegExp...

원시형은 값이 담긴 주소값을 바로 복제 & 불변성(immutability)
참조형은 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주소값을 복제

2. 데이터 타입에 대한 배경지식

  • 0이나 1인 비트(bit)는 하나의 메모리 조각
  • 메모리의 위치(주소)를 표현하기 위해서는 일정 수준의 비트 묶음(=바이트)이 필요
    (묶음 단위가 너무 작으면 표현할 수 있는 값이 적고, 너무 크면 낭비되는 비트가 발생)
  • 1 바이트(byte) = 8 비트(bit)
  • 메모리 용량이 제한적이던 예전에는 타입 별로 최소한의 용량으로 할당해 놓고, 초과하게 되는 경우 형변환 등을 해야 하는 불편 있었지만, 자바스크립트는 메모리 용량이 충분히 많아진 환경에서 탄생하여 이러한 제약에서 자유로움
  • 모든 데이터는 바이트 단위의 식별자(= 메모리 주솟값)을 통해 서로 구분하고 연결할 수 있음
  • 변수(variable)는 '변할 수 있는 데이터 자체'이고, 식별자(identifier)는 '어떤 데이터를 식별하는데 사용하는 이름(= 변수명)' 임

3. 변수 선언과 데이터 할당

  • 변수 영역에 값을 직접 입력하지 않고, 데이터 영역이라는 한 단계를 더 거치는 이유
    : 데이터 변환을 자유롭게 할 수 있도록 함과 동시에, 메모리를 더욱 효율적으로 관리하기 위함
    (이미 입력된 값이 변경되어, 차지하는 공간이 늘어나는 경우, 이미 저장된 데이터의 위치를 전부 이동시키고, 이동시킨 주소를 각 식별자에 다시 연결해야 하는 작업에 불필요한 연산 발생)
    : 따라서, 입력한 변수가 변경되는 경우에는 기존의 동일 공간에서 할당하지 않고, 새로운 공간에 저장하고, 그 새로운 주소를 기존 식별자(변수명)에 연결

4. 기본형(원시형)데이터와 참조형 데이터

  • 기본형은 불변값, 참조형은 가변값
  • 참조형의 기본형과의 차이점은 객체의 변수(프로퍼티) 영역이 별도로 존재하는점
  • 참조 카운트가 0인 메모리 주소는 garbage collector가 수거하여, 새로운 값을 할당할 수 있는 빈공간이 됨

변수의 복사 비교

var a = 10;
var b = a;

var obj1 = {c:10, d:'ddd'};
var obj2 = obj1;

b = 15
obj2.c = 20

(1) 기본형 데이터 복사의 경우

  • 재할당 되는 변경된 값의 메모리 주소가 변경되어, 기존 변수(a)에 영향 X

(2) 참조형 데이터 복사의 경우

  • c의 값이 재할당 되더라도, 해당 객체(obj2) 내 값들이 저장된 주소들의 정보가 저장된 주소 값은 변경되지 않고 유지되므로, obj2.c의 값이 변경되면, obj1.c의 값도 함께 변경됨
  • 다만, 아래와 같이 obj2 객체 자체를 변경하는 경우에는 기본형과 같이 기존 객체에 영향을 주지 않고 복사됨
...위와 동일
b = 15;
obj2= {c:20, d:'ddd'}

5. 불변객체

  • 객체를 복사하면서도, 상호 간 영향을 주지 않아야 하는 경우 발생하고, 이때 불변객체의 필요성 발생

불변객체 만드는 법

  • 새로운 객체를 반환하는 함수(책에서는 changeName)을 만들면 서로 다른 객체 생성

    얕은 복사 VS 깊은 복사

  • 얕은 복사(shallow copy)
    : 중첩된 객체에서, 바로 아래 단계의 값만 복사
    : 얕은 복사하게 되면, 바로 아래 단계 이외에는 값들의 주소가 서로 공유 및 연결되어 있어서, 변경하면 상호 영향을 받게 됨
var copyObject = function(target){
	var result ={};
    for (var prop in target) {
    	result[prop] = target[prop];
    }
    return result;
  • 깊은 복사(deep copy)
    : 중첩된 객체에서 내부 모든 값들을 복사
    : 아래의 3번째 줄에서, target이 객체인 경우 내부 프로퍼티들은 순회하며 copyObjectDeep함수를 재귀적으로 호출하게 되므로 깊은 곳까지 복사 가능
var copyObjectDeep = functon(target){
	var result = {};
    if (typeof target === 'object' && target !== null) {
    	for (var prop in target) {
        	result[prop] = copyObjectDeep(target[prop]);
            }
        } else {
        	result = target;
        }
        return result;
 };

깊은 복사를 간단하게 할 수 있는 방법

객체를 JSON 문법으로 표현된 문자열로 전환 후, 다시 JSON객체로 변경

var copyObjectViaJSON = function (target) {
	return JSON.parse(JSON.stringify(target));
};

6. undefined와 null

  • 공통점 : 둘 모두 "값 없음"을 나타내기 위해 사용
  • 차이점 : undefined는 사용자가 명시적으로 지정할 수 있지만, 변수가 선언만 되고 값이 지정되지 않아도 자바스크립트 엔지이 자동으로 부여
  • 혼동을 피하는 법 (predictable)
    : 값이 없음을 나타내기 위해서는 "null"만 사용할 것
    : "undefined"는 자동으로 부여되는 경우로만 제한

실행 컨텍스트

1. 실행 컨텍스트(excution context)란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체
(javascript의 hoisting, this 속성과 관련)
흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행 하는 것

  • 실행 컨텍스트에 담기는 정보s
  1. VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부환경 정보, 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경사항은 반영X
  2. LexicalEnvironment: 처음에는 VariableEnvironmnet와 같지만 변경 사항이 실시간으로 반영됨
  3. ThisBinding: this 식별자가 바라봐야할 대상 객체

stack VS queue
stack은 출입구가 하나인 깊은 우물같은 데이터 구조
queue는 양쪽이 열려있는 파이프과 같은 데이터 구조

2. VariableEnvironment

  • LexicalEnvironment와 동일하지만, 최초 실행시의 스냅샷을 유지한다는 차이
  • environmentRecord와 outer-EnvironmentReference로 구성

3. LexicalEnvironment

(1) environmentRecord와 호이스팅

  • environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장 (컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수 자체, var로 선언된 변수의 식별자)
  • environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에 대한 관심이 있고, 어떤 값이 할당될 지에 는 관심이 없음 => 따라서, 변수를 호이스팅 할 때 변수명만 끌어올리고 할당 과정은 원래 그자리에 그대로 두게 됨 / 단, 함수선언은 함수 전체를 끌어올림

    함수 선언문과 함수 표현식(익명, 기명)
    : 함수를 새롭게 정의할때 쓰이는 방식 2가지
    : 호이스팅하는 경우, 함수 선언문은 전체를 호이스팅 하지만, 함수 표현식의 경우에는 식별자(변수명) 부분만 호이스팅하고 실제 함수는 그자리에 둠

    실무에서, 여러사람이 작업하는 긴 코드 내에서 동일한 변수명이 포함되는 경우, 호이스팅 때문에 계속 override되어 마지막에 정의한 함수가 실행되는 오류 발생가능성 있음 => 따라서, 함수 표현식으로 함수를 정의하면 각각의 위치에서 함수가 다르게 작동가능하므로 상대적으로 함수 표현식이 안전

// 함수 선언문
function a () {}
//함수 표현식(익명)
var b = function (){}
//함수 표현식(기명)
var c = function d(){}

(2) 스코프, 스코프 체인, outerEnvironmentReference

Scope란?
: 식별자에 대한 유효범위

Scope Chain이란?
: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해 나가는 것
: 이를 가능케 하는 것이 LexicalEnvironmet의 outerEvironmentReference임

  • outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조

4. this

  • 실행 컨텍스트의 thisBindingdpsms this로 지정된 객체가 저장됨

this

1. 상황에 따라 달라지는 this

  • 컨텍스트는 함수를 호출할 때 생성되므로, 함수를 호출하는 시점에서 this가 결정됨
  • 전역공간에서의 this = 전역 객체 (브라우저 환경의 window 객체, Node.js 환경의 global 객체)
  • 자바스크립트의 모든 변수는 정확히는 특정 객체의 프로퍼티로 동작(전역공간에서 a를 선언하면, 전역객체의 a라는 프로퍼티로 할당)

(1) 메서드로서 호출할 때 그 메서드 내부에서의 this

함수를 실행한는 2가지 방법

  • 함수로서 호출하는 경우 : 그 자체로 독립적 기능 수행
  • 메서드로 호출하는 경우 : 자신을 호출한 대상 객체에 대한 동작을 수행 (obj.~ 식으로 호출하는 경우)

(2) 함수로서 호출할때 그 함수 내부에서의 this

실행 컨택스트를 활성화 할 당시(함수가 호출되는 시점) this가 별도로 지정되지 않으면, this는 전역 객체를 가리킴

  • 메서드 내부 함수에서 this를 우회하는 방법

    메서드 내부 함수에서 this를 변수(ex> var self = this)에 담은 다음 호출하도록 하면, 동일하게 메서드 내부 함수를 함수로 호출하더라도 전역 객체가 아닌 해당 객체를 반환

  • this를 바인딩 하지 않는 함수 = "arrow function"
    : ES6에서 도입한 arrow function을 사용하면, 위의 우회법 필요 x
var obj = {
	outer: function() {
    	console.log(this); 			// {outer: f}
        var innerFunc = () =>{
        	console.log(this);		// {outer: f}
        };
        innerFunc()
     }
 };
 obj.outer();

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

  • setTimeout 함수, forEach와 같은 메서드는 내부 콜백함수를 호출시, 대상이 될 this를 지정하지 않아, 콜백함수 내부에서 this는 전역객체를 참조
  • but, addEventListener 함수 내부의 콜백함수를 호출할때는 자신의 this를 상속하게 되어 있음

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

new 명령어와 함께 함수를 호출하면 생성자로 동작하고, 리렇게 생성자로 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신을 가리킴

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

(1) call 메서드

Function.prototype.call(thisArg,[ arg1,[, arg2[,...]]])

  • 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}, 4, 5, 6);		// {x:1} 4, 5, 6

(2)apply 메서드

Function.prototype.apply(thisArg[, argsArray])

  • call 메서드와 큰차이 없지만, 두번째 인자를 배열로 받는 다는 차이

(3) call/apply 메서드의 활용

1. 유사배열 객체에 배열 메서드 사용

  • 원칙적으로 object에는 array method를 사용할 수 없지만, 유사배열객체 (키가 0또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0또는 양의 정수인 객체)는 call or apply method 이용하여 array method 적용가능
  • 문자열도 가능하지만, 문자열의 경우 length property가 읽기 전용이기 때문에 원본 문자열에 변경을 가하는 method는 에러
  • call/apply를 이용한 형변환은 this를 원하는 값으로 지정하여 호출한다는 원래 의도에서 벗어난 활용법임
    => 이에, ES6에서는 유사배열 객체 또는 순환가능 모든 종류의 데이터 타입을 배열로 전환하는 Array.from 메서드가 새로 도입됨
var obj ={
	0: 'a',
    1: 'b',
    2: 'c',
    length: 3
   };
   Array.prototype.push.call(obj, 'd') 
   console.log(obj)   		// {0: 'a', 1: 'b', 2:'c', 3:'d', length: 4}
   var arr = Array.prototype.slice.call(obj);
   console.log(arr);		// ['a', 'b', 'c', 'd']
   //얕은 복사 결과물로 array 반환

2. 생성자 내부에서 다른생성자를 호출
: 생성자 내부에 다른 생성자와 공통된 내용이 있는 경우, call or apply를 이용해 다른 생성자 호출하면 간단히 반복을 줄일 수 있음

3. 여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply
: (예시-아래) 최소 및 최대값 구하기
: 원래 Math.max(a, b, c, d..) 식으로 들어가야 함
: ES6의 spread syntax 활용하면 더 간단

var numbers = [10,20,3,16,45]
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);  		// 45, 3

(4) bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

  • call과 비슷하지만, 즉시 호출하지 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 method
  • 이때 반환되는 새로운 함수는 name property에 bound라는 접두어가 붙음 ('bound xxx'라면, xxx라는 원본 함수에 bind method 반환 함수라는 뜻)
  • ES6에서 도입된 Arrow function을 이용하면, 스코프체인상 가장 가까운 this에 접근하게 됨

(5) 별도의 인자로 this를 받는 경우(콜백함수 내에서의 this)

  • 대표적으로 forEach
  • 콜백함 수 내부에서 this 값을 원하는 대로 변경할 수 있음

콜백함수와 함께 thisArg를 인자로 받는 메서드

Array.prototype.forEach(callback[, thisArg])
map, filter, some, every, find, findIndex, flatMap, from..


콜백함수

콜백함수는 다른 코드의 인자로 넘겨주는 함수

  • '제어권'과 관련이 깊음

제어권

호출시점

  • 콜백함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가짐
    (ex> setInterval(cbFunc, 300)에서 호출주체 & 제어권을 가지는 것은 setInterval)

인자

  • 콜백함수의 제어권을 넘겨받은 코드는 콜백함수를 호출할 때 인자에 어떤값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가짐
    (ex> map method의 첫번째 인자는 순환하는 배열 내 현재 값, 두번째 인자는 index 값, 세번째 인자는 순환대상인 array 자체를 가리킴)

this

  • 콜백함수도 함수이기 때문에(not method), 기본적으로 this가 전역객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정(call/apply, bind..)한 경우에는 그 대상을 참조하게 됨
var obj = {
	vals: [1, 2, 3],
    logValues: function(v, i) {
    	console.log(this, v, i);
        }
   };
   
obj.logValues(1, 2);   		    // { vals: [1 ,2, 3], logValues: f} 1 2
[4, 5, 6].forEach(obj.logValues);   // Window {...} 4 0
				    // Window {...} 5 1
                                    // Window {...} 6 2

콜백함수의 내부의 this에 다른 값 바인딩하기

  • 별도의 인자로 this를 받는 함수의 경우에는 값을 넘겨주면 되지만, 그렇지 않은 경우에는 this의 제어권도 넘겨주게 되므로 사용자가 임의로 값을 바꿀 수 없으므로, 전통적으로는 this를 다른 변수에 담아 콜백함수로 활용할 함수에서는 this 대신 그 변수를 사용하게 하고, 이를 클로저로 만드는 방식을 사용했음
    (but, call or bind와 같은 함수 활용하여 가능)

콜백 지옥과 비동기 제어

  • 콜백 지옥을 해결하는 간단한 방법은 익명의 콜백함수를 모두 기명으로 전환 => 위에서 아래로 순차적 실행
  • ES6의 Promise, Generator, ES2017의 async/await로 해결가능

클로저

  • A closure is the combination of a function and the lexical environment(outerEnvironmentReference) within which that function was declared.
  • 어떤 함수에서 선언한 변수를 참조하는 내부 함수에서만 발생하는 현상 (= 외부함수의 LexicalEnvironment가 가비지 컬렉팅되지 않는 현상)
    : 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집대상에 포함시키지 않음
  • 클로저란, 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상

클로저와 메모리 관리

  • 관리방법은 클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생하므로, 그 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해주면 됨 ( = 참조카운트를 0으로 만들어 Garbage Collector가 수거하도록)

클로저 활용 사례

1. 콜백 함수 내부에서 외부데이터를 사용하고자 할 때

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

  • 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나.
  • 흔히 접근 권한에는 public(외부에서 접근 가능),private(내부에서만 사용하고 외부노출 X),protected의 세 종류가 있음
  • 외부에 제공하고자 하는 정보들을 모아서 return하고, 내부에서만 사용할 정보들은 return하지 않는 것으로 접근 권한 제어가 가능

3. 부분 적용 함수 (partially applied function)

부분 적용 함수란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행결과를 얻을 수 있도록 하는 함수

4. 커링 함수 (curring function)

커링 함수란 여러개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것


프로토타입

클래스 기반 언어에서 '상속'을 사용하듯, 프로토타입 기반 언어는 어떤 객체를 prototype으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻음

프로토타입의 개념이해

constructor, prototype, instance

  • 어떤 constructor 함수를 new 연산자와 함께 호출하면 -> Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성 -> 이때 instance에는 __proto__라는 프로퍼티가 자동으로 부여되는데, 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조
  • __proto__는 생략가능 (함수를 method로 호출하는 경우는 method 명 바로앞의 객체가 곧 this가 됨을 유의)

constructor 프로퍼티

  • 생성자 함수의 프로퍼티인 prototype 객체 내부와 이의 인스턴스 객체에는 constructor라는 프로퍼티가 있는데, 이는 인스턴스와의 관계에 있어서 필요한 정보임
  • 읽기 전용 속성이 부여된 예외적인 경우(기본 리터럴 변수 - number, string, boolean)을 제외하고는 값을 바꿀 수 있음

프로토 타입 체인

메서드 오버라이드

  • 인스턴스 객체가 prototype에서 가져오는 prototype과 동일한 이름의 프로퍼티나 메서드를 가지고 있으면, 인스턴스 객체 자신의 프로퍼티나 메서드로 오버라이드

프로토타입 체인

어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인(prototype chain)이라고 하고 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝(prototype chaining)이라고함

객체 전용 메서드의 예외사항

  • 어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에, Object.prototype이 언제나 프로토타입 체인의 최상단에 존재 -> 따라서, 객체에서만 사용할 메서드를 Object.prototype 내부에 정의하면 다른 데이터 타입도 해당 메서드를 사용할 수 있게 됨
  • 예외적으로 Object.create를 이용하면, Object.prototyp의 메서드에 접근할 수 없는 경우가 있음(Object.create(null)은 __proto__가 없는 객체를 생성

다중 프토토타입 체인


클래스

  • 자바스크립트는 기본적으로 클래스가 아닌 프로토타입 기반의 언어
  • but 개발자들의 니즈에 따라 ES6에서 클래스 문법이 추가(다만, 일정부분 프로토타입을 여전히 사용)

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

클래스는 하위로 갈수록 상위 클래서의 속성을 상속하면서 더 구체적인 요건이 추가 또는 변경됨
인스턴스는 어떤 클래스의 속성을 지니는 실존하는 개체를 의미

자바스크립트의 클래스

  • 생성자 함수(ex> Array)를 new 연산자와 함께 호출하면 인스턴스 생성
  • 이때 Array의 prototype 객체 내부 요소들이 인스턴스에 '상속'되는 것처럼 됨 (엄밀히 말하면, 프로토타입 체이닝에 의한 참조)
  • 일반적으로 인스턴스에 상속되는지(참조하는지) 여부에 따라 static memberinstance member로 나뉨
    (다만, 자바스크립트의 경우, 클래스 기반 언어와 달리 인스턴스에서도 직접 메서드 정의가 가능하기 때문에 'instance'라는 명칭은 프로토타입에 정의한 메서드인지, 인스턴스에 정의한 메서드인지 혼란을 야기하므로 prototype method라고 함)
  • 클래스는 일반적인 사용방식(인스턴스의 틀을 제공)으로 사용하는 경우는 추상적 개념이지만, 클래스 자체를 this로 접근해서 static method를 호출하는 경우는 그자체가 하나의 개체로 취급

클래스 상속

기본 구현

클래스가 구체적인 데이터를 지니지 않게 하는 방법

constructor 복구하기

상위 클래스에의 접근수단 제공

ES6의 클래스 및 클래스 상속

정리


profile
21c Carpenter

0개의 댓글