웹, HTML

자바스크립트 call, apply, bind 및 this 판정, 프로토타입, 클로저 이해

디버그정 2016. 3. 28. 03:37

// Function.prototype에 구현됨(모든 함수들의 원형객체. 참고로 Function.prototype 위에는 모든 객체의 조상이자 루트인 Object.prototype이 존재) 

// 참고로 프로토타입(원형) 방식으로 자바 스크립트 객체는 서로 계층을 형성하고 연결을 하고 있습니다.

// 모든 객체는 원형에 대한 내부링크로(크롬, 파폭, 오페라는 __proto__ 속성에 상위객체를 링크, 다른 브라우저는 각각의 방식으로 구현) 상위 객체를 참조하고 있습니다. 가장 최상위인 Object.prototype에서 윗방향으로의 링크가 끝나게 되죠.(파폭이나 크롬에서 alert(Object.prototype.__proto__)하면 null로 확인됨)

__proto__는 표준규칙이 아니고 다른 브라우저에서는 다르게 구현한 경우가 있기 때문에 테스트용으로만 쓰고 실제 작성시에는 쓰면 안됩니다.

// 그래서 사용자가 필요한 고유의 속성, 메소드만 추가하고 상위 객체들이 이미 구현한 부분은  링크(참조)로 해결되므로  이미 만들어진 객체를 가져다 쓴다고 표현합니다.

// bind의 경우 구버전 브라우저에는 구현이 안된 경우가 있다고 함. 이 경우는 bind를 직접 구현해주면 됨. 구현 방법은 검색하면 많이 나옴.

/* bind 구현 코드) 모질라(파이어폭스 만든 곳) 사이트에 구버전 브라우저의 경우 대비 구현해놨네요.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // native functions don't have a prototype
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}
*/

배열의 최상위 원형객체인 Array.prototype의 함수를 사용하여 arguments 인수 객체를 마치 배열처럼 인식하게 하여
(
arguments 인수객체는 배열겍체는 아니지만 구성이 배열(인덱스와 값이 쌍으로 존재)과 비슷해서 배열객체라고 페이크로 인식(call의 역할)시켜서 처리가능)
일단 인수부분에서 맨 앞 객체 부분을 잘라낸 인수부분만 남겼습니다.(남은 인수부분은 커링에 사용 / 아래 사용례 참조)

처음에 진짜 조심할게 Function.prototype.bind 실행시 this는 bind 앞의  함수객체입니다.

가령 aOb라는 객체가 존재하고 객체의 메소드가 aObj.print인 경우 aObj.print.bind 수행시 this는 얼핏보면 aObj일 것 같은데
그게 아니고 aObj.print라는 함수객체가 this 입니다. 

aObj의 메소드는  aObj.print일 뿐이고 aObj.print.bind는 aObj.print 함수객체의 프로퍼티(메소드)입니다.

(모든 함수들의 원형객체인 Function.prototype에 구현했으므로 계층구조상 함수객체들은 모두 가져다가 쓸 수 있습니다.)

참고로 쉽게 파악하는 방법으로 마지막 점 앞의 대상이 호출객체임. aObj.print는 점 앞의 aObj가 호출,  aObj.print.apply나  aObj.print.bind는

마지막 점 앞의 aObj.print 함수객체가 호출. 직접 코드를 alert으로 this를 찍어보시면 해당 함수의 내용이 나오는 것을 볼 수 있습니다.

 function A() {
this.print = function() {return "print";}
}
var aObj = new A();
aObj.print.mybind = function() {alert(this);} 
aObj.print.mybind();


