JavaScript Fundamental(ES5)

khxxjxx·2022년 4월 14일
0

1. Variables

변수란?

  • 값이 아니라 값을 위한 컨테이너이며 특별한 점은 포함된 값이 변경될 수 있다는 것입니다.
  • 변수엔 숫자 또는 문자열 뿐만 아니라 무엇이든 포함할 수 있는데 그게 어느정도이냐면 함수까지 포함될 수 있습니다.(일급 객체)

변수의 선언

  • 변수를 사용하기 위해 var 키워드와 이름을 이용해서 변수를 선언할 수 있는데 이름을 만들때 몇가지 규칙이 있습니다.
  • 이름의 첫글자는 문자나 밑줄, 또는 달러기호로 시작해야하고 lower camel case를 사용합니다. 또한, 변수는 대소문자를 구분합니다.
  • var 키워드로 선언한 전역 변수는 window 객체에 새로운 속성을 추가합니다.
var betterWaySystems;
var heejin;

    ㄴ 위 변수들은 값을 포함하고 있지 않은 빈 컨테이너여서 출력해볼 경우 undefined를 반환합니다.

변수의 초기화

  • 변수를 선언한 후에는 값으로 초기화 할 수 있습니다. 변수 이름 다음에 등호(=)와 그 뒤에 부여 할 값을 입력하면 됩니다.
  • 변수를 선언함과 동시에 초기화하는 것도 가능하며 변수에 값이 할당되고 다른 값을 지정하여 변수 값을 업데이트 할 수 있습니다.
heejin = 26;  // 초기화
var address = "경기도";  // 선언 + 초기화
address = "경기 시흥";  // 재할당

초기화가 이루어져야 식별자(변수명)에 메모리를 할당하기 때문에 값의 참조 및 할당이 가능하기 위해선 초기화가 이루어져야 합니다. var의 경우 선언과 동시에 undefined로 초기화 됩니다.

Primitive Type(원시)

  • 객체가 아니면서 메서드도 가지지 않은 데이터로 ES5에서는 string, number, boolean, undefined, null 5종류가 존재합니다.
  • 모든 원시 값은 불변하여 변형할 수 없습니다. 변수는 새로운 값을 다시 할당할 수 있지만, 이미 생성한 원시 값은 객체, 배열, 함수와는 달리 변형할 수 없습니다.
  • 원시 타입은 메모리에 값이 그대로 저장되기 때문에 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달됩니다. 따라서 원본의 값을 변경하거나 복사본의 값을 변경해도 서로 영향을 끼치지 않습니다.
// 문자열
var string = "abc";
console.log(string[0]);  // a  =>  자바스크립트의 string은 유사배열로 index로 접근이 가능
string[0] = "A";
console.log(string);  // abc  =>  배열처럼 동작하지만 불변하기때문에 변형 x

// 배열
var array = ["a", "b", "c"];
console.log(array[0]);  // a
array[0] = "A";
console.log(array);  // Abc

Reference Type(참조)

  • 객체, 배열, 함수 등과 같은 Object 형식의 타입이며 메모리에 값을 주소로 저장하고, 출력시 메모리 주소와 일치하는 값을 출력합니다.
  • 참조 값을 갖는 변수는 다른 변수에 할당하면 원본의 참조값이 복사되어 전달됩니다. 따라서 원본데이터가 변경되거나 복사본의 데이터가 변경되면 같이 변경되게 됩니다. 참조타입의 이러한 특성때문에 얕은복사와 깊은 복사가 있습니다.

자바스크립트는 값을 매개변수로 전달할 때 인수값을 복사해 로컬 복사본을 생성하고 이러한 복사본은 함수의 스코프 내에서만 존재합니다. 그렇기 때문에 Primitive Type의 경우 원본에 전혀 영향을 주지 않지만 Reference Type의 경우 영향을 받습니다.

// 예시
var init = 1;

function AddTwo(num) {
  num += 2;
}

addTwo(init);
console.log(init);  // 1

Casting(형변환)

  • 자바스크립트는 타입이 매우 유연한 언어이기 때문에 대부분 상황에서는 자바스크립트가 자동으로 형변환을 하는데 이것을 암시적 형변환 이라고 하고 개발자의 의도에 따라 형변환을 하는 것을 명시적 형변환 이라고 합니다.
  • 더하기 연산자의 경우 다른 연산자와 다르게 숫자보다 문자열이 우선시 되기 때문에, 숫자형이 문자형을 만나면 문자형으로 변환되어 연산됩니다.

