2015년 3월 1일 일요일

javascript closer

클로저 정리

오브젝트에 종속된 속성들의 이름 해석

속성 이름에는 다른 오브젝트의 포인터 또는 primitive 타입을 할당 할수 있다.(undefined 도 primitive type)
var objectRef = new Object(); //일반적인 오브젝트 생성
objectRef.testNumber = 5;
//or
objectRef['testNumber'] = 5;
이름이 testNumber인 속성을 생성 , 속성 이름을 생성하고 나서 해당 속성에 값을 재 할당할 경우에는 존재하는 속성 값을 오버라이트 한다.
objectRef.testNumber = 8;
//or
objectRef['testNumber'] = 8;

오브젝트에서 값 읽기

오브젝트에 값을 읽을려고 할때는 prototypes도 고려해야한다. 만약 속성 접근자가 사용한 속성 이름을 오브젝트가 가지고 있다면 해당 속성 값을 리턴한다.
objectRef.testNumber = 8;
var val = objectRef.testNumber;
  • 오브젝트에 속성이름에 값을 할당하고
  • 오브젝트에서 해당 속성 이름으로 값을 읽어와
  • val 이라는 변수에 할당
    오브젝트는 prototype을 가질수 있다 . 해당 prototype은 오브젝트 이기 때문에 해당 prototype은 또다른 prototype을 가질수있다. 위의 식이 반복되어 prototype chain을 생성한다. prototype chain의 마지막 오브젝트는 prototype 으로 null값을 같는다.
    function MyObj1(num){
      this.testNumnber = num;
    }
    //MyObj1.prototype = new Object(); 프로토타입을 할당하지 않으면 디폴트로 할당됨
    function MyObj2(str){
      this.testStr = str;
    }
    MyObj2.prototype = new MyObj1(8);
    var obj = new MyObj2('test');
    
    위의 식을 보면 MyObj1을 new 로 생성해 인스턴스를 MyObj2의 프로토타입에 할당한다 그후 MyObj2를 new 로 생성해 obj에 할당한다.
  • obj 변수에 할당된 인스턴스는 프로토 타입 체인을 가지고 있다. (MyObj1 -> Object -> null)
  • var obj == MyObj2 의 인스턴스이며 MyObj2.prototype == MyObj1 -> Object -> null 과 같다.
    var val = obj.testStr;
    
    위와 같이 오브젝트의 값을 읽으려 할때 우선적으로 인스턴스의 값을 읽고 없으면 프로토타입 체인을 대상으로 읽기를 시도한다.위의 경우에서 testStr이 인스턴스에 존재함으로 ‘test’가 리턴된다 하지만 없으면 어떻게 될까?
    var val = obj.testNum;
    
    최초 인스턴스에 접근하지만 속성이 없음으로 prototype 체인을 확인한다. (javascript interpreter 가 instacne에서 값을 찾아 undefined가 리턴되야될 상황이라면 해당 instance 의 prototype chian 을 조사한다.) MyObj1이 해당 속성을 가지고 있음으로 8을 리턴한다.
    var val = obj.toString
    
    MyObj2 instacne가 값이 없음으로 prototype chain을 조사한다. MyObj1 이 값이 없음으로 MyObj1의 prototype chain을 조사한다 Object가 toString 속성이름을 가지고 있음으로 해당값을 리턴한다.(함수도 이런맥락에서는 속성값(오브젝트)으로 볼수있다.)
    var val = obj.isExist
    
    인스턴스와 모든 프로토 타입 체인을 조사해도 해당 속성 이름이 존재하지 않음으로 undefined를 리턴한다.
  • 위의 결과를 종합해 보자
  • 속성이름을 읽을때는 첫번째로 발견하는값을 리턴한다(읽는 순서 1.인스턴스, 2.프로토타입 체인)
  • 속성에 값을 할당할때는 해당 인스턴스에 할당된다(없으면 생성,있으면 재할당)
    MyObj2.testNumber = 100;
    
    위의 경우 어떻게 될까?
  • 위의 공식을 따르면 값을 할당함으로 MyObj2 인스턴스에 testNumber라는 속성을 만들고 100이라는 값을 할당한다. 이때
  • MyObj2의 프로토타입인 MyObj1의 인스턴스의 testNumber는 어떻게 될까? 당연이 아무런 일도 일어 나지 않는다.
  • 다시 MyObj2에 접근하면 읽는 순서에따라 인스턴스를 먼저 읽기 때문에 100 이 리턴된다.(해당속성이 mask되었다는 표현을쓴다.)

