Chapter 04 - norux/javascript_study GitHub Wiki

**Table of Contents** # 4. 함수와 프로토타입 체이닝

이 장에서는 함수와 관련된 다음의 개념에 대해 알아본다.

  • 함수 생성
  • 함수 객체
  • 다양한 함수 형태
  • 함수 호출과 this
  • 프로토타입과 프로토타입 체이닝

Top

4.1 함수 정의

함수를 생성하는 3가지 방법

  • 함수 선언문 (Function Statement)
  • 함수 표현식 (Function Expression)
  • Function() 생성자 함수

Top

4.1.1 함수 리터럴

함수 역시 일반 객체처럼 값으로 취급되므로, 객체 리터럴로 객체를 생성하는 것처럼 함수 리터럴로 함수를 생성할 수 있다. 함수 선언문이나 함수 표현식 방법 모두 이런 함수 리터럴 방식으로 함수를 생성한다.

function add(x,y) {
    return x + y;
}

Top

4.1.1.1 함수의 구성

  1. function 키워드: 자바스크립트 함수 리터럴은 function 키워드로 시작
  2. 함수명: 함수명은 함수를 구분하는 식별자로 사용되지만, 함수명은 선택사항이다. 함수명이 없는 함수를 익명 함수라 한다.
  3. 매개변수 리스트: 매개변수는 타입을 기술하지 않는다.
  4. 함수 몸체: 함수가 호출되었을 때 실행되는 코드

Top

4.1.2 함수 선언문 방식으로 함수 생성

함수 선언문 방식은 반드시 함수명이 정의되어 있어야 한다. 이 함수 선언문 방식은 C/C++의 함수 정의 방법과 유사하다.

// add() 함수 선언문
function add(x, y) {
    return x + y;
}

console.log( add( 3, 4 ) );    // 출력값: 7

Top

4.1.3 함수 표현식 방식으로 함수 생성

자바스크립트에서는 함수도 하나의 값처럼 취급된다. (이러한 특징때문에 자바스크립트의 함수를 일급 객체라고 한다.) 따라서 함수도 숫자나 문자열처럼 변수에 할당하는 것이 가능하다.

함수 리터럴로 함수를 만들고, 이 함수를 변수에 할당하여 생성하는 것을 함수 표현식이라 한다.

// add() 함수 표현식
var add = function (x, y) {
    return x + y;
}

var plus = add;

console.log( add( 3, 4) );     // (출력값) 7
console.log( plus( 5, 6) );    // (출력값) 11

함수 표현식에서는 함수 이름이 선택사항이며, 보통 사용하지 않는다.

  1. add는 함수 이름이 아닌, 함수 변수이다.
  2. add는 함수의 참조값을 가지므로 변수 plus에도 그 값을 할당할 수 있다.

ch04-2

위 예제처럼 이름이 없는 함수를 익명 함수라 한다. 반대로 함수 이름이 포함된 표현식을 기명 함수 표현식이라 한다.

기명 함수 표현식을 사용할 때는 주의해야 할 점이 있다.

// 기명함수 표현식의 함수 호출 방법
var add = function sum(x, y) {
    return x + y;
}

consoloe.log( add( 3, 4) );    // (출력값) 7
consoloe.log( sum( 3, 4) );    // (출력값) 에러 발생!!!

** 함수 표현식에 사용된 함수 이름이 외부 코드에서 접근 불가능하기 때문에 에러가 발생했다. **

그럼 함수 선언문으로 사용된 add() 함수는 어떻게 함수 이름으로 호출이 가능했을까? 함수 선언문의 함수는 자바스크립트 엔진에서 아래와 같이 함수 표현식 형태로 변경된다.

// 자바스크립트 엔진에 의해 함수 표현식 형태로 변경
var add = function add(x, y) {
    return x + y;
}

함수 이름과 함수 변수의 이름이 add로 같으므로, 함수 이름으로 호출되는 것처럼 보이지만 실제로는 add 함수 변수로 호출된 것이다.

ch04-3

함수 표현식에서 함수이름은 선택사항이지만, 함수 이름을 사용하면 함수 내부에서 재귀호출이 가능하다.