2. Execution Context

  • 자바스크립트 엔진은 Execution Context를 객체로 관리하며 코드를 Execution Context 내에서 실행합니다.
  • 실행 컨텍스트는 scope, hoisting, this, function, closure 등의 동작 원리를 담고 있는 자바스크립트의 핵심원리입니다. 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이라고 정의할 수 있으며 간단히 말해 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
  • 여기서 실행 가능한 코드란 전역코드, eval코드, 함수코드 3가지가 있는데 eval은 사용하면 안되므로 일반적으로 실행 가능한 코드는 전역코드와 함수 내 코드입니다. *ES6에서는 블록에 의해서도 실행 가능
  • 자바스크립트 엔진은 코드를 실행하기 위해서 변수(전역변수, 지역변수, 매개변수, 객체의 프로퍼티)와 함수, scope, this를 알고 있어야 하고 실행에 필요한 정보를 형상화하고 구분하기 위해 자바스크립트 엔진은 실행 컨텍스트를 물리적 객체의 형태로 관리합니다.

  • 자바스크립트는 실행(브라우저가 스크립트를 로딩해서 실행)될 때 아무런 코드가 없다고 해도 전역 컨텍스트가 만들어지고 페이지가 종료될때 까지 유지됩니다.
  • 전역에서 시작해 위에서부터 코드를 실행하면서 변수에 값을 할당하는 구문을 만나면 실행 컨텍스트에 값을 저장하고 함수를 호출하는 구문을 만나면 함수 컨텍스트가 생성되어 스택에 쌓이게 됩니다. 생성된 함수 컨텍스트는 함수 실행이 끝나면 파기 됩니다.
var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();

    ㄴ 스택의 각 항목을 스택 프레임이라고 합니다.

Variable Object

  • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 컨텍스트를 구성하는 함수에 지정된 매개변수, 선언한 함수(함수 표현식x)가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 해당합니다. => 호이스팅
  • 전역 컨텍스트의 경우 Variable Object는 유일한 최상위에 위치하고 모든 전역변수, 전역함수 등을 포함하는 전역 객체(Global Object / GO)를 가르킵니다.
  • 함수 컨텍스트의 경우 Variable Object는 활성 객체(Activation Object / AO)를 가리키며 매개변수와 인수들의 정보를 배열의 형태로 담고 있는 arguments object가 추가됩니다.

var name = "Heejin";

function sayHello(value) {
  console.log(value)
}

function sayHi() {
  console.log("hi")
  sayHello("hello")
};

sayHi()

ㄴ 위 코드 실행시 생기는 실행컨텍스트를 객체형식으로 표현 ↓

"전역 컨텍스트" : {
  전역객체 : {
    arguments : null,
    variable : [{sayHi : Function},
                {name : "Heejin"}],
  },
  scopeChain : ["전역 변수객체"],
  this : window
}

"함수 컨텍스트" : {
  활성객체 : {
    arguments : [{value : "hello"}],
    variable : null,
  },
  scopeChain : ["sayHello 활성 객체",
                "전역 객체"],
  this : window 
}

Scope Chain

  • 리스트로서 해당 전역 또는 함수가 참조할 수 있는 전역 객체와 중첩된 함수의 활성객체가 차례로 저장되어있습니다.
  • 현재 실행 컨텍스트의 활성 객체를 시작으로 순차적으로 상위 컨텍스트의 활성 객체를 가리키며 마지막 리스트는 전역 객체를 가리킵니다.
  • 자바스크립트 엔진이 식별자에 접근하려면 스코프 체인의 안에서부터 바깥쪽으로 검색을 하며 찾습니다. 이때, 해당 식별자를 찾으면 더이상 스코프 체인 검색을 더 진행하지 않습니다. 그렇기 때문에 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 됩니다.

스코프 체인은 객체의 프로퍼티가 아닌 변수를 검색하는 매커니즘입니다.(전역객체 제외)
변수가 아닌 객체의 프로퍼티를 검색하는 매커니즘은 프로토타입 체인입니다.

This

  • 자바스크립트 함수는 호출될 때 매개변수로 전달되는 인자값 이외에 arguments 객체와 this를 암묵적으로 전달 받습니다.
  • 여기서 this는 함수의 호출 환경에 따라 객체가 동적으로 결정되는데 함수를 호출하는 방식엔 단순 함수호출, 메서드 호출, 생성자 호출, 간접 호출(call, apply, bind) 이 외 콜백함수(특정 동작 이후 불려지는 함수) 호출이 있습니다.