fNOP 부분: 해당 함수가 객체를 찍어내는 생성자 함수로서 기능을 하는경우
이 생성자 함수로 생성될 객체들의 원형객체(만들어질 객체의 프로토타입 객체. 생성자함수.prototype === 객체.내부링크)를
생성시켜서 생성자 함수의 prototype프로퍼티에 연결시키는 이를테면 원래 함수와 그대로 똑같은 구조로 복사하고 있는 형태인 듯,
참고로 함수의 prototype 프로퍼티와 함수의 내부링크(크롬, 파폭에서 __proto__)를 구분하세요.
prototype 프로퍼티는 오로지 함수에만 존재하는 프로퍼티이고 이것의 기능은 함수가 생성자 역할을 하는 경우
그 만들어질 객체의 상위원형객체를 링크하고 있습니다.
생성자 함수로 생성하는 경우 기본적으로 객체- 부모역할 프로토토입 객체(원형객체) 2개가 존재하게 됩니다. 원형객체에는 보통 공통적인 프로퍼티나 메쏘드를 집어넣구요.
좀 특이한 구조이죠. 이를테면 보통 생성자 함수로 사용자 정의 객체 생성시
말단 객체(찍어낼 때마다 각각 존재) - 한단계 위 프로토타입 객체(1개 존재) - 최상위 Object.prototype 루트객체 이런 계층구조를 형성함.
참고로 함수가 정의될 시 아무 내용도 없는 빈 프로토타입 객체를 생성 후 프로토타입의 constructor 프로퍼티에 생성자 함수를 링크(등록, 참조)하고
생성자 함수객체의 prototype 속성에 그 프로토타입 객체를 링크(등록, 참조)합니다.(상호 참조)
(링크, 참조 이런 표현은 쉽게 말해 객체 주소를 넣는 것. 이 주소를 바탕으로 런타임시 여기저기 찾으러 다니겠죠.)
참고할만한 사항으로 constructor 속성은 new로 만들어지는 객체에 존재하는 프로퍼티는 아니고 함수정의시 생성되는 프로토타입 객체에 존재하는 프로퍼티입니다.
물론 원형객체가 new로 만들어지는 객체의 부모 계층이니 당연히 사용, 접근 가능합니다.
이렇게 함수에 prototype 속성이 존재하고 정의시마다 빈 원형객체가 자동 생성되는 구조는 생성자로서 기능을 할때만 필요하지만
자바 스크립트는 별도의 키워드를 준비하거나 분리를 안 해놔서 일반적으로 함수는 저런 구조가 됩니다.
다만 Function.prototype, alert, confirm 등 네이티브 함수들은(우리가 정의한 함수들은 문자열 해석후 기계어로 변환해서 수행하지만,
저런
바로 기계어로 박아놓하고 됨.) prototype 프로퍼티가 존재하지 않습니다.
사실상 저런 함수들은 생성자로 작동할 필요가 전혀 없는 함수들이므로 prototype 속성은 무의미하다 보겠습니다.
alert(alert.prototype); 혹은 alert(Function.prototype)로 검사해보면 undefined로 나옵니다.)

생성자함수 = function 정의;
생성자함수.prototyp.메소드 = function 정의; 혹은
생성자함수.prototye.공통변수 = 값;
보통 이런 구조가 많음. 이런식으로 설정하면 저 원형객체에 공통적으로 사용할 수 있는 메소드, 프로퍼티가 들어가게 됩니다.
공통으로 사용하는 메소드의 경우 이건 개별 객체마다 생성하면 메모리 낭비이기 때문에 프로토타입객체에 작성하는걸 권장한다고 합니다.
다른 C++ 등에서는 하나의 객체는 멤버변수들공간 + 멤버함수 구현된 곳의 주소(32비트 OS에서는 4바이트)만 할당하면 되는데
자바스크립트는 조금 다르죠. 메소드는 일반적으로 공통으로 사용하므로 사실상 이원화가 되겠죠.

객체를 찍어낼 때마다 생성자함수에 존재하는 prototyep 프로퍼티의 값을 생성된 객체의 내부링크에 그대로 집어넣어서 상위원형객체를 링크할 수 있게 되겠죠.
(한단계 위 원형객체 === 생성자함수.prototype === 객체.내부링크)
함수의 prototype 프로퍼티는 오로지 생성자 함수의 기능으로서 작동할 때만 사용하는 함수 특유의 속성이고(다른 일반객체에는 없고 함수객체만 존재)
함수의 내부링크(__proto__ 등으로 구현. 비표준, 각가 다름) 프로퍼티는 함수객체 자신의 상위 원형객체에 링크 담당 속성입니다.
알다시피 모든 함수들의 원형객체인 Function.prototype를 참조하고 있죠. 그래서 함수원형객체의 함수들인 call, apply, bind 등등을 맘대로 가져다 쓸수 있게 되죠.
(저런 내부링크는 모든 객체에 존재합니다. 최상위 루트객체 Object.prototype의 __proto__ 값은 null로 여기서 프로토타입 체인 탐색이 끝남)
해당 웹사이트 가서 생성자 함수에서 bind 사용부분을 읽어보니.
생성자 함수에서는 this값이 무엇으로 전달되든지 시스템이 이값을 무시하고, 새로 할당된 객체로 설정한다고 합니다.
그래서 bind로 this값을 특정객체로 바꾸어 전달하는것은 무의미합니다. 이건 개념적으로 new의 역할이 새 객체 생성이니 당연한 개념이라고 볼 수 있습니다.
혹시나해서 아무개 객체를 만들어서 bind로 조작 생성자 함수를 만들어서 new 수행하면 아무일 없는 듯이 새객체를 잘 생성했고, 아무개 객체 또한
그 위에 강제로 덮어씌워지거거나 수정되지 않았습니다.
따라bind로 특정객체를 this로 설정하여 생성자함수를 수정하는건 전혀 의미가 없고
오로지 고 때만 다.
function A(name, age) {
this.name = name;
this.age = age;
}