// 함수 표현식 방식으로 구현한 팩토리얼 함수
var factorialVar = function factorial(n) {
    if( n <= 1 ) {
        return 1;
    }
    return n * factorial( n - 1 );
};

console.log( factorialVar( 3 ) );    // (출력값) 6
console.log( factorial( 3 ) );       // (출력값) undefined

ch04-4

function statement와 function expression에서의 세미콜론

함수 선언문 방식으로 함수를 정의할 때는 세미콜론을 붙이지 않았지만, 함수 표현식 방식으로 함수를 정의할 때는 함수 끝에 세미콜론을 붙였다.

이것은 하나의 관습으로, 일반적으로 자바스크립트 코드를 작성할 때 함수 선언문 방식으로 선언된 함수의 경우는 함수 끝에 세미콜론을 붙이지 않지만, 함수 표현식 방식의 경우에는 세미콜론을 붙이는 것을 권장한다.

즉시 실행 함수 가 문제를 야기할 수 있다.

var func = function() {
    return 42;
}     // 세미콜론 사용하지 않음
(function()) {
    console.log( "function called" );
})();

이 함수를 호출하면, 의도와는 다르게 'number is not a function'이라는 에러가 발생한다.

자바스크립트 파서가 func() 함수 정의에서 세미콜론을 사용하지 않아, return 42; 문장을 지나 func()함수 정의 끝에 있는 } 만으로 함수가 끝났다고 판단하지 않기 때문이다. 그리고 이후에 나타난 즉시 실행 함수를 보고 func()함수를 호출해 버린다. 때문에 func()의 리턴값인 42를 받고 마지막에 남겨진 ()를 받아 42(); 의 형태가 된다. 하지만 42는 숫자이므로 위와 같은 에러를 발생시킨다.

따라서, 많은 자바스크립트 가이드에서는 함수 표현식 방식에서의 세미콜론 사용을 강력하게 권고한다.

Top

4.1.4 Function() 생성자 함수를 통한 함수 생성하기

자바스크립트의 함수는 **Function()**이라는 기본 내장 생성자 함수로부터 생성된 객체라고 볼수 있다. 리터럴로 선언한 자바스크립트의 함수 역시, 내부적으로는 Function() 생성자 함수로 함수가 생성된다고 볼 수 있다.

Function() 생성자 함수로 함수를 생성하는 문법

new Function( arg1, arg2, ..., argN, functionBody )

  • arg1, arg2, ..., argN: 함수의 매개변수
  • functionBody: 함수가 호출될 때 실행될 코드를 포함한 문자열
// Function() 생성자 함수를 이용한 add() 함수 생성
var add = new Function( 'x', 'y', 'return x + y' );
console.log( add( 3, 4 ) );    // (출력값) 7

참고: 생성자 함수로 함수를 생성하는 방법은 일반적으로 자주 사용되는 기법은 아니다.

Top

4.1.5 함수 호이스팅

세 가지 방식의 함수 생성방법에는 각각 동작 방식에 약간의 차이가 있다. 자바스크립트의 Guru인 더글러스 크락포트는 함수 생성에 있어, 함수 표현식만을 사용할 것을 권한다. 그 이유 중 하나가 함수 호이스팅 때문이다.

// 함수 선언문 방식과 함수 호이스팅
add( 2, 3 );    // 5

// 함수 선언문 형태로 add() 함수 정의
function add(x, y) {
    return x + y;
}

add( 3, 4 );    // 7
  1. add( 2, 3 )이 호출되는 시점에 add 함수는 정의되지 않은 상태이다. 하지만 호출하는 것이 가능하다. 함수 선언문 형태로 정의한 함수의 유효범위는 코드의 맨 처음부터 시작한다는 것을 함수 호이스팅 이라고 부른다.
// 함수 표현식 방식과 함수 호이스팅
add( 2, 3 )    // uncaught type error

// 함수 표현식 형태로 add() 함수 정의
var add = function (x, y) {
    return x + y;
};

add( 3, 4 );    // 7
  1. 여기서는 add가 함수 표현식 형태로 정의되어 있어서 함수 호이스팅이 일어나지 않는다.