1) 단순 호출

  • 전역 함수는 물론이고 내부함수의 경우에도 어디에서 선언되었든 관계없이 this는 외부함수가 아닌 전역객체에 바인딩됩니다
  • 전역객체는 일반적으로 browser-side에서는 window, Server-side(node.js)에서는 global 객체를 의미하지만 엄격 모드에선 this의 값이 null 또는 undefined 일 경우 전역 객체로 변환하지 않고 그대로 반환합니다.

2) 메서드 호출

  • 해당 메서드를 호출한 객체에 바인딩(메서드명 바로 앞의 객체)합니다.
  • 함수와 메서드는 비슷하지만 차이점이 있다면 함수는 독립적이고 메서드는 자신을 호출한 객체에 관한 동작을 수행합니다.

3) 생성자 호출

  • 생성자 함수 호출일 경우 생성자 함수의 코드가 실행되기 전 새로 생성한 빈 객체에 바인딩 됩니다. 따라서 this를 사용하여 동적으로 프로퍼티나 메소드를 생성할 수 있습니다.
  • 생성된 객체에 return이 없거나 this를 반환하는 경우 this에 바인딩된 새로 생성된 객체가 반환되는데 return에 this가 아닌 다른 객체를 반환하는 경우 해당 객체가 반환됩니다. 이렇게 되면 생성자 함수로서의 역할을 수행하지 못하므로 생성자 함수는 반환문을 명시적으로 사용하지 않습니다.
function Person(name) {
  this.name = name;
    return {
        age: 26
    }
}

var me = new Person('Heejin');
console.log(me.name);  // undefined

4) call, apply, bind

  • 첫번째 인자로 전달하는 객체에 this를 바인딩 할 수 있습니다.
  • call과 apply의 경우 함수를 실행하고 bind의 경우 함수를 실행하지 않고 새로운 함수를 반환합니다.
  • 두번째 인자로 함수의 매개변수값을 넘겨줄 수 있는데 apply의 경우 배열 형태로 넘겨야 합니다. 이때, 배열은 배열 자체를 넘기는 것이 아니라 배열의 요소를 전달합니다.
function func(age, address) {
  console.log(this.name, age, address);  // 희진 26 경기
}

var person = {
  name: '희진',
};

func.apply(person, [26, "경기"]);

3. Call Stack

  • 자바스크립트 엔진은 Memory Heap과 Call Stack으로 구성되어 있습니다.

  • Memory Heap에는 메모리 할당이 일어나는 곳(우리가 선언한 변수, 함수 등)이고 Call Stack은 위에서 설명한 실행 컨텍스트가 stack형태로 쌓이는 곳입니다.
  • 자바스크립트는 단일 스레드(Sigle thread) 프로그래밍 언어이므로 단일 호출 스택이 있습니다. 이 뜻은 한번에 하나의 일만 처리할 수 있다는 뜻입니다.
  • 단일 호출 스택의 문제점으로 하나의 함수 처리가 엄청 느려서 다른 함수 실행에 지장을 줄 수 있고, 브라우저가 호출 스택에서 많은 작업을 처리하느라 오랜시간 응답을 멈추고 웹페이지를 종료할지 여부를 묻는 오류 메시지를 표시하기도 합니다.
  • 이 문제를 해결하고자 사용하는 것이 비동기 콜백입니다. 비동기 콜백은 즉시가 아닌, 특수한 시점에 실행되므로 동기 함수와는 다르게 스택 안에 바로 Push 될 필요가 없습니다. 자세한 비동기에 대해선 아래에서 다루겠습니다.

Stack Overflow

  • 재귀를 호출할 때 종료 조건없이 자신을 계속해서 호출하게 되면 계속해서 호출 스택에도 쌓이게 됩니다.
  • 그러다 어떠한 시점에 함수 호출 수가 스택의 실제 크기를 초과하게 되면서 브라우저는 오류를 발생시키고 함수를 종료시키게 되는데 이것을 스택오버 플로우라고 합니다.
function foo() {
  foo();
}
foo();


4. Scope & Closure

