2012. 4. 13. 15:31

Javascript에서 Scope (3) - this, prototype, new

#1. This는 Evalute되는 순간이 중요

각각의 경우에 aa값이 어떻게 출력될 지 예상 해보세요.

var aa = 10;

function check1() { this.aa.print("check1"); };

var obj = {

    thisref: this,

    aa: 20,

    check1: check1,

    check2: function() { this.aa.print("obj.check2"); },

    innerObj: {

        thisref: this,

        aa: 30,

        check1: check1,

        check3: function() { this.aa.print("obj.innerObj.check3"); }

        }

    };

 

check1();

obj.check2();

obj.check1();

obj.innerObj.check3();

obj.innerObj.check1();

(obj.thisref == window).print("obj.thisref == window");

check1: 10
> obj.check2: 20
check1: 20
> obj.innerObj.check3: 30
check1: 30
> obj.thisref == window: true

 this는 Object내의 Method로 된 함수 내에서 현재 Object를 참조하는 역할을 합니다.
this는 함수가 어디서 선언 되었느냐 보다는 함수가 어느 Object에서 Evaluation 되었느냐가 중요하며, Evaluation되는 순간의 현재 Object를 참조 합니다.

 아무런 한정자가 없는 함수나 변수는 가장 최상위 Object인 window Object의 하위 Node에 속하게 됩니다. 따라서 check1()으로 실행될 때는 마치 window.check1()으로 호출되는 것과 같은 효과를 가지게 되며 this.aa는 window.aa가 되어서 10의 값을 가집니다.
 obj.check2()와 obj.innerObj.check3() 경우의 this는 현재 자신의 Object를 참조 합니다.
 obj.check1()과 obj.innerObj.check1()의 경우가 약간 혼동을 주는 케이스 입니다. 분명 이 함수들은 동일한 Context를 가지는 check1을 호출하게 되지만 Evalutation되는 순간 참조하는 Object가 각각 다르기 때문에 다른 this값을 가지게 됩니다. ( 만약 this.aa가 아닌 aa라면 check1을 호출하는 세 함수는 같은 값(10)을 가집니다. )
 경우에 따라서는 임의로 참조하는 Object를 변경하고 싶을 때가 있습니다. 이럴 때는 call()이나 apply()를 사용하면 됩니다.


#2. new는 Function의 prototype으로 Object를 만든다

Javascript에서는 Class를 만드는데 prototype과 new를 제공합니다. new operator를 이용하면, Function type의 변수를 가지고 Object를 생성할 수 있습니다. 이때, 생성되는 Object는 Function Object의 prototype property의 값을 기본값으로 가지고 있습니다.

var aa = 10;

var check = function() { this.aa.print("check()"); };

var func = function() { this.check(); }

func.aa = 20;

func.check = function () { this.aa.print("func.check()"); };

func.prototype.aa = 30;

func.prototype.check = function () { 
                          this.aa.print("func.prototype.check()"); };

 

func();

func.aa.print("func.aa");

func.check();

var newFunc = new func();

newFunc.aa.print("(new func).aa");

newFunc.check();

> check(): 10
> func.aa: 20
> func.check(): 20
> func.prototype.check(): 30
(new func).aa: 30
> func.prototype.check(): 30

헷갈리지 말아야 할 부분은 new operator는 prototype 부분을 기반으로 하여서 Object를 만들어 준다는 것입니다. Function type도 Object type에 기반하기 때문에 다른 값들을 추가할 수 있는데 이는 new 로 생성된 object와는 아무런 연관이 없습니다.



#3. 참조가 바뀌는 전환점

prototype으로 지정된 값들은 여러 개의 Object가 생성이 되었을 때, 같은 값은 공유하는 것일까요? 각각 다른 값을 가지고 있는 것 일까요?

var func = function(name) { this.name = name; }

func.prototype.aa = 10;

func.prototype.check = function () { this.aa.print(this.name); };

 

newFunc1 = new func("o1");

newFunc2 = new func("o2");

func.prototype.aa = 20;

newFunc1.check();

newFunc2.check();

newFunc1.aa = 30;

func.prototype.aa = 40;

newFunc1.check();

newFunc2.check();

> o1: 20
> o2: 20
> o1: 30
> o2: 40

위의 결과 처럼 새로 생성한 Object에서 물려받은 값을 변경하지 않을 때에는 원래의 prototype의 값을 참조하다가 생성한 Object에서 값을 변경하면 이 참조가 깨지게 됩니다. 이는 변수 뿐만 아니라 함수도 적용되는 것이므로 객체의 상속이나 Polymorphism을 구현할 때 알고 있으면 좋습니다.



#4. this를 바꾸는 Call과 Apply

Call()과 Apply()는 파라미터로 Array를 받는지 여부만 차이가 있고 기능은 같은 함수 입니다. 이 함수들은 호출되는 순간의 this를 바꾸는데 사용합니다.

function check(str) { this.aa.print(str); };
var
 obj = {

    aa: 10,

    check: check

    };

var func = function() { this.aa = 20; };

func.prototype.check1 = function(str) { obj.check(str); };

func.prototype.check2 = obj.check;

 

var newFunc = new func();

newFunc.check1("indirect");

newFunc.check2("direct");

newFunc.check2.call(obj"call");

check.apply(newFunc, ["apply"]);

> indirect: 10
> direct: 20
> call: 10
> apply: 20