이러한 함수 호이스팅은 함수를 선언하기 전에 반드시 선언해야 한다는 규칙을 무시하므로 코드 구조를 엉성하게 만들수 있다고 지적된다. 함수 호이스팅의 원인은 자바스크립트가 변수생성, 초기화의 작업이 분리되어 진행되기 때문으로 5장에서 다룬다.

Top

4.2 함수 객체: 함수도 객체다

Top

4.2.1 자바스크립트에서는 함수도 객체다

function add(x, y) {
    return x+y;
}

add.result = add(3,2); //5
add.status='OK'        //OK

일반 객체처럼 result 와 status 프로퍼티를 추가하는게 가능하다.

Top

4.2.2 자바스크립트에서는 함수는 값으로 취급된다.

  1. 리터럴에 의해 생성
  2. 변수나 배열의 요소, 객체의 프로퍼티 등에 할당 가능
  3. 함수의 인자로 전달가능
  4. 함수의 리턴값으로 리턴 가능
  5. 동적 프로퍼티를 생성 및 할당 가능

일급객체란?

Top

4.2.2.1 변수나 프로퍼티의 값으로 할당

var foo = 1000;
var bar = function() = { return 100; };

var obj = {};
obj.baz = function() { return 200; };

Top

4.2.2.2 함수 인자로 전달

var foo = function(func) {
    func();
}
foo(function() {console.log('test');});

Top

4.2.2.3 리턴값으로 활용

var foo = function() {
    return function() {
        console.log('test');
    }
}

var bar = foo();
bar();

Top

4.2.3 함수 객체의 기본 프로퍼티

함수는 객체이다 함수는 일반 객체와 다르게 함수만의 표준 프로퍼티가 정의되어 있다.

Top ####4.2.3.1. length 함수가 정상적으로 실행될 때 기대되는 인자의 개수

function func1(x){
    return x;
}

function func2(x, y) {
    return x + y;
}

console.log('func1.length - ' + func1.length ); // (출력값) func1.length - 1
console.log('func2.length - ' + func2.length ); // (출럭값) func2.length - 2

Top ####4.2.3.2. prototype 프로퍼티 constructor 프로퍼티 하나만 있는 객체

prototype 프로퍼티의 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다.

즉, 함수의 prototype 프로퍼티는 프로토타입 객체를 가리키고, 프로토타입 객체의 constructor 프로퍼티는 함수 객체를 가리킨다.

function func1(){
    return true;
}

console.dir(func1.prototype.constructor); // (출력값) constructor : function func1()

Top ####4.2.3.3. 그 외 기본 프로퍼티

  • name : 함수 이름

  • caller : 자신을 호출한 함수

  • arguments : 인자값

  • proto : 부모 역할을 하는 프로토타입 객체

Top

4.3 함수의 다양한 형태

Top

4.3.1 콜백함수

함수이름은 선택사항이다. (붙여도되고 안붙여도 됨) 콜백함수는 익명함수의 대표적인 용도 중 하나이다.

대표적인 콜백함수의 사용 예

  • 이벤트 핸들러 처리: 웹페이지 로드, 키보드 입력등의 DOM 이벤트가 발생할 경우 DOM이벤트에 해당하는 이벤트 핸들러를 실행시킨다.
//window.onload 이벤트 핸들러 예제 코드
<!DOCTYPE html>
<html><body>
  <script>
    // 페이지 로드 시 호출될 콜백 함수
    window.onload = function() {
      alert('this is the callback function');
    };
  </script>
</body></html>
  • 위 예제코드는, 웹페이지가 로딩 될 때 경고창이 뜨게 된다.

Top

4.3.2 즉시 실행 함수

함수를 정의함과 동시에 바로 실행하는 함수를 즉시 실행 함수라고 말한다.

(function (name) {
  console.log('This is the immediate function -> ' + name);
})('foo');

// 결과: This is the immediate function -> foo
  • 이러한 즉시 실행 함수는, 같은 함수를 다시 호출할 수 없다.
  • 최초 한번의 실행만을 필요로하는 초기화 코드에 사용될 수 있다.