식별자 해석, 실행 환경, 스코프 체인(indetifier resolution, execution contexts and scope chain)

  1. 실행 환경 객체
    ECMA 스펙에서는 추상적으로 정의되어 있고 구현 명세가 있지 않다. execution context는 특정 속성들과 그 속성들이 정의된 구조를 가지고 있으면 됨으로 자바스크립의 object로도 구현될수 있다.
    모든 자바스크립트의 코드는 실행 환경 안에서 실행된다. 글로벌 코드의 경우 글로벌 실행 환경에서 실행되며 모든 함수 호출은 각각의 실행 환경을 가지고 실행된다. eval 의 경우는 조금 다른 실행 환경을 같지만 이문서에서는 다루지 않는다.
    함수가 호출되면 해당 함수의 실행 환경에서 실행이 된다 그 후 또 다른 펑션을 호출하면(재귀도 동일) 새로운 실행 환경이 형성되고 좀전의 실행 환경 위에 쌓고 거기 에서 실행한다. 리턴 되면 이전의 실행 환경에서 실행 된다. (스택구조이다.)
    그렇며 어떻게 실행 환경이 어떤 순서로 형성되는지 알아보자
    • 1.Actiavation 객체를 생성한다. 해당 객체는 속성값을 가지는 오브젝트이다. 하지만 prototype을 가지지 않으며 자바스크립트 코드에서 접근 할수없다.
    • 2.arguments 오브젝트를 생성한다. (어래이 비슷한 객체이다 *어레이는 아니다. 함수의 파라미터들과 callee를가지고 있다.))그후 Activation.arguments 속성에 생성한 arguments를 할당한다.
      1. 실행환경에 스코프를 할당한다. 스코프는 객체로 이루어진 리스트(체인)이다. (모든 함수 객체는 내부적으로 [[scope]] 속성을 가지고 있다) 호출된 함수의 [[scope]]속성 앞에 Activation object 를 더한 후 실행 환경에 스코프를 할당한다.
      1. variable instantiation 이라는 단계가 실행된다. Activation 오브젝트가 Variable object로 동시에 사용된다. 해당 단계에서는 함수의 모든 파라미터들에 대해 Varaible object에 속성을 생성하고 만약 값이 넘어왔다면 할당한다.(없다면 undefined로 할당), 내부에 정의된 함수 정의의 이름을 Variable object에 만들고 해당 함수를 해당 속성에 할당한다. 로컬 변수들을 Varaible object의 속성으로 이름을 할당한다.(이떄 값은 undefined임, 실제 값은 함수가 실행될때 할당됨)
      • Ativation object 와 variable object는 같기 때문에 arguments 속성을 로컬 변수처럼 사용 할수 있다.
      1. this 키워드에 값을 할당한다 만약 이미 할당된 값이 오브젝트라면 해당 오브젝트를 사용하고 아니라면 글로벌 오브젝트를 사용한다.
    글로벌 오브젝트의 경우 파라미터가 없음으로 arguments속성이 없으며 scope의 체인은 글로벌오브젝트 객체 하나로 구성되어져 있다. 그후 변수 초기화 단계를 하는대 이때 글로벌에 정의된 함수들이 모두 글로벌 함수가 된다. 글로벌에 정의된 변수도 마찬 가지이다.
  2. scope chain and [[scope]]
    실행 되는 함수의 [[scope]] 속성앞에 Activation/Variable 객체를 추가해서 스코프 체인을 만들고
    실행 환경의 스코프 속성에 위의 스코프 체인을 할당한다.
    ECMA 스펙에서는 함수를 오브젝트로 정의하며 함수는 1. 정의된 함수는 변수 초기화 단계에서 생성된고 2. 함수 표현식은 실행될때 생성된다 3. 또는 함수 생성자를 호출해 생성할수도 있다.(생성자로 형성된 함수는 [[scope]]속성에 글로벌 오브젝트만 할당되어 있다.)
    함수 정의, 또는 함수 표현식으로 생성되는 함수들은 해당 함수를 생성한 실행 환경 객체의 스코프 속성의 값을 [[scope]]에 할당받는다.(재귀적 정의라는걸 알 수 있다.)
    글로벌에서 정의된 함수를 보자
     function exampleFunction(a1){
         //code
     }
    
    위의 함수는 글로벌 실행 환경 객체의 변수 초기화 단계에서 생성된다.(글로벌 실행 환경 객체는 Activation 단계가 없으며 variable object를 글로벌 실행 환경 객체와 동일한 오브젝트를 사용한다.) 글로벌 실행 환경의 스코프 속성은 글로벌 객체로 만 구성되어 있다.
    즉 exampleFunction 의 [[scope]]깂은 글로벌 객체 하나이다.(실행 환경 객체의 스코프 체인을 함수의 [[scope]]속성에 할당함으로)
     var exmpleFunctionRef = function(){
         //code
     }
    
    위의 식의 경우 함수 표현식임으로 글로벌 환경 객체의 변수 초기화 단계에서 실행되지 않는다. 하지만 함수의 생성이 글로벌 실행 위에서 실행됨으로 [[scope]] 값으로 글로벌 객체를 같는다.
    자 이번에는 재귀적 정의에의해서 할당되는 모습을 확인해보자
     function exampleOuterFunction(a1){
         function exmpaleInnerFunction(){};
     }
     exmpaleOuterFunction(999);
    
    1.exampleOuterFunction 의 [[scope]]속성은 글로벌 오브젝트이다(글로벌 실행 환경의 스코프값)
    1. 이제 글로벌 실행 환경에서 exampleOuterFunction(999)을 호출하면 새로운 실행 환경이 생성된다 이때 실행 환경의 스코프는 방금 생성된 Activation/Variable object + 함수의 [[scope]] 체인이 할당된다.
      도식화 한다면 아래의 그림과 같다. 번호는 할당되는 순서이다.
      @@@@@@스택(exmpaleOuterFunction)@@@@@@@@
      신규 실행환경(== actiavtion/variable) = {
       scope : 3.activation/variable -> exmpaleOuterFunction.[[scope]]
       a1:999
       exmpaleInnerFunction:{
           4.[[scope]] : activation/variable -> exmpaleOuterFunction.[[scope]]
       }
      }
      @@@@@@스택(global)@@@@@@@@
      글로벌 실행 환경( == variable) = {
       scope : 1.글로벌 오브젝트
       exmpaleOuterFunction:{
           2.[[scope]] : 글로벌 오브젝트
       }
      }
      @@@@@@스택@@@@@@@@
      
      with 문의 경우 해당 블럭안에서는 실행환경의 스코프 앞부분에 해당 오브젝트를 붙인다 그후 불락이 끝나면 오브젝트를 없앤다. 만약 함수 표현식이 with {}안에서 실행된다면 조금 다른 [[scope]]속성을 가지게 된다.
      var y = {x:5}; // object literal with an - x - property
      function exampleFuncWith(){
       var z;
       /* Add the object referred to by the global variable - y - to the
          front of he scope chain:-
       */
       with(y){
           /* evaluate a function expression to create a function object
              and assign a reference to that function object to the local
              variable - z - :-
           */
           z = function(){
               ... // inner function expression body;
           }
       }
       z()
       ... 
      }
      /* execute the - exampleFuncWith - function:- */
      exampleFuncWith();
      
      위에서 z는 with문 밖에서 실행되지만 이미 생성될때 [[scope]] {x:5} -> actiavation/variable -> 글로벌 오브젝트 를 할당 받았기 때문에 실행될때는 x변수에 접근이 가능하다.
  3. Identifier Resolution
    식별자 해석은 스코프 체인을 상대로 실행된다. ECMA262 스펙에서는 this 를 키워드로 구분한다 왜냐하면 스코프 체인과 상관없이 늘 실행 환경 객체에 의존해 해석되기 때문이다.
    식별자 해석은 최초 스코프 체인의 첫번째 오브젝트를 확인다 만약 해당 오브젝트에 존재하지 않는다면 해당 오브젝트의 모든 프로토타입을 확인한다. 만약 첫번째 찾지 못하면 체인의 2번째 오브젝트에 접근하고 식별자의 값이 리턴되거나 스코프 체인에서 null이 발견될때 까지 조사한다. 모든 스코프 체인의 마지막은 글로벌 오브젝트이고 글로벌 오브젝트의 프로토타입은 null이기 때문에 식별자 해석은 반드시 종료 된다.
     var c = {
         val3:3
     }
     function example(){
         var a = {val2:2};
         var val1 = a.toString;
         var val2 = a.val2;
         var val3 = c.val3;
         var val4 = c.val4;    
         console.log(val1);//function() toString{}
         console.log(val2);//2
         console.log(val3);//3
         console.log(val4);//undefined
     }
     example();
     신규 실행환경 객체 = {
         scope : activation/variable[a{val2:2}->Object{},...] -> 글로벌 오브젝트[c{val3:1},...]
     }
    
    위의 example함수의 신규 실행환경 객체위에서 실행되고 이때의 scope chain 은 위와 같이 구성되기 때문에 주석의 결과가 나온다.

원본
http://jibbering.com/faq/notes/closures/#clIdRes