A.prototype.print = function () {alert(this.name + ' ' + this.age);};

var emptyObj = {}; // 빈 객체 생성

var Abound = A.bind(emptyObj, "태희");
//
var Abound = A.bind(null
, "태희"); // 네이티브로 bind가 구현된 경우 빈객체 할당 필요 없음.

var aObj = new Abound(35); // 됨.
생성자에서 this 값 강제 설정은 아무런 의미가 없으므로 네이티브 구현된 브라우저는 null 입력을 유효하게 받고 있죠.

다시 실행문에 들어가서 fNOP 부분을 살펴보면
fNOP = function() {};로 빈 함수객체를 만듭니다.
~
if (this.prototype) { // 현재 함수에서 프로퍼티 속성이 존재하면
// native functions don't have a prototype. 이전에 언급했다시피 네이티브 함수들은 해당 속성을 가지고 있지 않음
fNOP.prototype = this.prototype; // 일단 빈함수의 prototype 속성에 값을 넣음.
}
즉 대상함수(타겟함수)가 생성자로 작동할시 만들어질 객체들의 부모로 작동하는 프로토타입(원형)객체를
fNOP함수의
prototype 속성에 참조(링크)시켜 놓습니다.
fBound.prototype = new fNOP(); // fNOP을 생성자로 객체를 하나 만듭니다.
new로 빈 객체가 생성되고 이 빈 객체의 내부링크로는 위의 프로토타입 객체가 부모로서 연결되겠죠.
이 빈 객체를 fBound의 prototype 속성에 넣네요. 따라서 fBound를 생성자로 굴릴시
fNOP으로 생성한 빈객체가 프로토타입 객체가 됩니다. 빈객체는 상위객체로 타겟함수의 프로토타입객체를 링크하죠.
따라서
Bound를 생성자로 굴릴시 계층구조를 보면
fBound 생성자로 생성한 객체 - fNOP으로 생성한 빈 객체 - 타겟함수 생성자 작동시 프로토타입객체
이렇게 연결됩니다.
중간에 빈객체가 끼어있긴 하지만 아무튼간에 원래 타겟함수의 프로토타입객체에는 연결이 되고 있습니다.

this instanceof fNOP
? this
: oThis, 콘캣(슬라이스~)
이해가 간단한 두번째 콘캣 부분부터 살펴보면 concat은 아까 잘라낸 인수부분 뒤에 실행시의 인수들을 붙이는 것입니다.
현재 결과값 함수가 호출되어 내부함수 실행시 전달되는 인수들 집합소인 arguments를 배열취급해서(구조가 인덱스, 값 형태라서 가능) 슬라이스로 배열객체 생성 후
아까 바인드시 처리한 인수들이 들어간 배열객체 뒤에 붙이는 과정을 하고 있습니다.
apply는 배열로 인수들을 전달함을 잊지 마세요. 자바스크립트는 순서대로 매개변수에 인수 입력을 받으므로
아까 잘라낸 부분이 차근차근 먼저 적용이 됩니다. 참고로 인수 개수가 매개변수 개수들 초과하는 경우 그 초과하는 부분은 그냥 무시됩니다.
이런식으로 커링이 수행됩니다. 쉬운부분이라서 실습하면 바로 이해 감.


이제 첫번째 부분을 살펴보면 우선 this는 외부함수의 this가 아닙니다. 내부함수를 외부함수의 바깥에서 리턴값으로 받은후
그 리턴값인 함수 실행시의 this입니다. 조건문을 해석하면 fNOP의 인스턴스이면 this값을 그대로 사용하고
아니면 앞서 첫번째 인수로 전달한 객체를 this로 강제 설정해서 수행하라입니다.
이게 생성자로 작동할 때와 관련이 있다는 삘은 받을 겁니다.

먼저 instanceof는 앞의 객체가 뒤의 생성자함수의 인스턴스인가를 판단하는 비교문입니다. 기준은 프로토타입 객체입니다.
일반적으로 함수가 정의되면,,, 가령 A()라는 함수라고 치면. 정의시(함수 객체 생성시) 빈 프로토타입 객체를 하나 만들고