jQuery와 같은 라이브러리, 프레임워크에서는 소스코드 전체가 즉시 실행함수로 감싸져 있다.

// jQuery에서 사용된 즉시 실행 함수
(function( window, undefined ) {
...
})( window );

이렇게 사용하는 이유는 함수 유효 범위 특성 때문이다.

기본적으로 자바스크립트는 변수를 선언할 경우, 전역 유효 범위를 가지게 된다. 함수 내에 선언된 변수들은 함수 유효 범위를 가진다.

따라서, 라이브러리같이 범용적으로 사용 될 코드에서 전역 유효 범위의 변수를 지정하게 되면 문제가 있을 수 있기 때문에, 즉시 실행 함수내에 라이브러리 코드를 추가하여 라이브러리간 변수 이름 충돌 문제를 방지할 수 있다.

유명 라이브러리의 즉시 실행 함수 패턴

// Underscroe 1.3.3 Library
(function() {
  var root = this;
  var previousUnderscore = root._;
  ....
  var _ = function(obj) { return new wrapper( obj ); };
  ....
  root['_'] = _;
  ...
}).call( this );

call 함수를 this 인자와 함께 사용되었다. 즉시 실행 내부의 전역 객체인 this에 바인딩 되고, 함수 내부에서 root라는 이름으로 사용된다.

// Sugar 1.2 Library (function(){ ... // Initialize buildObject(); buildString(); buildFunction() initializeClass(date); })();


> 특별한 인자 없이 즉시 실행 함수를 호출한다. sugar에서 제공하는 대부분의 함수는 Object.prototype이나 Function.prototype 등 기존에 있는 객체에 들어가므로, 특별히 네임스페이스를 정의하지 않았다.