Scope

  • 변수는 전역변수 또는 코드 블록(if, for, while, try/catch 등)이나 함수 내에서 선언하는 지역변수로 나눌 수 있고 코드 블록이나 함수는 중첩될 수 있습니다.
  • 식별자는 자신이 어디에서 선언되었는지에 의해 자신이 유효한(다른 코드가 자신을 참조할 수 있는) 범위를 갖습니다.
  • 전역에서 선언된 변수는 어디에든 참조할 수 있지만 함수 내에서 선언된 변수는 그 함수 내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없습니다. 이러한 규칙을 스코프라고 합니다.
  • 만약 스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없습니다. 만약 전역변수와 지역변수의 이름이 중복 선언될 경우 전역에서는 전역 변수만 참조 가능하고 함수 내 지역에서는 전역과 지역변수 모두 참조 가능하나 지역변수를 우선하여 참조(스코프 체인 탐색에 의해)합니다.

1) Function Scope

  • 자바스크립트는 대부분의 다른언어와 다르게 기본적으로 함수 레벨 스코프를 따릅니다.
  • 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다는 것입니다. *유효하지 않다 = 참조할 수 없다.

2) Block Scope

  • 이 개념은 ES6부터 적용되므로 추후에 다뤄보겠습니다.

3) Lexical scope & Dynamic scope

  • 렉시컬 스코프는 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것이고 동적 스코프는 함수를 어디서 호출하였는지에 따라 상위 스코프를 결정합니다.
  • 자바스크립트를 비롯한 대부분의 프로그래밍 언어에서 렉시컬 스코프를 따릅니다.
function func1() {
  console.log(name); // func2 함수 안에서 호출되었지만 값 참조 불가
}

function func2() {
  var name = "heejin";
  func1()
}

func2()

Closure

  • 클로저는 외부 함수에서 선언한 변수를 참조하는 내부 함수에서만 발생하는 현상으로 내부 함수가 정의될 때 외부 함수의 환경을 기억하고 있는 내부 함수를 말합니다.
  • 위에서 설명한 실행 컨텍스트를 보면 함수를 호출할때 함수 컨텍스트가 생성되고 함수가 종료되면 파기된다고 되어있습니다. 함수 컨텍스트가 파기될 때 저장된 식별자들에 대한 참조도 지우게 되고 메모리에 저장되어있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터의 수집 대상이 됩니다.
  • 하지만 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않기 때문에 외부함수가 종료되더라도 내부함수가 외부함수의 변수를 참조할 수 있습니다.
  • 전역 변수를 대체하여 클로저를 사용할 수 있어 전역 변수의 남용을 막을 수 있고 변수 값을 은닉(캡슐화)하는 용도로도 사용할 수 있지만 메모리 소모가 발생하므로 메모리 관리법을 잘 파악해서 적용하면 좋습니다.

5. Asynchronous

  • 위에서 Call Stack을 다루면서 Call Stack의 문제점에 대한 해결 방법으로 비동기가 있었습니다.
  • 자바스크립트는 동기적 언어여서 코드를 순차적으로 실행하고 해당 코드가 종료되어야 다음 라인의 코드를 실행하는데 비동기의 경우 해당 코드가 종료되기 전에 다음 라인의 코드를 실행합니다.
  • 하지만 자바스크립트는 싱글 스레드이기 때문에 비동기를 처리하기 위해선 자바스크립트 엔진을 구동하는 런타임 환경(브라우저나 Node.js)에서 처리합니다.
  • 여기서 런타임이란 프로그래밍 언어가 구동되는 환경을 말합니다.
  • 이렇게 런타임 환경과 자바스크립트 엔진이 만나면 아래 사진과 같이 작동합니다.

Web APIs

  • Call Stack에서 실행된 비동기 함수는 Web API를 호출해서 비동기 작업에 대한 정보와 콜백 함수를 Web API를 통해 브라우저에게 넘깁니다.
  • 작업을 Web API에게 넘기면 자바스크립트 엔진은 그 작업을 CallStack에서 제거하기 때문에 비동기 작업을 기다리는 동안 메인 스레드는 다른 작업을 처리할 수 있습니다.
  • 브라우저는 이러한 요청들을 Javascript Engine의 싱글 스레드 외에 별도의 스레드에 위임하게 되고 그 쓰레드는 해당 요청이 완료되면 전달받았던 콜백 함수를 Callback Queue에 집어넣습니다.

Callback Queue

  • Callback Queue 공간에 있는 것들은 Call Stack 내부가 비어 있을 때 Call Stack으로 이동을 하게 됩니다. 하지만 Callback Queue에서 Call Stack으로 이동할 때 우선순위가 존재합니다.