빈 프로토타입 객체의 consturctor 속성으로 A()함수를 등록하고
A.prototype에는 프로토타입 객체를 등록하게 합니다.(상호 참조)
참고로 생성자 함수는 개별객체의 프로퍼티로 존재하는게 아니라 프로토타입 객체의 constructor 프로퍼티에 등록됩니다.
그리고 instanceof 연산자는 주의할 점이 함수.프로토타입으로 판단하기 때문에 일부러 같게 하는 경우.
순히 런타임시 현재상태에서 함수.prototype에 무슨 값이 들어있는지만을 체크해서 비교하니 조심히 사용하세요.
(constructor을 일부러 같게 하여 사용하는 경우도 조심)
function A() {};
function B() {};
var a = new A();
var b = new B();
alert (b instanceof B); // 당연히 참
B.prototype = A.prototype; // 프로토타입을 한번 바꿔봄.
alert (b instanceof B); // 거짓이 되버림.
alert (a instanceof B); // 참이 되버림.
이점 살짝 유념할 것


instanceof 작동방식에서 주의할 점이
일단 우변은 고정으로 기준이 되는 값은 해당함수.prototype입니다.
좌변은 객체의 프로토타입 객체가 검사대상이 됩니다.(여기도 살짝 조심. 객체가 아니라
프로토타입 객체 즉 객체.__proto__부터 검사가 시작됩니다.)
개념적으로 객체의 프로토타입 객체가 생성자 함수와 일대일 대응을 하므로
(생성자함수에서는 prototype 속성으로 상대 등록, 프로토타입객체는 consructor로 상대 등록)
프로토타입 객체는 인스턴스의 표상이라고 불러도 무방하니 저걸 기준으로 삼았겟죠. 생성자 함수를 인스턴스 판단의 기준으로 삼을 수도 있겠지만
자바스크립트 규약으로 프로토타입 객체가 일치하는지 검사하라고 정해놨습니다.
생성자 함수는 따로 객체끼리 검사에 사용하는 듯 함. if (a객체.constructor === b객체.constructor) 이런 식으로 검사하면 됨.
규약을 읽어보니 생성자함수는 객체판단의 근거로 부족하다네요. 조금 후 서술.

좌변의 값이 우변과 일치하지 않으면 검사가 끝나는게 아니라.... 다시 좌변객체의 부모객체로 갑니다.(__proto__ 같은 내부링크로 연결됨)
이번에도 같은지 검사합니다. 또 안 같으면 또 부모객체로 가서 검사합니다. ...이런식으로 루트객체까지 올라갑니다.
그래서 참고로 alert(아무개 객체 instanceof Object)는 딱 한 경우만 제외하고 제외하고 모조리 참이 됩니다.
딱 한 경우는 루트객체겠죠. alert(Object.prototype i
nstanceof Object) // 거짓. 위에서 살짝 조심할 부분에서 객체의 프로토타입 객체(부모)부터 탐색입니다.
루트객체의 부모는 null이기 때문에
거짓이 되는 것입니다. 딱봐도 개념상 루트의 부모가 존재하는게 말이 안되므로 null로 설정하고 체인에서 탐색의 종착점이 됨.
주목할만한 점은 부모객체부터 검사하고 일치하지 않으면 조부모, 증조... 등등 쭉~ 상위객체들을 거슬러올라가면서 일치하는지 검사하는 점입니다.
따라서 부모-자식 구조 형성시
자식 instanceof 부모생성자함수는 항상 참이 됩니다.
참고로 ECMA스크립트 규약 서두 부분을 읽어보니 자바스크립트는 생성자는 객체의 표상으로 의미가 상당히 많이 떨어진다고 합니다.
바로 실시간 실행중에 객체나 프로토타입 객체에 속성을 추가하는게 가능하기 때문인데
(이를테면 A생성자에는 정의 안되었지만 aObj.프로퍼티 = 모모;하면 바로 멤버가 추가되고 A.prototype.메소드 = 모모; 이런 명령문은 너무 흔하죠.)
그래서 생성자는 결코 객체들의 대표 형태, 표상이 된다고 하기엔 많이 부족하게 됩니다.
이 때문에 생성자함수보다는 객체 공통으로 사용하는 프로퍼티, 메소드를 가진 프로토타입 객체가 객체를 표상하는 기준이 된다고 합니다.
instanceof가 프로토타입 객체 기준으로 설정되는 이유입니다.
다른 C++의 생성자 클래스처럼 객체가 클래스에 선언한대로의 딱 그 형태인 구조와 다르죠.