[Top](#_top)
<a name="ch04.3.3"/>
### 4.3.3 내부 함수

함수 코드 내부에 정의한 함수를 **내부 함수**라고 한다.
**내부 함수**는, 클로저를 생성하거나 부모 함수 코드에서 외부의 접근을 막고 독립적인 헬퍼 함수를 구현하는 용도로 사용한다.

```javascript
//내부 함수 예제 코드

//parent() 함수 정의
function parent() {
    var a = 100;
    var b = 200;

    // child() 내부 함수 정의
    function child() {
        var b = 300;
        console.log( a );
        console.log( b );
    }
    child();
}
parent();
child();

//출력결과
//100
//300
//Uncaught ReferenceError: child is not defined

내부 함수에서는 자신을 둘러싼 부모 함수의 변수에 접근이 가능하다.

  • child 내부함수에 변수 a개 선언되지 않았지만, parent() 함수의 변수 a에 접근 가능하다.
  • 변수 b는 child() 함수에 선언되어 있으므로, child() 함수의 b를 참고했다.

자바스크립트의 스코프 체이닝 자세한 내용은 5장에서 설명한다. 내부 함수는 자신을 둘러싼 외부함수의 변수에 접근 가능하다

내부 함수는 일반적으로 자신이 정의된 부모 함수 내부에서만 호출이 가능하다.

  • parent() 함수 외부에서 child()함수의 호출을 시도하지만 함수가 정의되지 않았다는 에러가 발생한다.

함수 스코프 함수 스코프 밖에서는 함수 스코프 안에 선언된 모든 변수나 함수에 접근이 불가능하다. 함수 스코프 안의 선언된 내부 함수를 호출할 수 있는 방법이 있는데, 부모함수에서 내부함수를 외부로 리턴한다면 부모 함수 밖에서도 내부 함수를 호출하는 것이 가능하다.

//함수 스코프 외부에서 내부 함수 호출하는 예제 코드

function parent() {
  var a = 100l
  // child() 내부함수
  var child = function() {
    console.log( a );
    }
  
  //child() 함수 반환
  return child;
}

var inner = parent();
inner();

//출력결과
//100

실행이 끝난 parent() 함수의 내부 변수인 a를 참조하여 100이 출력되었는데, 이처럼 사용되는 inner()같은 함수를 클로저 라고 한다.

Top

4.3.4 함수를 리턴하는 함수

자바스크립트에서 함수도 일급 객체이므로, 함수 자체를 리턴할 수 있다.

//자신을 재정의하는 함수 예제 코드

//self() 함수
var self = function () {
  console.log('a');
  return function() {
    console.log('b');
  }
}
self = self();  // a
self();         // b
  1. 처음에 self()가 호출될 때, a가 출력된다. 그리고 다시 return으로 새로운 함수를 저장한다.
  2. 두번째로, self()가 호출되면 b가 출력된다.

Top

4.4 함수 호출과 this

c/c++과는 다르게 엄격한 문법호출을 하지 않아 함수 호출 자체가 자유롭다.

Top

4.4.1 arguments 객체

c/c++과는 달리, 함수를 호출할 때 함수 형식에 맞춰 인자를 넘겨주지 않더라도 에러가 발생하지 않는다.

function func(arg1, arg2) {
  console.log(arg1, arg2);
}

func();       //(출력) undefined undefined
func(1);      //(출력) 1 undefined
func(1,2);    //(출력) 1 2
func(1,2,3);  //(출력) 1 2
  • 함수의 인자보다 적게 호출할 경우, undefined가 할당된다.
  • 인자보다 많이 호출할 경우, 초과된 인수는 무시된다.
  • 이렇게 넘겨준 인자들은 arguments 객체에 유사 배열 객체 형태로 저장되어 넘겨진다.
//arguments 객체 예제 코드

//add() 함수
function add(a,b) {
  //arguments 객체 출력
  console.dir(arguments);
  return a+b;
}

console.log(add(1));      //(출력) NaN
console.log(add(1,2));    //(출력) 3
console.log(add(1,2,3));  //(출력) 3

console.dir(arguments) 크롬에서 출력되는 결과 ch04-15

다시말하지만, arguemtnts는 객체이다. 배열이 아니다. 유사 객체 배열에서 배열 메소드를 사용하는 방법은 4.4.2.4 call과 apply 메소드를 이용한 명시적인 this 바인딩에서 알아본다.

//arguments 객체의 사용 예

function sum() {
  var resule = 0;

  for(var i=0; i < arguemnts.length; i++){
    result += arguments[i];
  }
  return result;
}

console.log(sum(1,2,3));              //(출력) 6
console.log(sum(1,2,3,4,5,6,7,8,9));  //(출력) 45

Top

4.4.2 호출 패턴과 this 바인딩

자바스크립트에서 함수를 호출하면, arguemnts 객체this 인자가 암묵적으로 전달된다. 함수가 호출되는 방식(호출방식)에 따라서 this가 다른 객체를 참조(this바인딩)하기 때문에 this를 이해하는 것이 어렵다.

Top

4.4.2.1 객체의 메서드 호출할 때 this 바인딩

메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.

//메서드 호출 사용 시 this 바인딩

// myObject 객체 생성
var myObject = {
  name: 'foo',
  sayName: function() {
    console.log(this.name);
  }
};

//otherObject 객체 생성
var otherObject = {
  name: 'bar'
}

// otherObject.sayName() 메서드
otherObjet.sayName = myObject.sayName;

myObject.sayName();
otherObject.sayName();

//출력결과
//foo
//bar

위의 예제에서 보다시피 this는 자신을 호출한 객체에 바인딩 된다.

Top

4.4.2.2 함수를 호출할 때 this 바인딩

함수 내부에서 사용된 this는 전역 객체에 바인딩된다. 브라우저에서 자바스크립트를 실행하는 경우, 전역객체는 window 객체가 된다.

전역 객체란 무엇인가?? (브라우저, Node.js)

브라우저 환경에서 자바스크립트를 실행하는 경우, 전역객체는 window 객체가 된다. Node.js같은 자바스크립트 런타임 환경에서 전역객체는 global 객체가 된다.

// 전역 객체와 전역 변수의 관계를 보여주는 예제 코드

var foo = "I'm foo";  // 전역변수 선언

console.log(foo);         //(출력) I'm foo
console.log(window.foo);  //(출력) I'm foo

따라서 전역변수는 전역 객체(window)의 프로퍼티로도 접근 가능하다.

// 함수를 호출할 때 this 바인딩을 보여주는 예제 코드

var test = 'This is test';
console.log(window.test);

//sayFoo() 함수
var sayFoo = function(){
  console.log(this.test);   // sayFoo() 함수 호출 시 this는 전역 객체에 바인딩된다.
}

//출력결과
//This is test
//This is test
  1. 우선 test라는 전역변수는 전역객체 window 프로퍼티로 접근이 가능하다.
  2. sayFoo()함수에서 this는 전역객체 window에 바인딩된다. 따라서 this.test 는 window.test를 의미한다.

이같은 특성은 내부함수를 호출했을 경우에도 동일하다. 그래서, 내부함수에서 this를 사용할 때는 주의해야 한다.

// 내부함수의 this 바인딩 동작을 보여주는 예제 코드

// 전역변수 value 정의
var value = 100;

//myObject 객체 생성
var myObject = {
  value:1,
  func1: function() {
    this.value += 1;
    console.log("func1() called. this.value : " + this.value);

    //func2 내부함수
    func2 = function() {
      this.value += 1;
      console.log("func2() called. this.value : " + this.value);

      //func3 내부함수
      func3 = function() {
        this.value += 1;
        console.log("func3() called. this.value : " + this.value);
      }

      func3();     //func3() 내부 함수 호출
    }

    func2();       //func2() 내부 함수 호출
  }
};
myObject.func1();  //func1() 메서드 호출


//출력결과
func1() called - this.value : 2
func2() called - this.value : 101
func3() called - this.value : 102

출력결과를 보면, func2(), func3() 의 결과가 의도와는 다른 것을 볼 수 있다. myObject.func1() 을 호출하며 this는 myObject에 바인딩되었지만, 내부함수들의 this는 전역객체인 window에 매핑되었기 때문이다. 그림으로 보면 다음과 같다.

ch04-17

ch04-18

이런 현상은 다음 코드처럼 해결할 수 있다.

//내부 함수의 this 바인딩 문제를 해결한 예제 코드

var value = 100;

var myObject = {
  value: 1,
  func1: function() {
    var that = this;

    this.value += 1;
    console.log("func1() called. this.value : " + this.value);

    func2 = function() {
      that.value += 1;
      console.log("func2() called. this.value : " + that.value);
    
      func3 = function() {
        that.value += 1;
        console.log("func3() called. this.value : " + that.value);
      }
      func3();
    }
    func2();
  }
};

myObject.func1();

that은 this를 내부 함수에서 사용하기 위해 사용하는 관례적인 변수이다.

Top

4.4.2.3 생성자 함수를 호출할 때 this 바인딩

생성자 함수: 기존 함수에 new 연산자를 붙여서 호출 자바스크립트에서는 모든 함수를 생성자 함수로 사용할 수 있으므로, 생성자 함수로 정의하여 사용되는 함수의 경우 함수이름의 첫 문자를 대문자로 쓰기를 권장하고 있다.

생성자 함수가 동작하는 방식

생성자함수가 호출될 때, 동작하는 방식은 약간 다르다. 다음과 같은 순서로 동작하게 된다.

  1. 빈 객체 생성 및 this 바인딩

빈 객체가 생성되며, this로 바인딩 된다. 이 빈 객체는 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.

  1. this를 통한 프로퍼티 생성
  1. 생성된 객체 리턴

리턴문이 동작하는 방식은 경우에 따라 다르다. 일반적으로 리턴문이 없는 경우, this로 바인딩 된 새로 생성한 객체가 리턴된다. 하지만 일반함수를 호출할 때, 리턴값이 명시되어 있지 않으면 undefined가 리턴된다. 4.4.3 함수 리턴에서 자세히 알아본다.

//생성자 함수의 동작 방식

//Person() 생성자 함수
var Person = function( name );
  // 함수 코드 실행 전
  this.name = name;
  // 함수 리턴
}

//foo 객체 생성
var foo = new Person('foo');
console.log( foo.name );     //(출력) foo

ch04-20

객체 리터럴 방식과 생성자 함수를 통한 객체 생성 방식의 차이 객체 리터럴:

  • 정해진 형식으로 밖에 만들 수 없음
  • 프로토타입 객체(proto 프로퍼티)는 Object이다.

생성자 함수:

  • 인자를 받아 여러개의 객체를 다른 형식으로 만들 수 있음
  • 프로토타입 객체(proto 프로퍼티)는 생성자 함수의 prototype 프로퍼티가 된다. (위의 예제에서 Person)

생성자 함수를 new를 붙이지 않고 호출 할 경우

일반함수로 호출이된다. 일반함수는, this의 바인딩 방식이 다르기 때문에 window 전역 객체에 바인딩 된다.

//new를 붙이지 않고 생성자 함수 호출시의 오류

var qux = Person('qux', 20, man);
console.log( qux );          //(출력) undefined
console.log(window.name);    //(출력) qux
console.log(window.age);     //(출력) 20
console.log(window.gender);  //(출력) man

강제로 인스턴스 생성하기

생성자 함수를 일반 함수로 호출하는 실수를 피하려고 사용되는 패턴이 있다.

function A(arg) { if( !( this instance A ) ) return new A( arg ); this.value = arg ? arg : 0; } // 또는 다음과 같이 사용 한다. if( !( this instance arguments.callee ) ) ...


> arguments.callee가 바로 함수 이름을 나타내므로, 위의 코드와 아래의 코드는 같은 의미이다.

[Top](#_top)
<a name="ch04.4.2.4"/>
#### 4.4.2.4 call과 apply 메서드를 이용한 명시적인 this 바인딩

this를 특정 객체에 **명시적으로 바인딩** 시키는 방법도 있다.

1. apply(thisArg, argArray)
1. call(thisArg, arg1, arg2, arg3, ...)

apply와 call 메서드는 기능은 같으나, 인자를 넘겨받는 것에서 차이가 있을 뿐이다.

```javascript
// apply와 call의 명시적 this 바인딩

function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

var foo = {};  // 빈객체

Person.apply( foo, ['foo', 30, 'man'] );

Person.call( foo, 'foo', 30, 'man' );

위의 코드는 this를 foo 객체에 명시적으로 바인딩하게 된다.

apply 메서드를 활용하여 arguments 객체를 배열로 변환하여 사용하기.

function myFunction() { var args = Array.prototype.slice.apply( arguments ); // Array.prototype.slice() 메서드를 호출 하되, 이때 this는 arguments 객체로 바인딩하라. }


[Top](#_top)
<a name="ch04.4.3"/>
### 4.4.3 함수 리턴

[Top](#_top)
<a name="ch04.4.3.1"/>
#### 4.4.3.1 규칙 1) 일반 함수나 메서드는 리턴값을 지정하지 않을 경우, undefined 값이 리턴된다.

[Top](#_top)
<a name="ch04.4.3.2"/>
#### 4.4.3.2 규칙 2) 생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴된다.

생성자함수에서는 리턴값을 지정하지 않으면, this로 바인딩된 새로 생성된 객체가 리턴된다. 하지만 다음의 몇가지 예외사항이 있다.

- 명시적으로 다른 객체를 리턴할 경우
```javascript
function Person(name, age, gender){
  this.name = name;
  this.age = age;
  this.gender = gender;

  //명시적으로 다른 객체를 리턴
  return {name:'bar', age:20, gender: 'woman'};
}
var foo = new Person( 'foo', 30, 'man' );

이 경우, foo에는 bar, 20, woman 객체가 리턴된다.

  • 생성자 함수의 리턴값이 객체가 아닌 불린, 숫자, 문자열의 경우

이 경우, 리턴값을 무시하고 this로 바인딩 된 객체가 리턴된다.

Top

4.5 프로토타입 체이닝

### 4.5.1 프로토타입의 두 가지 의미
  • 자바스크립트에는 클래스 개념이 없다. 다만, 상속의 개념은 존재한다.
  • 상속의 개념의 부모 객체는 '프로토타입' 객체이다.
  • 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 암묵적 프로토타입 링크이다.
  • 암묵적 프로토타입 링크는 Prototype 프로퍼티에 저장되고, Prototype링크라고 불린다.

자바스크립트의 객체 생성 규칙 자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모객체로 설정하는 Prototype링크로 연결한다.

// Person 생성자 함수
function Person(name){
  this.name = name;
}

// foo 객체 생성
var foo = new Person('foo');
  • Person() 생성자 함수는 prototype 프로퍼티로 자신과 링크된 프로토타입 객체를 가리킨다.
  • Person() 생성자 함수로 생성된 foo 객체는 Person() 함수의 프로토타입 객체를 Prototype 링크로 연결한다.

ch04-23

ch04-24

Top

4.5.2 객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝

부모역할을 하는 프로토타입 객체의 프로퍼티에 접근하는 것을 가능하게 하는 것이 프로토타입 체이닝 이다.

객체 리터럴로 생성한 모든 객체는 Object()라는 내장 생성자 함수로 생성된 것이다. 따라서, Object()함수의 prototype 프로퍼티가 가리키는 Object.prototype 객체를 자신의 프로토타입 객체로 연결(prototype링크)한다.

Top

4.5.3 생성자 함수로 생성된 객체의 프로토타입 체이닝

객체 리터럴방식과 약간 다른 체이닝이 이루어지지만, 다음의 기본 원칙을 잘 지키고 있다.

자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수prototype프로퍼티가 가리키는 객체를 자신의 **프로토타입 객체(부모객체)**로 취급한다.

Person() 생성자 함수의 prototype 프로퍼티가 가리키는 Person.prototype 객체가 있고. 이 객체 또한 Object.prototype을 Prototype링크로 가지고 있다.

Top

4.5.4 프로토타입 체이닝의 종점

Object.prototype 객체가 결국 프로토타입 체이닝의 종점이다.

Top

4.5.5 기본 데이터 타입 확장

  • Number.prototype : 숫자를 사용하는 객체의 프로토타입
  • String.prototype : 문자열을 사용하는 객체의 프로토타입
  • Array.prototype : 배열을 사용하는 객체의 프로토타입
//String 기본타입에 메서드를 추가하여 사용할 수 있다.

String.prototype.testMethod = function() {
  console.log("This is the String.prototype.testMethod()");
};

var str = "This is test";
str.testMethod();

Top

4.5.6 프로토타입도 자바스크립트 객체다.

프로토타입 객체도 자바스크립트의 객체이므로, 일반 객체처럼 프로퍼티를 추가/삭제하는 것이 가능하다.

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

var foo = new Person('foo');

Person.prototype.sayHello = function(){
  console.log("hello");
}

foo.sayHello();   // hello

Top

4.5.7 프로토타입 메서드와 this 바인딩

프로토타입 메서드 내부에서 this를 사용한다면, 어디에 바인딩 될 것인가??

// 프로토타입 메서드와 this 바인딩

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

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

var foo = new Person('foo');

console.log(foo.getName());   //(출력) foo

// Person.prototype 객체에 name 프로퍼티 동적 추가
Person.prototype.name = 'person';

console.log(Person.prototype.getName());  //(출력) person

ch04-31

Top

4.5.8 디폴트 프로토타입은 다른 객체로 변경이 가능하다.

객체지향의 상속을 구현하는 데 사용된다.

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

console.log(Person.prototype.constructor);      //(출력) Person(name)

var foo = new Person('foo');
console.log(foo.country);                       //(출력) undefined

// 디폴트 프로토타입 객체 변경
Person.prototype = {
  country: 'Korea'
};
console.log( Person.prototype.constructor );    //(출력) Object();

var bar = new Person('bar');
console.log( foo.country );      //(출력) undefined
console.log( bar.country );      //(출력) Korea
console.log( foo.constructor );  //(출력) Person(name)
console.log( bar.constructor );  //(출력) Object()

ch04-32

Top

4.5.9 객체의 프로퍼티 읽기나 메서드를 실행할 때만 프로토타입 체이닝이 동작한다.

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

Person.prototype.country = 'Korea';

var foo = new Person('foo');
var bar = new Person('bar');
console.log(foo.country);   //(출력) Korea
console.log(bar.country);   //(출력) Korea
foo.country = 'USA';

console.log(foo.country);   //(출력) USA
console.log(bar.country);   //(출력) Korea

ch04-33 Top

⚠️ **GitHub.com Fallback** ⚠️