1) Macrotask Queue(Event Queue)

  • setTimeout, setInterval, XMLHttpRequest 등과 같은 비동기 함수의 콜백 함수, 이벤트 핸들러가 일시적으로 보관되는 영역입니다.
    • setTimeout
      • 비동기 함수로서 함수 스택의 다른 함수 호출을 막지 않습니다. 지정한 시간이 되면 전달받은 함수를 실행합니다. 딜레이 시간을 0초로 지정해도 콜 스택에서 나와 콜백 큐로 이동하게되고 콜스택이 비어있을때까지 대기하기 때문에 코드가 순차적으로 실행되지 않습니다.
    • XMLHttpRequest
      • 웹 개발 방법 중 하나인 AJAX를 이야기할 때 사용하는 통신객체로 서버와 데이터를 주고 받을 수 있습니다. open() 함수를 통해 요청을 초기화하고 send() 함수를 통해 서버에 요청을 보냅니다. onreadystatechange 이벤트를 이용해 요청에 대한 응답 결과를 처리합니다.

2) Microtask Queue(Job Queue)

  • Promise 등이 보관되는 영역입니다.
  • Promise는 비동기 작업을 표현하는 자바스크립트 객체로 비동기 작업의 진행, 성공, 실패 상태를 표현합니다.

3) Animation Frames

  • requestAnimationFrame이 보관되는 영역입니다.

위 세가지를 우선순위가 높은 순으로 나타내면 아래와 같습니다.
Microtask Queue -> Animation Frames -> Macrotask Queue

Event Loop

  • Callback Queue와 Call Stack을 지켜보다가 스택이 비는 시점에 콜백 큐의 첫번째 콜백을 콜 스택으로 push합니다. 이러한 반복적인 행동을 tick이라고 부릅니다.

6. JavaScript OOP(객체지향 프로그래밍, Object-Oriented Programming)

  • 최근의 프로그래밍 패러다임을 보면 가장 크게는 선언형과 명령형으로 나눌 수 있고, 발전하게 된 순서는 절차적 -> 객체지향 -> 함수형으로 단점들을 극복을 위해 나왔다고 합니다.
    • 명령형 프로그래밍 : 어떻게(How)을 할 건지 설명하는 방식
      • 절차지향 프로그래밍 : 수행되어야 할 순차적인 처리 과정을 포함하는 방식
      • 객체지향 프로그래밍 : 객체들의 집합으로 프로그램의 상호작용 표현
    • 선언형 프로그래밍 : 무엇(What)을 할 건지 설명하는 방식
      • 함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방식
  • 자바스크립트는 멀티-패러다임 언어로 명령형, 함수형, 프로토타입 기반 객체지향 언어입니다.
  • 객체지향 프로그래밍에서 객체란 변수 따로, 함수 따로의 분산적이고 통일성 없는 실행절차 과정을 추상화 과정으로 통합한 것을 말합니다. *추상화 : 과일, 음식과 같이 어떤 사물들의 공통 속성을 모아 정의한것
  • 자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓기 때문에 함수에 new 연사자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작합니다.
  • 그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인 __proto__(생략가능)가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype 프로퍼티를 참조합니다. 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 됩니다.
  • 자바스크립트는 이미 생성된 인스턴스의 자료구조와 기능을 동적으로 변경할 수 있다는 특징이 있고 객체 지향의 상속, 캡슐화(정보 은닉) 등의 개념은 프로토타입 체인과 클로저 등으로 구현할 수 있습니다.

Prototype

  • 프로토타입 기반 객체지향 프로그래밍 언어는 클래스 없이도 객체를 생성할 수 있습니다. (ES6에 class가 추가 되었고 class에 대한건 추후에 다뤄보겠습니다.)
  • 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있고 부모 객체의 프로퍼티 또는 메서드를 상속받아 사용할 수 있습니다. 이러한 부모 객체를 Prototype(또는 Prototype 객체)이라고 합니다.

1) [[Prototype]]

  • 모든 객체는 자신의 프로토타입 객체를 가리키는 [[Prototype]] 이라는 내부 슬롯(자바스크립트 엔진의 내부 로직)을 가지며, 상속을 위해 사용됩니다.
  • 자바스크립트에선 함수도 객체이므로 내부 슬롯을 가지게 되는데 함수 객체는 일반 객체와 달리 prototype 프로퍼티도 소유하게 되므로 [[Prototype]]과 prototype 프로퍼티 두개를 가지게 됩니다.

2) __Proto__

  • [[Prototype]] 내부 슬롯에는 직접 접근이 불가능(프로토타입 체인의 단방향을 지키기 위해)하여 __proto__ 프로퍼티로만 접근할 수 있습니다.
  • ES6에서는 __proto__ 를 표준으로 채택했지만 브라우저에서의 호환성을 고려한 지원일 뿐 권장되는 방식은 아니므로 Object.getPrototypeOf()의 사용을 권장합니다.