본문으로 돌아가서
fBound.prototype = new fNOP();으로 대입시켰기 때문에 fBound가 생성자로 작동시
fBound로 만든 객체들은 필연적으로 fNOP()으로 생성한 객체와 부모자식으로 연결될 수 밖에 없습니다.(__proto__같은 내부링크로 참조)

구체적으로 계층구조를 살펴보면
타겟함수가 native 함수처럼 프로토타입 속성이 존재하지 않는경우
fBound 생성자로 만든 객체 - fNOP 생성자로 만든 객체(빈객체) -
fNOP.prototype 객체(참고로 여기에 constructor 속성으로 fNOP()이 등록됨)
타겟함수가 프로토타입 속성이 존재하는 경우 fNOP.prototype = this.prototype; 했으므로(this는 타겟함수)
fBound 생성자로 만든 객체 - fNOP 생성자로 만든 객체(빈객체) - fNOP.prototype(=== 타겟함수.prototype. 참고로 여기 혹은 보다 상위객체에 constructor가 등록됨)
참고로 타겟함수로 만든 객체들의 constructor는 타겟함수()가 아닐 수도 있습니다. 왜냐면 앞에서처럼 상속구조이면 프로토타입 대입문이 사용되고
보다 상위 객체에 constructor가 존재하게 되겠죠. 결국 최상위 계층에 존재하는 constructor가 생성자 취급을 받겠죠.
constuctor 프로퍼티도 수정, 조작시 주의가 필요하겠죠.
아무튼 확실한 것은 저 계층구조에서 프로토타입 비교시 ....부모자식 계층이 형성되므로 참이 됩니다.
실제로 맞는지 하나하나 보면 우
변에서 일단 fNOP.prototype이 값 비교대상이고
좌변에서 (생성될 객체.__proto__)는 (fNop으로 생성한 빈객체)라서 아직 불일치. 이제 부모로 가서 찾습니다.
그 부모인 (
fNop으로 생성한 빈객체.__proto__)는 fNOP.prototyp과 같은 값이므로 참이 되겠죠.
(
참고로 타겟함수의 프로토타입이 존재하는 경우 이 값은 위에서 대입해서 일치시켰으니 타겟함수.prototype과 같겠죠)
그래서 생성자 함수로 작동할 경우 instanceof는 필연적으로 참이 될 수 밖에 없습니다.

한가지 의문점이 드는게 굳이 생성자인지 판단에서 this instanceof fNOP로 했느냐인데
쉽게 this instanceof fBound로 하면 당연히 생성자이겠거니 생각되고 파악도 바로바로 되는데,
실제로 수정해서 간단한 테스트로는 문제없이 작동하더라구요.
이경우 생성자함수 내에서
this instanceof fBound 비교문은 사실상 동의어 비교이겠죠.

그리고 또 한가지 의문점이 fNOP를 둬서 빈객체를 생성하고 이를 통해 링크를 하는게 조금 의문이 들죠.
계층구조에서 빈객체에 중간에 끼워져 있고 이건 단순히 상위단계로 거쳐가는 역할만 하고 있음.
(파폭 디버거로 실제로 확인해봄. 원래 타겟함수로 생성한 객체에 비해 빈객체가 중간에 끼워져 있음.)
전자: 말단 객체 - 프로토타입 객체 - 루트객체
후자: 말단 객체 - 빈객체 - 프로토타입 객체 - 루트객체
if (this.prototype) {
fBound.prototype = this.prototype;
}

이런 식으로 바로 대입해서 링크하고 생성자 판단부분은 앞의 직관적인 this instanceof fBound으로 수정하고 fNOP 없애버려도 잘 되긴 하더라구요.
fBound는 새로 조작한 함수, 생성자인데 프로토타입객체를 기존 타겟함수와 일치시키면 안된다는 무슨 새술은 새부대에 규칙 그런 의미인가 싶기도 하고...?
나중에 알게될 날이 올지도.^^;;;

참고로 생성자 함수내에서 this값을 엉뚱한 값으로 조작하면 제대로 생성도 안되고 엉망이 됩니다. 위 조건에서 반드시 this 그대로 유지해야 제대로 실행됨.
function A() {
this = xObj; // 아무 외부객체를 this로 시험삼아 설정. 제대로 실행이 안됨
this.name = name;
}
유념할게 앞에서 말한 bind로 조작한 생성자함수 실행시 시스템이 알아서 this로 설정할 전달객체를 무시해주는 경우와 구별하세요.
저렇게 생성자 함수 내부에서 직접 this값을 조작하는 코드를 작성하면 조작한대로 변하게 됩니다.

