Data Types
기본형 데이터 타입과 참조형 데이터 타입
- 기본형 (Primitive Type) 데이터 타입
숫자, 문자열, 논리, null, undefined / symbol (ES6 에서 추가된 ) - 참조형 (Reference Type) 데이터 타입
대표적으로 객체 (Array, Function, RegExp (정규표현) / Set - WeakSet, Map - WeakMap (ES6 에서 추가된 것) )
- Stack Memory (스택 메모)
: 변수와 함께 기본형 데이터가 저장된다, 정적할당
: 변수 = 데이터 를 설정할 때 메모리에 올리게 되는데 순서는 다음과 같다 var a; // 변수 a 를 임의의 메모리에 올린다 (주소를 1 이라고 하자) // 주소 1 에 이름과 값을 저장할 수 있는데, 이름은 a 로 저장한다 a = 'abc'; // 값 'abc' 를 임의의 메모리에 올린다 (주소를 2라고 한다) // 주소 1 의 값을 주소 2로 저장한다 a = 'abcde'; // 값 'abcde' 를 임의의 메모리에 올린다 (주소 3 이라고 한다)\ // 주소 1의 값을 주소 3 으로 저장한다
기본형 데이터 타입은 가르키고 있는 주소를 직접 변경한다
- Heap Memory (힙 메모리)
: 참조형 데이터가 저장된다, 동적할당
객체를 복사하면 원본의 값이 변경된다 var obj = { a: 1, b:'bbb' };
- 5002 의 주소에서는 값을 하나씩만 가져올 수 있기 때문에 obj 배열의 객체를 모두 저장하지 못한다
7103, 7104 의 메모리에 프로퍼티를 할당하여 참조할 수 있도록 한다
값을 직접 저장하는 방식
: 데이터 할당 시에는 빠르고 비교에 비용이 많이 든다. 메모리 낭비가 심하다
값의 주소를 저장하는 방식
: 데이터 할당 시에는 느리나 비교에 비용이 들지 않는다.
= 같은 값이 오직 하나만 존재한다 즉, 불변값을 의미한다
메모리 낭비를 최소화할 수 있다
var a = 10;
var b = a;
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;
->
b = 15;
obj2.c = 20;
실행 컨텍스트 (Execution Context)
; Context : 어떠한 코드를 봤을 때 그 코드가 해당 자리에서 어떤 역할을 수행하는 지를 이해하기 위해서는 그 코드에 영향을 주는 주변 코드나 변수들을 파악해야 한다
=> 코드를 실행하는 데 필요한 배경이 되는 조건, 환경 = 실행 컨텍스트
- JS 에서 동일한 조건을 지닐 수 있는 조건 : 전역공간, 함수, eval, module -> 함수
전역공간 : 자바스크립트가 실행되는 순간에 바로 전역 컨텍스트가 생성되며 전체 코드가 끝날 때에 비로소 전역 컨텍스트가 종료된다 (거대한 함수*로 간주)
모듈 : 어딘가에서 import 되는 순간에 모듈 내부에 있는 컨텍스트가 생성되고 해당하는 모듈 코드가 전부 끝났을 때 컨텍스트가 종료된다 (하나의 *함수 로 간주)
= 전역 공간, 모듈 또는 함수로 묶인 내부에서는 결국 '같은 환경 안에 있다' 성립된다
1) if, for, switch, while 문 등은 별개의 실행 컨텍스트를 가지지 않는다
실행 컨텍스트 : 함수를 실행할 때 필요한 환경정보를 담은 객체 로 정의할 수 있다
코드로 알아보기
var a = 1;
function outer(){
console.log(a);
function inner(){
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
: 1 1 3 1
- 이런 식으로 가장 마지막에 들어온 것이 가장 먼저 빠지고 가장 먼저 들어온 게 마지막으로 빠지는 것을 스택 이라고 한다
흐름 : 전역 컨텍스트 -> outer() 호출 -> outer 컨텍스트 -> inner() 호출 -> inner 컨텍스
콜스택 (call stack)
: 코드 실행에 관여하는 스택을 일컫는다
현재 어떤 함수가 동작 중인지, 다음에 어떤 함수가 호출 될 예정인지 등을 제어하는 자료구조이다
실행 컨텍스트 내부
- VariableEnvironment : 식별자 정보 수집, 값의 변화를 반영하지 않는다
- LexicalEnvironment : 각 식별자의 '데이터' 추적, 값의 변화를 반영한다
둘의 차이는 값의 변화가 실시간 으로 반영되느냐 그렇지 않느냐이다
LexicalEnvironment
: 어휘적 환경, 사전적인 환경 = 어떤 실행 컨텍스트룰 구성하는 환경 정보들을 모아 사전처럼 구성한 객체이다
= 내부 식별자에 대한 정보와, 외부 정보 등이 담겨 있다
- environmentRecord : 실행 컨텍스트가 실행될 때 가장 먼저 진행하는 일이며, 현재 문맥의 식별자 정보를 저장한다
Hoisting(호이스팅)
: 현재 컨텍스트 식별자 정보들을 수집하여 environmentRecord 에 담는 과정을 말한다
실행 컨텍스트 맨 위로 식별자를 끌어올린다
`function a()` , `var b` , `var c` 순서대로 호이스팅 됐다
- 우측의 최종 결과가
environmentRecord
이다 = 호이스팅과 똑같은 개념
실행 컨텍스트가 가장 처음으로 하는 일
function a(){
return 'a';
}
var b;
var c;
/** {
function a(){ ... };
b : undefined,
c : undefined
} **/
- outerEnvironmentReference
: 현재 문맥과 관련있는 외부 환경 (=LexicalEnvironment) 을 참조하는 식별자 정보들이 담겨 있다
**Scope Chain** 현상이 만들어진다
: scope : 변수의 유효범위, 실행 컨텍스트가 수집해 놓은 정보만 접근할 수 있기에 이에 의해 만들어진다
정리
- outer context 종료
- global context 에서 a 탐색 -> 1 출력
this
핵심 : ThisBinding 은 실행 컨텍스트가 활성화 될 때 (함수가 호출될때) 한다
ThisBinding
- 함수가 호출될 때 결정되며 호출하는 방식에 따라 동적인지 정적인지 바뀐다
전역 공간에서 : this 는 전역 객체를 말한다 브라우저 : window / node.js : global
: 전역 컨텍스트를 실행하는 주체는 전역 객체 (host 객체) 이기 때문이다
자바스크립트가 실행되는 환경 즉, 런타임에 따라 전역객체의 정보가 달라진다 (런타임에서 제공하는 구현체이기 때문이다)
함수 호출 시 : this 는 전역 객체를 말한다 브라우저 : window / node.js : global
function a(){
console.log(this);
}
a(); // 호출하는 대상 = 전역 객체, 호출한 위치가 전역 공간이기 때문이다
function b(){
function c(){
console.log(this);
}
c(); // 전역 객체
}
b(); // 전역 객체
c( )
를 호출하면 b( ) 내부에서 실행 되었기 때문에 주체가 b( ) 라고 생각할 수 있는데 전역 객체가 주체라고 나온다 -> 버그일 수도 있다는 의견이 분분하여 this 를 바인딩하지 않는 arrow funciton
이 등장하게 되었다
1) arrow function : 바로 위 컨텍스트에 있는 this
를 그대로 가져다 쓴다 (위에서는 전역객체가 된다)
var d = {
e: function(){
funtion f(){
console.log(this);
}
f();
}
}
d.e();
메서드로써 호출했을 때의 this 는 호출한 대상 객체가 바인딩 되며, 해당 메서드 안에 f
내부 함수가 있고 이 내부 함수를 다시 호출하였을 때는 어떻게 될까?
-> 함수로써 호출하였기 때문에 this 전역 객체 이다 ( 호출 형태만 보도록 )
메서드 호출 시 : this 는 메서드를 호출한 주체 (메서드명 앞, 메서드를 누가 호출하였느냐)
var a = {
b: function(){
console.log(this);
}
}
a.b(); // b 메서드의 호출 주체는 a -> this 는 a
=> b 함수를 a 객체의 '메서드' 로서 호출한다
메서드 : 객체 지향 언어에서는 인스턴스와 관련된 동작을 의미하며, 자바스크립트에서는 클래스가 아니더라도 객체이기만 하면 어떤 객체와 관련된 동작을 뜻하는 말로 범위를 확장시켰다
(= 원래는 함수이더라도 어떤 객체와 관련된 동작이면 메서드가 된다)
obj.func();
obj[ 'func' ](); // obj 가 this, [ 'func' ] 가 메서드
person.info.getName( );
person.info[ 'getName' ]( );
person[ 'info' ].getName( );
person[ 'info' ][ 'getName' ]( ); // person[ 'info' ] 가 this, [ 'getName' ] 가 메서드
1) 메서드 내부함수에서의 우회법
var a = 10;
var obj = {
a: 20,
b: function(){
console.log(this.a); // 20
function c(){
console.log(this.a); // 10
}
c( ); // 함수로써 c( ) 를 호출
}
}
obj.b( );
-> function c() 를 호출하여 로그를 찍었을 때 10이 나온다
: 함수로써 c( ) 를 호출하였으므로 this 는 전역 객체가 된다 (=10)this.a
= window.a
와 일치하고, 이는 전역변수 a 와 이어진다 (10)
Q. 이러한 상황일 때 this 를 전역 객체가 아닌 obj 를 바라보게 하려면 어떻게 해야 할까?
A. call
, apply
등 명시적인 this 바인딩 명령을 쓰지 않고서는 this 자체를 직접 다른 값으로 덮어씌울 수는 없다
그렇다면 다른 변수를 사용하면 되지 않을까 ? -> 스코프 체인
스코프 체인을 활용하여 작성하기
var a = 10;
var obj = {
a: 20,
b: function(){
var self = this;
console.log(this.a);
function c(){
console.log(self.a);
}
c( );
}
}
obj.b();
- 내부함수 (
c
) 보다 상위에서 self 라고 하는 변수에 this 를 담는다 - 내부함수 (
c
) 는 자신의 'LexicalEnvironment' 에서 self 를 찾는다 - 찾는 결과가 없기 때문에 'outerEnvironmentReference' 를 타고 외부에 있는 b 함수의 'LexicaEnvironment' 에서 self 를 찾는다
- self 에는 앞서 들어온 this 는
obj.b();
로 들어왔기 때문에 obj 가 들어있고 이를 통해 메서드에서와 동일한 this 를 그대로 활용할 수 있게 된다
arrow funciton 이 등장하면서 우회법을 사용할 일은 없어졌다
ES6 arrow function
var a = 10;
var obj = {
a: 20,
b: function(){
console.log(this.a);
const c = () => { // 1.
console.log(this.a); // this.a = obj.a
}
c();
}
}
obj.b();
: arrow function 에서는 this 를 바인딩 하지 않기 때문에 스코프 체이닝 상위의 this 에 바로 접근 할 수 있다
ES5 call/apply
var a = 10;
var obj = {
a: 20,
b: function(){
console.log(this.a);
function c (){
console.log(this.a);
}
c.call(this);
}
}
obj.b();
실무에서 es6 가 아닌 환경에서 작업된 것들은 우회해서 많이 사용하고 있다
보통 변수명은_this
나that
같은 것으로 통용되고 있다
callback 호출 시
: 기본적으로는 함수 내부에서와 동일하다
call, apply, bind 메서드에 대하여 배경지식
function a(x, y, z){
console.log(this, x, y, z);
}
var b = {
bb : 'bbb'
};
a.call(b,1, 2, 3);
a.apply(b, [1, 2, 3] );
var c = a.bind(b);
c(1, 2, 3);
var d = a.bind(b, 1, 2);
d(3);
넷의 출력 결과는 모두 { bb : 'bbb' } 1 2 3 으로 동일하다
func.call( thisArg[, arg1[, arg2[, ... ]]] )
func.apply( thisArg, [argsArray] )
func.bind( thisArg[, arg1[, arg2[, ... ]]] )
세 메서드들의 api 를 살펴보면, this argument 자리를 제외하고는 다 생략 가능하다thisArg
자리는 func 를 호출하게 될 때 this 는 thisArg
로 인식되어 필수 매개변수이다 = 명시적 this 바인딩
콜백함수에서의 this
val callback = function( ){
console.dir(this);
};
var obj = {
a: 1,
b: function(cb){
cb(); // 넘어온 callback 함수 실행
cb.call(this);
}
};
obj.b(callback); // 메서드로써 호출
cb( );
함수로써 호출하였기 때문에 전역객체인 window 가 나온다cb.call(this)
: call 함수의 thisArg 에 this 를 넘겼다 -> this 는 obj 가 된다
var callback = function( ){
console.dir(this);
};
var obj = {
a: 1
};
setTimeout(callback, 100);
- 0.1 초 뒤에 callback 함수를 실행하고 'setTimeout' 은 this 를 별도로 처리하지 않기 때문에 전역객체가 나오게 된다
setTimeout 이 콜백을 처리하는 방식을 임의로 바꿀 수는 없기 때문에 this 를 원하는 값으로 지정하려면 바인드를 사용한다
var callback = function( ){
console.dir(this); // 결과 a :1
};
var obj = {
a: 1
};
setTimeout(callback.bing(obj), 100);
이벤트 핸들러
document.body.innerHTML += '<div id="a"> 클릭하세요 </div>';
document.getElementById('a').addEventListener(
'click',
function(){
console.dir(this);
}
)
addEventListener
이 이벤트 발생 시 콜백함수를 실행할 때, 별도로 무언가 따로 설정하지 않았다면, console.dir(this)
에서는 전역객체가 나와야 맞다
하지만,
이런 html DOM element 으로 나오게 되는데, this 는 이벤트가 발생한 그 타겟 대상 엘리먼트로 하도록 정의
되어 있는 것이다
이벤트 핸들러에서, this 를 바꿀 수 있다
document.body.innerHTML += '<div id="a"> 클릭하세요 </div>';
var obj = { a: 1};
document.getElementById('a').addEventListener(
'click',
function(){
console.dir(this);
}
);
this 를 obj 로 하고 싶다!
document.body.innerHTML += '<div id="a"> 클릭하세요 </div>';
var obj = { a: 1};
document.getElementById('a').addEventListener(
'click',
function(){
console.dir(this);
}.bind(obj)
);
콜백 함수를 넘겨줄 때 this 를 obj 로 하도록 명시적으로 바인딩한 것이다 -> this = obj
정리
- 기본적으로는 함수의 this 와 같다
- 제어권을 가진 함수가 콜백의 this 를 지정해둔 경우도 있다
- 이 경우에도 개발자가 this 를 바인딩하여 콜백을 넘기면 그에 따르게 된다
생성자 함수 호출 (new 사용)
: 생성자 함수의 내용을 바탕으로 인스턴스 객체를 만드는 명령이다 즉, 새로 만들 인스턴스 객체 그 자체가 곧 this 이다
callback 함수
: 회신되는 함수, 내가 넘기고자 하는 대상에게 콜백함수에 대한 제어권을 넘기고 결과를 받는다
콜백을 어떻게 처리할 지는 제어권을 넘겨받은 대상이 전적으로 관여하게 된다
제어권 위임
실행시점
setInterval( function() {
console.log('1초마다 실행될 겁니다.');
}, 1000);
setInterval( 인자1, 인자2 )
은 일정 시간 간격으로 한 번씩 함수를 실행해주는 주기 함수로, 실행시점에 대한 제어권을 넘겨준다인자 1
: 콜백함수 인자 2
: 주
매개변수
var arr = [1, 2, 3, 4, 5];
var entries = [];
arr.forEach(function(v, i) {
entries.push( [i, v, this[i]] );
}, [10, 20, 30, 40, 50]);
console.log(entries);
// [ [0, 1, 10], [1, 2, 20], [2, 3, 30], [3, 4, 40], [4, 5, 50] ]
forEach( 인자1 [, thisArg] )
메서드 자체에서 첫번째 인자는 callback 함수를 받고, 두번째 인자는 thisArg 를 받는다 (생략 가능)인자 1
: 인수 셋을 취하는 각 요소에 대해 실행할 함수
currentValue : 배열에서 현재 처리 중인 요소
index : 배열에서 현재 처리 중인 요소의 인덱스
array : forEach( ) 가 적용되고 있는 배열반환값
: undefined
this
document.body.innerHTML = '<div id="a"> abc </div>';
function cbFunc(x) {
console.log(this, x);
}
document.getElementById('a').addEventListener('click', cbFunc);
target.addEventListener(type, listener[, options || use... ]);
type
: 반응할 이벤트 유형을 나타내는 대소문자 구분 문자열 (click, mousemove, keyup, dragstart, scroll)listener
: 지정된 타입의 이벤트가 발생했을 때 콜백함수가 온다 (단일 매개변수를 허용하며 발생한 이벤트를 설명하는 Event 에 기반한 객체이며 아무것도 반환하지 않는다)this
: 핸들러 내부의 this 값은 요소에 대한 참조. 핸들러에 전달된 이벤트 인수의 currentTarget 속성(프로퍼티) 와 같다
- 콜백함수의 특징
: 다른 함수 (A) 의 인자로 콜백함수 (B) 를 전달하면, A가 B 의 제어권을 갖게 된다
: 특별한 요청(bind) 가 없는 한 A에 미리 정해놓은 방식에 따라 B 를 호출한다
: 미리 정해둔 방식이란, 어떤 시점에 콜백을 호출할지 인자에는 어떤 값들을 지정할지 this 에 무엇을 바인딩할 지 등이다
콜백함수로 넘기는 것은 무조건 함수이며 다른 것으로 지정하고 싶으면 bind 로 넘겨주거나, this 를 지정해주어야 한다
클로저 (Closure)
: 내부함수와 LexicalEnvironment 의 조합, 함수의 생성과 함께 무조건 생긴다
= 보통, 컨텍스트 A 에서 선언한 변수를 내부함수 B에서 참조할 경우에 발생하는 특별한 현상을 말한다
일반적인 현상
var outer = function(){
var a = 1;
var inner = function(){
console.log(++a);
};
inner();
}
outer();
특별한 현상
var outer = function(){
var a = 1;
var inner = function(){
return ++a;
};
return inner;
}
var outer2 = outer();
console.log(outer2( )); // 2
console.log(outer2( )); // 3
a 변수는 전역 컨텍스트가 종료되기 전까지 살아있다 outer2 변수가 inner 함수를 물고 있고 여기서 outerEnvironmentReference
를 통해 a 를 참조하고 있기 때문이다
참조카운트가 0이 되지 않기 때문에 살아있고 0이 되게 하려면 outer2 변수에 다른 것을 대입하면 된다
-> inner dp eogks 참조카운트가 0이 되고, 참조하지 않으니 outer 는 GC(Grabage Collection) 대상이 된다
특별한 현상은 outer 에 있는 변수 a 가 사라지지 않고 계속해서 참조되는 상황
=컨텍스트 A 에서 선언한 변수 a 를 참조하는 내부함수 B 를 A 의 외부로 전달할 경우, A 가 종료된 이후에도 a 가 사라지지 않는 현상
클로저는 지역변수가 함수 종료 후에도 사라지지 않게 할 수도 있다
다른 예제
function user(_name){
var _logged = true;
return {
get name() {return _name},
set name(v) {_name = v},
login() {_logged = true},
logout() {_logged = false },
get status(){
return _logged ? 'login' : 'logout';
},
}
}
var roy = user('남');
console.log(row.name); // '재남'
roy.name = '제이';
console.log(roy.name); // '제이'
roy._name = '로이'
console.log(roy.name); // '제이'
console.log(roy.status); // 'login'
roy.logout();
console.log(roy.status); // 'logout'
함수 종료 후에도 사라지지 않고 값을 유지하는 변수를 확인할 수 있다 (roy.name)
외부로부터 내부 변수 보호 (캡슐화)
Prototype 프로토타입
Prototype, [[Prototype]]
(프로토), constructor
생성자 함수가 new 연산자가 인스턴스를 만들면 생성자의 'prototype' 이라고 하는 프로퍼티의 내용이 이 프로토 라고 하는 프로퍼티로 참조를 전달하게 된다Constructor.prototype
와 instance 의 프로토
가 같은 객체를 향한다
그런데 프로토는 접근 가능한 것이 아니고 정보를 보여주기만할 뿐으로 실제 동작 상으로는Constructor.prototype = instance
가 된다
인스턴스가 기본형일 경우
: 기본형 타입의 데이터 리터럴 자체는 객체가 아니므로 프로토
프로퍼티가 있을 수가 없는데, 그러함에도 리터럴을 인스턴스인것처럼 사용하려고 한다면 (=메서드를 사용하고자 한다면)
- 자바스크립트가 임시로 해당하는 리터럴에 해당하는 생성자 함수의 인스턴스를 만들어준다
- 원하는 메서드를 적용하여 결과를 얻게 한다
- 다시 인스턴스를 제거하는 식으로 동작하게 된다
인스턴스가 참조형일 경우
: 참조형 데이터들은 처음부터 인스턴스이기 때문에, 메서드를 호출한 순간에 임시로 인스턴스를 생성했다가 폐기하는 복잡한 과정을 거치지 않는다
데이터 자신에게는 메서드들이 없지만 생성자의 프로토타입 프로퍼티에 있는 것을 프로토라는 연결 통로에 의해 사용할 수 있게 된다
null 과 undefined 를 제외한 모든 데이터 타입은 생성자 함수가 존재하여 각 데이터 타입에만 해당하는 전용 메서드들이 정의되어 있다
인스턴스의 직접 접근
프로토는 콘솔에 표시되는 내용일 뿐이고, 실제로 이 프로퍼티를 이용하여 프로토타입에 직접적으로 접근할 수는 없다 그러면 인스턴스에서 직접 접근할 수 있는 방법은 ?
instance.__proto__
Object.getPrototypeOf(instance)
위의 두가지가 있다
__proto__
프로퍼티 : 콘솔에 보이지는 않지만 접근하고자 하면 접근이 가능하다
ES2015 에서 기존 브라우저들이 마음대로 제공하고 있는 기능을 호환성 차원에서 하는 수 없이 문서화해준 것일 뿐이기 때문에 가급적이면 두 번째 방법을 사용해야 한다
function Person(n, a){
this.name = n;
this.age = a;
}
var roy = new Person('로이', 30);
var royClone1 = new roy.__proto__.constructor('로이_클론1', 10);
var royClone2 = new roy.constructor('로이_클론2', 25);
var royClone3 = new Object.getPrototypeOf(roy).constructor( '로이_클론3', 20 );
var royClone4 = new Person.prototype.constructor('로이_클론4', 15);
roy.__proto__.constructor
, roy.constructor
, Object.getPrototypeOf(roy).constructor
, Person.prototype.constructor
네 가지 모두 프로토타입의 프로퍼티에 접근하고 있다
메서드 상속 및 동작 원리
function Person(n, a){
this.name = n;
this.age = a;
}
var roy = new Person('로이', 30);
var jay = new Person('제이', 25);
roy.setOlder = function(){
this.age += 1;
}
roy.getAge = function(){
return this.age;
}
jay.setOlder = function(){
this.age += 1;
}
jay.getAge = function(){
return this.age;
}
setOlder
, getAge
메서드의 반복되므로 최대한 반복을 줄이도록 변경해보자
function Person(n, a){
this.name = n;
this.age = a;
}
Person.prototype.setOlder = function(){
this.age += 1;
}
Person.prototype.getAge = function( ){
return this.age;
}
var roy = new Person('로이', 30);
var jay = new Person('제이', 25);
메서드를 프로토타입으로 이동시켰는데, 이렇게 할 경우 이점이 무엇일까?
- 딱 한번 만들어 놓은 코드로 여기저기 참조하여 인스턴스를 만든다
- 인스턴스들은 저마다의 고유한 정보들만 가지고 있으면 되고, 인스턴스들이 모두 똑같이 가지는 정보들은 프로토타입으로 보내면 된다
메모리 사용 효율 최적화 시킬 수 있다
어떤 객체가 속한 집단의 특징을 알 수 있는 좋은 수단이 되기도 한다
Prototype Chaining 프로토타입 체이닝
: 프로토타입 체인
Class
: 인스턴스들의 공통적인 속성을 한 데 묶은 추상적인 개념 덩어리 또는 명세라고 할 수 있다
인스턴스 : 해당 클래스의 속성을 지닌 구체적인 객체들이다
- static methods, static properties
: 프로토타입 프로퍼티 내부에 할당되지 않고 Array 생성자 함수 객체에 직접 할당되어 있는 프로퍼티
array 함수를 new 연산자 없이 함수로써 호출할 때에만 의미가 있는 값이다
: 보통 공동체적인 판단을 필요로 하는 경우에 사용한다
: 인스턴스에서는 접근할 수 없고 클래스 자체에서만 접근 가능한 static
getInformations
메서드는 static 메서드로, new 연산자 없이 함수로써 호출해야 한다
console.log( roy.getInformations(roy) );
는 new 키워드를 통해 생성된 변수 roy 로 스태틱 메서드에 접근하였기 때문에 에러가 발생한다
Class Inheritance 클래스 상속
위와 같은 구조일 때 코드는 다음과 같이 구성되어 있다
function Person(name, age) {
this.name = name || '이름없음';
this.age = age || '나이모름';
} // Person 생성자함수
// 프로토타입 메서드
Person.prototype.getName = function(){
return this.name;
}
Person.prototype.getAge = function(){
return this.age;
}
function Employee(name, age, position){
this.name = name || '이름없음';
this.age = age || '나이모름';
this.position = position || '직책모름';
} // Employee 생성자 함수
// 프로토타입 메서
Employee.prototype.getName = function(){
return this.name;
}
Employee.prototype.getAge = function(){
return this.age;
}
Employee.prototype.getPosition = function( ){
return this.position;
}
getName, getAge 메서드들이 겹치는데 프로토타입 체인을 사용하여 간결하게 사용해보자
Employee.prototype = new Person();
을 통해 둘을 연결하여 사용할 수 있다, Employee 의 프로토 타입에 Person 의 인스턴스를 할당하는 것이다
그리고, 위처럼 연결해버리면 기존에 있던 Employee.prototype 객체를 새로운 객체로 대체하는 결과가 되어버린다 -> 여타의 프로토타입과 동일하게 동작하게 해주기 위해서는 본래 가지고 있던 기능을 다시 부여해줄 필요가 있다
프로토타입의 생성자는 Employee 라고 알려주어야 한다
Employee.prototype.constructor = Employee
function Person(name, age) {
this.name = name || '이름없음';
this.age = age || '나이모름';
}
Person.prototype.getName = function(){
return this.name;
}
Person.prototype.getAge = function(){
return this.age;
}
function Employee(name, age, position){
this.name = name || '이름없음';
this.age = age || '나이모름';
this.position = position || '직책모름';
}
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
Employee.prototype.getPosition = function(){
return this.position;
}
var roy = new Employee('로이', 30, 'CEO');
console.dir(roy);
프로토타입 체이닝 상에는 프로퍼티가 아닌 메서드들만 존재하게끔 하는 것이 '추상적인 클래스' 라고 하는 정의에 부합한다
만약, Employee 에 name: '로이'
를 작성하지 않아도, 프로토타입 체이닝을 타고 undefined 를 반환하지 않고 이름없음
을 반환하게 된다, 이는 부적합하기 때문에 수정해보도록 하자
Person.prototype 을 상속 받는 별도의 인스턴스가 있고, 그 객체에는 아무런 프로퍼티도 존재하지 않으면 된다
function Person(name, age) {
this.name = name || '이름없음';
this.age = age || '나이모름';
}
Person.prototype.getName = function(){
return this.name;
}
Person.prototype.getAge = function(){
return this.age;
}
function Employee(name, age, position){
this.name = name || '이름없음';
this.age = age || '나이모름';
this.position = position || '직책모름';
}
function Bridge(){ }
Bridge.prototype = Person.prototype;
Employee.prototype = new Bridge();
Employee.prototype.constructor = Employee;
Employee.prototype.getPosition = function(){
return this.position;
}
var roy = new Employee('로이', 30, 'CEO');
console.dir(roy);
function Bridge( ){ }
는 아무런 프로퍼티도 생성하지 않는 비어있는 생성자 함수다Bridge.prototype = Person.prototype;
Bridge 의 프로토타입에 Person 의 prototype 을 덮어씌운다Employee.prototype = new Bridge();
Employee 의 프로토타입을 Bridge 의 인스턴스와 연결해준다Employee.prototype.constructor = Employee;
Employee 의 프로토타입의 생성자가 다시 Employee 를 가리키게끔 복원시켜준
이후에 하위 클래스의 고유 메서드를 지정한 다음 인스턴스를 생성하면 원하던 결과를 볼 수 있다
더블라스 크락포드 께서는 Bridge 가 실제 코드에 영향을 주지도 않고 상관 없기 때문에 함수로 바꿔서 사용할 것을 추천한다
var extendClass = (function( ) {
function Bridge(){}
return function(Parent, Child) {
Bridge.prototype = Parent.prototype;
Child.prototype = new Bridge();
Child.prototype.constructor = Child;
Child.prototype.constructor = Parent;
}
}) ();
extendClass(Person, Employee);
Employee.prototype.getPosition = function(){
return this.position;
}
var roy = new Employee('로이', 30, 'CEO');
console.dir(roy);
function Person(name, age) {
this.name = name || '이름없음';
this.age = age || '나이모름';
}
Person.prototype.getName = function(){
return this.name;
}
Person.prototype.getAge = function(){
return this.age;
}
function Employee(name, age, position){
this.superClass(name, age);
this.position = position || '직책 모름';
}
즉, 클로저를 이용해서 Bridge 생성자 함수는 단 한 번만 생성하여 계속 재활용하고, super 와 sub 클래스로 쓰일 생성자 함수를 매개변수로 넘겨주면 자동으로 둘 사이의 상속 구조를 연결해주는 함수이다
원래 하던 대로 class A extends b 이런 거 쓰면 될 듯....
'Front > JavaScript' 카테고리의 다른 글
자바스크립트에서 백그라운드, 이벤트 루프, 테스트 큐란? (0) | 2024.08.25 |
---|