3) [[Prototype]] vs prototype 프로퍼티

  • prototype 프로퍼티와 [[Prototype]]은 모두 프로토타입 객체를 가리키지만 관점의 차이가 있습니다.
  • [[Prototype]]은 객체의 입장에서 자신의 부모 역할을 하는 객체를 가리키고 prototype 프로퍼티는 함수 객체가 생성자로 사용될 때 이 함수를 통해 생성된 객체의 부모 역할을 하는 객체를 가리킵니다.

Prototype chain

  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [[Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례로 검색합니다.
  • __proto__ 안에 다시 __proto__를 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있습니다. 이때 접근 방식은 스코프 체인과 마찬가지로 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며 원하는 값을 찾으면 검색을 중단합니다.
  • 이런 프로토타입을 이용하여 생성자 함수 내부의 메소드를 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체로 이동시키면 생성자 함수에 의해 생성된 모든 인스턴스는 프로토타입 체인을 통해 프로토타입 객체의 메소드를 참조할 수 있습니다.
function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function () {
  return this.name + ' hi!';
};

var person = new Person('heejin');

console.log(person); // {name: 'heejin'}
console.log(person.sayHi()); // heejin hi!
  • Object.prototype 객체를 프로토타입 체인의 종점(End of prototype chain)이라 합니다.

Subclassing

  • 자바스크립트는 기본적으로 프로토타입을 통해 상속을 구현하는데 이것은 프로토타입을 통해 객체가 다른 객체로 직접 상속된다는 의미입니다.
  • 자바스크립트에서 상속을 구현했다는 것은 결국 프로토타입 체이닝을 잘 연결한 것으로 이해하면 됩니다.
  • 간단히 자식 생성자 함수의 prototype 프로퍼티를 부모 생성자 함수의 인스턴스로 교체하여 상속을 구현할 수 있습니다.
// 부모 생성자 함수
function Shape(width, height) {
  // Construtor
  this.width = width;
  this.height = height;
}

// method
Shape.prototype.getArea = function () {
  return this.width * this.height;
};

// 자식 생성자 함수
function Triangle(width, height) {
  Shape.call(this, width, height);
}

// 자식 생성자 함수의 프로토타입 객체를 부모 생성자 함수의 인스턴스로 교체
Triangle.prototype = new Shape();

// 메소드 오버라이드
Triangle.prototype.getArea = function () {
  return (this.width * this.height) / 2;
};

var triangle = new Triangle(20, 20);

console.log(triangle.getArea());  // 200
  • 하지만 이 방법은 Triangle의 prototype 프러퍼티의 값이 Shape가 되기때문에 Triangle.prototype을 출력해보면 width와 height의 값이 undefined로 되어있는것을 볼 수 있습니다. 불필요한 인스턴스 프로퍼티가 남아있게 됩니다.
  • 또한, 객체를 인스턴스로 교체하는 과정에서 constructor의 연결이 깨지게 되기 때문에 triangle.constructor을 출력해보면 Triangle 생성자 함수가 아닌 Shape 생성자 함수를 가리킵니다.

1) 인스턴스에 영향을 주는 클래스값 없애기

  • delete prototype properties
    • 일일이 지워야해서 프로퍼티가 많다면 반복 작업이 됩니다.
delete Triangle.prototype.width;
delete Triangle.prototype.height;
Object.freeze(Triangle.prototype);
  • Bridge function
    • 아무런 프로퍼티를 생성하지 않는 빈 생성자 함수를 하나 더 만들어서 다리 역할을 부여합니다.
function Bridge() {}
Bridge.prototype = Shape.prototype;
Triangle.prototype = new Bridge();
Object.freeze(Triangle.prototype);
  • Object.create
    • Triangle의 prototype의 __proto__가 Shape의 prototype을 바라보되, Shape의 인스턴스가 되지는 않으므로 앞서 소개한 두 방법보다 간단하면서 안전합니다.
Triangle.prototype = new Shape();Triangle.prototype = Object.create(Shape.prototype);
Object.freeze(Triangle.prototype);

2) constructor 복구하기

  • 위와 같은 방법으로 기본적인 상속에는 성공했지만 Triangle의 인스턴스의 constructor는 여전히 Shape를 가리키므로 constructor를 변경해줘야합니다.
Triangle.prototype.constructor = Triangle
profile
코린이

0개의 댓글