생성자가 아닌 일반함수으 형태로 호출한 경우 this를 oThis값으로 강제 설정하여 수행됩니다.
사실상 실무적으로 쓰이는건 이런 일반함수형태일건데 이론 부분은 생성자 함수일때의 부분이 훨씬 많네요.

특이한게
var a = 모모,
b = 아무개,
c = function () {...};
이렇게 쓰는게 좀 낯설죠 . 보통 다른 언어에서는 한줄에 하나의 명령문만 쓰고 ,쉼표 써서 문장 연결하는걸 매우 싫어하는데.
자바스크립트에서는 저렇게 자주 쓰더라구요. 문자열을 실시간으로 해석해서 그런가...
이를테면 다음 줄 넘어가서 var라는 문자열 판단하는 것 보다 쉼표 하나를 파악하는게 빠르니 시간을 줄이는 의미가 있는것 같음ㅎㅎ;;;

이후 내용을 살펴보면 fBound라는

내부함수를 정의하고 그 함수를 리턴하죠.

내부함수에서는 this값을 바꿔주는 apply를 사용하고 있고, bind시 전달되었던 인수들과 나중에 리턴함수 실행시 전달되는 인수들을 함치는 작업을 하죠.(커링부분 / 아래 사용례 참조)

주의할게 내부함수객체를 리턴값으로 넘겼으므로 리턴값함수 실행시 수행되는 부분은 저 내부함수 부분에 한정되게  됩니다. 

처음 줄부터 다시 쭉~ 실행하는 게 아님을 유의하세요.(bind 함수를 실행하는 것과 bind의 결과값인 함수를 실행하는 것을 구분하세요.)


한가지 더 유념할게 oThis, aArgs, fToBind, fNop, fBound이 매개변수(함수에서 인수를 받는 변수), 지역변수임에도 불구하고

bind함수가 수행된 후에도 파괴(정리, 해제)되지 않고 여전히 유효하게 남아 있다는 점입니다.

여기서 자바스크립트 클로저라는 특이한 개념이 나오는데
- 내부함수를 밖에서 사용할 수 있게 함수가 리턴되고(return fBound;)
- 내부함수(fBound)에서 사용하는 외부함수의 변수(oThis,  aArgs,
fToBind, fNop, fBound)가 존재하는 경우

외부함수 수행 끝난 후에도 변수들을 파괴(정리, 해제)시키지 않고 남겨 놓습니다. 이러한 내부함수를 클로저라고 합니다.
(이것저것 더 찾아보니 저런 구조, 파괴되지 않는 범위, 영역을 일컫기도 한다네요.)

이때문에 바인드 수행시 사용되는 매개변수나 지역변수들이 계속 유효하게 남아서 내부함수에서 사용가능합니다.
스코프 체인이라는 개념으로 이를 구현하는데, 일단 함수정의시...
(실행시가 아닌 정의시임을 유념하세요. 참고로 정의시는 함수객체가 메모리에 생성되는 시기이고 실행시는 함수로서 호출되어 내용을 실행하는것)
함수의 [[Scope]] 프로퍼티에 현재 실행중인 함수(내부함수 관점에서 부모)의 변수객체(~ 최상위 전역 변수객체까지 리스트 식으로 연결됨)를 등록해놓습니다.
(이건 유저에 노출되지 않아서 직접 유저가 스크립트 작성해서 접근하거나 수정할 수 없습니다.
파이어폭스 디버거상에는 <Closure>라고 표시를 해주긴 하네요. 범위별 변수객체들이 차근차근 들어가 있음을 볼 수 있습니다. )

참고로 자바스크립트는 실행중에 어떤 함수 진입시
자신의 범위(자바스크립트는 단순 { }블록 범위가 아닌 함수범위 function () { }로 범위가 구분됩니다.)를 한번 쭉~ 스캐닝한 다음에
실행에 필요한 제반 정보(매개변수, 인수, 지역변수, this값 등등... 이걸 실행 문맥이라고 부름. execution context)를 모두 세팅 후
실제 수행에 들어갑니다. 현재 실행중인 함수의 지역범위의 변수객체가 이제 변수 찾기에 사용되는 스코프 체인의 선두에 위치하게 됩니다.
자기지역범위에서 못찾으면 부모의 변수객체에서 찾고 못찾으면 또 위로 올라고 이런 식으로 최상위의 전역 변수객체까지 거슬러 올라가서 찾습니다.
이런식으로 연결된 구조를 스코프 체인이라고 함. 참고로 각각의 변수객체는 링크드 리스트로 연결되었다고 하네요.
(주소와 데이터가 합쳐진 방식으로 현재노드에서 못 찾으면 다음 노드 위치가 적힌 주소로 가서 찾는 방식.)

내부함수(자식함수)가 존재하면 정의시에(함수객체 생성시) 이 변수객체를 자식함수의 [[scope]] 프로퍼티에 등록합니다.
실행시가 아니라 함수 정의시에 등록하므로 자식함수가 부모함수 내에서 실행 안되고 나중에 저 부모함수 밖에서 호출되어도
함수객체가 만들어지는 정의시에 [[scope]]에 등록했으므로 부모함수의 변수객체에 접근할 수 있게 되죠.
(물론 시스템이 저런 경우 부모의 변수들을 절대 파괴, 해제시키지 말아야겠죠.)

클로저의 이 같은 독특한 특성 때문에  private 키워드가 없는 자바스크립트에서 이를 구현하기도 하고 독립적인 모듈구조 작성시 이용하는 등

(저 외부함수 밖에서는 외부함수에서 정의한 지역변수들을 전혀 접근할 수 없고, 내부함수인 클로저만 접근, 수정 가능하게 되죠.)
여러 기능을 한다고 합니다. 키워드를 제공하지 않으니 사용자들이 직접 개발한 듯,,,

함수 안에 함수 넣고 거기에 또 함수 넣고, 리턴값 역시 함수가 많죠. 이런 구조가 너무 빈번해서 함수 스크립트로 불러도 무방할 듯
자바스크립트 무슨 사파기술 같은게 많습니다. 프로그래밍하시는 분들은 절대 자바 스크립트부터 배우게 하면 안될 것 같습니다.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JavaScript Study</title>
</head>
<body>




<!-- <script type="text/javascript" src="test.js"></script> -->
<!-- 스크립트는 바디 아랫부분에 놓는게 좋다고 함. 일단 화면부터 보여지는게 고객들을 조금이라도 붙잡는다고 함. ^^ -->
<script>

// 객체 리터럴로 단독객체 생성
var s = {
name: "수지",
age: 22,
print: function(aStr, bStr) {document.write(this.name + '(' + this.age + ") " + aStr + ' ' + bStr + "<br />");}
};

s.print("매우", "청순");

// 객체 리터럴로 단독객체 생성
var t = {name: "태희", age: 35};

s.print.call(t, "짱", "이쁨"); // this를 t객체로 설정해 사용 가능.
s.print.apply(t, ["짱", "이쁨"]); // 위는 인수를 각각 전달, 이것은 배열 하나로 전달함
s.print.bind(t)("짱", "이쁨"); // bind는 조작된 함수가 리턴되므로 바로 (인수...) 붙여 호출 가능
var newFunc = s.print.bind(t); // 위를 풀어서 쓴 표현
newFunc("짱", "이쁨");
s.print.bind(t, "엄청")("우아"); // bind는 객체 외에 인수를 더 줄 경우 이 인수들은 고정 기능을 함. 참고로 인수를 특정 값으로 고정하는 걸 currying(커링)이라고 함
s.print.bind(t, "하하", "호호")();
</script>

</body>

</html>


/* call이나 apply는 표현식이 쉽게 읽히지만 bind 표현식은 조금 생소하죠. 자꾸 봐서 눈에 익히도록 하는 수 밖에요.
A.prototype.print = function() { // 객체 메소드
실행코드 ...
var test = function() {
실행코드 ...

}.bind(this); // 바인드 앞의 내부함수는 메소드 내부에서 사용하는 함수일 뿐이지 객체 메소드가 아니므로 bind(여기는 내부함수 영역이 아닌 메소드 영역이므로 this는 사용자 객체)하지 않으면 내부함수 안에서 this는 전역객체 window가 된다.
};
위처럼 함수 선언식 뒤에 바로 .bind 붙여서 처리하기도 하더라구요. 자바스크립트 이론부분은 그냥 함수의 떡칠이네요.;;; 함수 속의 함수 찾기
bind 개념을 기억할 때 아래처럼 기억하면 좋습니다.
bind 문자열 앞의 함수가 그 대상이고 / bind의 첫번째 인수인 객체로 this가 강제 설정되고 / 조작된 함수가 리턴된다.
bind에서 두번째 인수 이상 설정한 경우 고정인수를 가진 커링 기능도 수행한다.
*/


//------------- 결과 -------------//
수지(22) 매우 청순
태희(35) 짱 이쁨
태희(35) 짱 이쁨
태희(35) 짱 이쁨
태희(35) 짱 이쁨
태희(35) 엄청 우아
태희(35) 하하 호호



========================== bind를 이벤트 콜백함수에 적용 및 this 판정 알아보기 ==========================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test</title>
</head>
<body>



<!-- <script type="text/javascript" src="test.js"></script> -->

<!-- 스크립트는 바디 아랫부분에 놓는게 좋다고 함. 일단 화면부터 보여지는게 고객들을 조금이라도 붙잡는다고 함. ^^ -->

<script>


function addStr(objTextArea, strAdd) {
objTextArea.value += strAdd;
}

var btn = document.createElement("button");
var btnText = document.createTextNode("버튼");
btn.appendChild(btnText);
document.body.appendChild(btn);

document.body.appendChild(document.createElement("br"));

var ta = document.createElement("textarea");
ta.cols = 60;
ta.rows = 20;
document.body.appendChild(ta);

// 객체 리터럴로 단독객체 생성
var s = {
name: "수지",
status: "청순",
print: function() {addStr(ta, this + ' ' + this.name + ' ' + this.status + '\n');},
toString: function() {return "[s객체]";} // this값 확인시 사용
};

// 객체 리터럴로 단독객체 생성
var t = {
name: "태희",
status: "이쁨",
toString: function() {return "[t객체]";} // this값 확인시 사용
};

// addEventListener로 등록하면 onclick과 다르게 여러개 등록할 수 있고 이벤트 발생시 등록된 콜백함수 모두 실행됨.
// 주의할게 btn.addEventListener의 두번째 인수 s.print는 함수를 인수로 받는다는 의미이지 s.print(); 실행문이 아님을 유의함.
// 참고로 var aFunc = s.print; 하면 aFunc 실행시 더이상 this는 s객체가 아니게 됩니다.(자바스크립트 this 판정규칙 중 하나)
// 자바스크립트 this 판정 규칙:
// - 일반적으로 this 디폴트값은 전역객체(웹브라우저에서는 window / node.js에서는 global)이고 예외적인 경우만 기억하면 됨.
// - 객체 메소드 함수, 객체 생성자 함수인 경우 객체(만들어질 객체)가 this값을 가짐.
// (안 만들어졌는데 어떻게 지정하냐고 의아한 분들에게 this는 객체가 생성될 메모리 영역을 의미하는 것으로 인식하면 됨.)
// - call, apply, bind로 this를 임의로 설정할 수 있다.
// - 주의) 변수로 객체 메소드를 받는 경우 그 변수함수 실행시 더이상 this는 해당객체가 아니게 됨.
// - 주의) 객체 메소드 안에 존재하는 내부함수에서 this는 해당 객체가 아님.(내부함수가 객체의 메소드는 아니라는 점 유의)
// 참고로 아래 리스너로 이벤트 등록하면 "btn.클릭이벤트함수 = 등록함수;"로 저장됨.
// 이벤트 발생시 결국 btn 메소드인 btn.클릭이벤트함수가 실행되므로 btn이 this값이 됨.(this가 전역객체 window가 아니라서 의아한 분들 참고)
btn.addEventListener("click", s.print); // 클릭 이벤트 발생시 this가 어떤 값인지 확인
btn.addEventListener("click", s.print.bind(s)); // this를 무조건 s객체로 설정하는 함수 등록
btn.addEventListener("click", s.print.bind(t)); // this를 무조건 t객체로 설정하는 함수 등록

// 다른 매우 쉬운 방법으로 빈 함수 하나 만들어서 그 안에서 s.printf();를 직접 실행하면 됨.
// s.printf();는 s객체의 객체 메소드 직접 실행이므로 this는 s객체임.
// 이와 같이 구성시 함수에 들어가서 다시 함수를 실행하므로(2단계를 거치게 되므로) 함수 진입 비용이 하나 더 생겨서
// 이론적으로는 조금 느리다고 볼 수 있음. 이런게 천개 정도 있으면 느릴 수도.^^;
btn.addEventListener("click", function() {s.print();});
btn.addEventListener("click", function() {s.print.call(t);});
btn.addEventListener("click", function() {s.print.bind(t)();});
</script>

</body>

</html>


//------------- 결과 -------------//
[object HTMLButtonElement] undefined
[s객체] 수지 청순
[t객체] 태희 이쁨
[s객체] 수지 청순
[t객체] 태희 이쁨
[t객체] 태희 이쁨