2014년 11월 20일 목요일

Javascript Closures

원문 : http://jibbering.com/faq/notes/closures/

Introduction

Closure
 클로저는 자유 변수 와 그 변수들을 묶을수 있는 환경을 가지는 표현식(특히 함수)을 의미한다.

클로저는 ECMA 스크립트 중에서 가장 강력한 부분이다. 하지만 정확이 이해하지 않고서는 적절하게 사용하기 힘들다.
하지만 반대로 쉽게 생성할수 있으며 심지어 의도하지 않았을때도 사용될수 있다.(실수로)
그리고 클로저 사용은 잠재적으로 브라우저 실행 환경에서 좋지 않은 영향을 줄수있다.

그들의 단점을 커버하고 장점을 사용하기 위해서는 클로저 메커니즘에(작동하는 방) 대한 이해가 필요하다
클로저는 scope 의 identifier resolution(식별자 해석 후 선택) 에 많이 의존하는대 그말은 object 의 property resoultion(속성 해석 후 선택)에 의존한다는 말과 동일하다.

클로저를 간단이 설명해보면 ECMAscript 는 inner functions 을 사용할수 있다.
inner function 이란 다른 function안에 정의(function definition) 되거나 표현식(function express)으로 표현된 function 을 의미한다.

inner functions은 바깥 펑션(outer funciton)의 파라미터와 로컬 변수에 접근 할 수 있다.
클로저는 inner function 들이 outer function에 접근할수 있다는 것으로 구현된다.
그말은 outer fuction 이 리턴된 후에도 실행 될수 있음을 의미한다. 즉 outer function의 리턴후에도 로컬 변수와 파라미터에 접근 할수 있음을 의미한다. 이 로컬 변수와 파라미터들은 outer function이 리턴될 떄믜 값을
가지고 있으며 inner function 으로 조작할수있다.

불행이도 클로저를 이해하기 위해서는 클로저가 내부적으로 어떤 메커니즘을 사용하는지를 이해해야 한다.
기술적으로 꽤 복잡하다. ECMA 262 명세의 알고리즘은 조금전에 설명한 부분을 조금 이해하기 힘들게 할수도 있지만
쉽게 단순화 시켜 설명할수 없다.

객체 속성 해석(object property name resoultion)에 대해 잘알고 있는 사람은 아래 섹션을 넘어가고 밑에 부분의 보면된다.

오브젝트 속성 이름 해석
(The resolution of property names on objects)

Ecmap Script 는 크게 Native object 와 Host Object 2가지로 구성되어 있다.
native object 의 하위 카테고리에는 Built-in object(이미 구현되어 있는 객체)다

Native object 는 언어에 종속되어 있으며 host object는 환경에의해(브라우저, node.js) 제공된다. 예를 들면 (doc obj or Dom node 같은 애들)

Native object 는 이미 정의된 속성들을 가지고 있으며 필요시 동적으로 정의할수 있다.(구현체에 따라 동적으로 정의 할수 없을수도 있다. 하지만 별 상관없다.);

object 에 정의된 속성들은 value 을 가질수도 있다. 값은 다른 객체의 reference 일수도 있고 primitive 값일수도 있다.
(primitive type 은 String, number, Boolean, Null, Undefiend 가 있고 Undefiend가 값이라면 속성은 정의 되었지만 값은 아직 할당되지 않은 상태를 의미한다. )

아래는 object 에 속성이 어떻게 정의 될수 있는지를 표현한다.

Assignment of values
객체의 속성들은 새로 생성될수도 있고 이미 존재하는 속성에 값을 할당할수도 있다.

var objectRef = new Object(); //javascript object 객체를 생성하고

testNumber 속성을 생성해보자

objectRef.testNumber = 5;
// 또는
objectRef["testNumber"] = 5;

javascript object 는 prototype 을 가지고 있으면 그들은 object 일수도 있다 또한 그 오브젝트안에 속성들이 존재할수도 있다 하지만 이부분은 변수 배정에는 아무런 영향을 끼치지 못한다.

속성에 값을 배정할때 실제 객체에 해당 속성이 존재하지 않는다면 속성이 생성되고 값이 배정된다.
만약 존재한다면 해당 속성의 값을 재설정한다.

Reading of Values

속성의 값을 읽을때 prototype이 영향을 미친다.
만약 속성 접근자에서 사용된 속성의 이름이 객체에 존재한다면 그값이 리턴된다.

/* 객체의 속성값에 값을 할당할때 만약 해당 속성이 존재하지 않는다면 생성하고 배정한다.
그러므로 값을 할당 후에는 해당 속성이 생성되어 있다 */
objectRef.testNumber = 8;

/*속성에서 값읽기*/
var val = objectRef.testNumber;
/* val은 객체 속성에서 읽어드린 8 값을 가지고 있다. */

하지만 모든 객체들은 prototype 을 가지고 있으며 prototype에 할당된 객체들도 prototype을 가지고 있을수 있다
그러므로 prototype chian 이라는걸을 구현한다.
prototype chain은 체인의 마지막의 객체가 null을 prototype으로 가지고 있으면 종료된다.
Object 생성자는(constructor) 기본적으로 null prototype을 가지고 있다 .


var objectRef = new Object(); //javascript object 만들기

위의 오브젝트는 Object.prototype을 prototype 으로 가지고 생성되었다
Object.prototype 의 prototype은 null임으로 ObjectRef 의 prototype chain 은 오직하나의 Object(Object.prototype)를 가지고 있다

하지만 아래를 보자

/*MyObject1 type의 object를 만드는대 사용하는 생성자 함수*/
function MyObject1(formalParameter){
 /*생성된 오브젝트에 - testNumber - 라는 속성을 할당하고 생성자에 넘겨진 첫번째 arg로 값을 할당함
 */

 this..testNumber = formalParameter;
}

/* MyObject2 type의 object 를 만드는대 사용하는 생성자 함수*/
function MyObject2(formalParameter){
 /* 생성된 오브젝트에 -testString -라는 속성을 할당하고 생성자에 넘겨진 첫번째 arg로 값을 할당함
 this.testString = formalParameter;
}

/* MyObject2의 default prototype 에 MyObject1을 할당하며 이때 MyObject에 8을 arg로 넘겨
   testNumber 를 8로 할당
*/

MyObject2.prototype = new MyObject1(8);

/* MyObject 를 생성하고 해당 오브젝트를 objectRef 에 넘긴다 이때 MyObject2 의 "String_value"를 넘겨
   testString 을 "String_value"로할당
*/

var objectRef = new MyObject2("String_Value");

objectRef 에 할당된 MyObject2 instance는 prototype chain 을 가지고있다
chain 의 첫번째는 MyObject1 instance다
MyObject 1 의 prototype 은 기본적으로 Object.prototype 이며
Object.prototype 의 prototyp은 null prototype을 가지고 있다
즉 MyObject2 아래와 같은 prototype chain 이 만들어진다.
MyObject1->Object->null

property accesor(프로퍼티 접근자)가 objectRef 의 속성에 접근할때는 모든 prototype chian 이 process에
들어옴니다.
var val = objectRef.testString;

위의 경우 MyObject2 가 testString 을 가지고 있음으로 val 에 "Sring_Value"가 할당됨니다.
하지만

var val = objectRef.testNumber;

를 하게 될경우 MyObject2에는 해당 속성이 없음으로 객체의 prototype 을 검삼합니다. MyObject1 에 testNumber가 있음으로 찾아서 8로 변수를 할당함니다.

var val = objectRef.toString;
의 경우 MyObject1, MyObject2 모두 속성값을 가지고 있지 않으면 prototype chain을 따라 올라감니다. Object 가 toString 을 가지고 있음으로 해당 함수고 리턴됨니다.


var val = objectRef.madeUpProperty
undefiend 가 할당됩니다. 왜냐하면 모든 prototype chian 을 조사해도 존재하지 않기 때문입니다.(순서대로 조사하다 마지막 null 에 도착하면 찾기가 종료됩니다.)

값을 읽을때는 첫번째로 찾은 값이 리턴됩니다.(체인에서 앞쪽에 있는 객체의 속성값)
하지만 값을 할당할때 해당 속성이 해당객체(prototype chain 제외)존재하지않는다면 해당 속성을 생성합니다.

즉 objectRef.testNumber = 3 를 하게 될경우 MyObject2의 인스턴스에 testNumber 가 만들어지고 값이 3으로 설정됩니다

추후의 접근 모두 My object2에 접근하게 되고  prototype chain은 더이상 검사되지 않습니다.
하지만 MyObject1.testNumber 는 8로 변경되지 않은체 존재하고 있습니다.
위와 같은 상황을 mask 라고 합니다.

ECMAScript 는 내부적  [[prototype]]을 Object type의 property로 할당합니다.
이 내부적 [[prototype]]은 script에서 접근할수 없습니다. 하지만  property accessor resoultion을 할경우
사용됩니다. 또한 object.prototype은 변경 할수 있으며 prototype 관련 접근 문제에는 object.prototype 과
object.[[prototype]]이 두개가 사용됩니다.

Identifier Resolution, Execution Contexts and scope chains
(식별자 해석, 실행 Contexts 그리고 scope chain)

The Execution Context
An executin context 는 ECMAScript의 구현체에 필요해서 사용되는 추상 사상이다(abstract concept).
명세서는 execution context 가 어떻게 구현되어 있어야 된다는 정의 되어 있지 않다. 하지만 execution context 는
명세에서 정의된 구현체(structure)를 위해서 몇개의 속성을 가져야한다.
그러므로 execution context 는 몇개의 공개되지 않는 속성을 가진 object 로 구현된다.

모든 javascript 코드는 execution context에서 실행된다. Global code는 globla execution context 에서 실행된다. 또한 호출되는 모둔 함수들은(constructor 포함)연관된 execution context 를 가진다.
eval 함수의 경우 다른 execution context 를 가진다. 하지만 일반적으로 사용하지 않음으로 여기에서는 다루지 않는다.
ECMS 262(3rd edition)의 10.2 절에  execution context 에 대한 정의가 있다.

javascript 함수가 호출되면 execution cotext 안에 들어온다. 만약 새로운 함수가 호출되면(같은 함수가 재귀적으로 호출되어도) 새로운 execution context 가 만들어지고 함수가 호출되는 동안 새로이 생성된 execution context 안에 들어온다. 그러므로 javascript  code 의 실행은 execution context stack 형태로 구현된다.

execution context 가 생성될때 정해진 순서대로 여러가지가 일이 이루어진다.
첫번쨰로 function 의 exectution context 에 Activation object가 생성된다.  activation object 의 매커님즘은 아래와 같다. 이건 몇개의 속성을 가진 오브젝트다 하지만 prototype을 가지지않는다(at least not a defiend prototype) 또한 javascript 코드에서 직접적으로 접근할수 없다.

두번째로 arguments를 만든다. 해당 함수 호출에 같이 넘겨진 arg들을 array 비슷한 객체에(integer index)로 접근가게 하여 만든다. 이것은 length 와 callee 속성을 가지고 있다

Activation object 는 arguments 속성을 만들고 좀전에 만들어진 arguments 를 속성의 값으로 할당한다

세번째로 execution context가 scope에 할당된다.
scope란 object 들이 연결된 리스트이다. 모든 function object 는 내부적으로 [[scope]] 속성을 가지고 있
다 [[scope]]속성또한 object들이 연결된 리스트로 구현되어 있다 execution context 에 할당된 스코프는
호출된 fuction이 가지고 있는 [[scope]] 의 속성 objects 리스트 맨앞에 activation object를 더한 거다

그후 variable instantation 이 ECMA262에서 "Varible" Object 라고 지칭하는 Object 를 사용해서 일어난다.
하지만 Activation object 가 Variable object 로 사용된다(Actiovation object 와 Variable Object는 동일한 Object 이다.)

함수에 넘겨진 parameter 별로 variable object에 속성을 만든다. 그후 argumnets 의 값을 동일한 해당 속성에 할당한다.
(없을 경우 undefiend 로 할당한다.)

ex) function aa(arg1, arg2, arg3) -> called aa(1,2) -> parameter arg1 = 1, arg = 2, arg3 = undefiend

그후 innerfunction definition으로 fuction object 를 생성하고 fuction 이름을 속성으로 하여 생성된 function object 를 할당한다. 마지막으로 함수안에 정의된 로컬 변수들을 variable object의 속성 이름을을 생성한다.
(이떄 로컬변수들은 undefiend 값이 할당된다.. 실제적인 로컬변수의 값은 함수의 body code 가 실행될때 변수에 할당된 expresstion 을 evaultion(평가)해서 할당된다. )

arguments 속성이 정의된 Activation Object가  함수의 로컬 변수가 정의된 Varaible Object 가 같은 객체이기 때문에 argumnets 식별자를 함수의 로컬 변수처럼 사용 할수 있다.

마지막으로 this keyword에 value 가 할당된다. 만약 object 가 할당된다면 this는 그 object 를 지징하며 null이라면
Global object 를 지칭하게 된다.

Global execution context 의 경우는 조금 다르다 외냐하면 argumnets가 없기 때문에 activation object를 정의할 필요가 없다 또한 scope chain은 정확이 하나의 global object 로 구성되어있다 그후 variable instantiation 단계를 실행한다. Global object를 Variable Object 로 사용한다  이렇기 때문에 global 에 선언한 함수들이 Global object 의 속성이 되는 이유이다. 또한 속성도 마찬가지이다.

도한 global execution context 에서 this 키워드는 global object 를 가리킨다.

scope chains and [[scope]]
execution context 에 할당되는 scope chain은 호출된 함수의 [[scope]]속성 맨앞에 execution context의 Activation /variable object 를 더해서 만들어진다.
그러므로 함수의 [[scope]] 속성이 어떻게 정의 되었는지 아는 부분이 중요하다.

ECMAScript 에서 함수는 object 이다. function object 는 variable instantiation 단계에서 만들어지거나
함수 표현식을 평가(evalution) 또는 Fuction 생성자를 호출할때 만들어 진다.

Function construcotr 로 만들어진 Fuction object의 [[scope]] 속성은 global object 로만 구성되어 있다.

execution context 의 scope chain 을 가진 function declaration 또는 function expresssion 안에서 생성된 함수들은 그들의 [[scope]]  속성에 부모의 scope chain 을 할당 받는다.

global function declaraion 으로 간단한 예를 들어보자
function exampleFunction(formalParameter){
 // function body code
}

해당 함수는 globle execution context 의 variabl instantiation 단계에서 생성된다. global execution context 는 global object 로만 이루어진 scope chain 을 가지고 있음으로 해당함수의 [[scope]]속성에는
glbal object 로 만 이루어진 scope chain을 가지게 된다.

비슷하게 global 에 정의된 fuction expression 을 보자
var exampleRuncRef = function(){
 // functio body code
}

위와 같은 경우 global object 의 named property 가 vaiable instantiation 단계에서 만들어지지만
function object의 경우 생성되지 않고 해당 속성이 사용될때 만들어진다.

?하지만 함수의 생성이 global executio contxt 에서 일어나므로 [[scope]] 속성은 하나의 global object 로 구성된다.

아래는 inner function declaration 과 express 이 function object 내부에서 만들어지기 때문에 조금 상향된 scope chain 을 같는다. 아래의 코드를 생각해보자
내부에 innerfunciton 을 정의하고 outer function 을 실행하는 예제이다

function exampleOuterFunction(formalparamter){
 function exampleInterFunctionDec(){
  // inner function body
 }
 // the rest of the outer funciton body
}

exampleOuterFunction(5)

outerfuncion 의 경우 gloabl execution context 의 vairable instantiation 단계에서 생성되가 이때문에
[[scope]] 속성에 glolbal object 가 할당된다.

그후 글로벌에서 exampleOuterFunction 을 실행 시키면 새로운 execution context 가 생성된다.
그리고 Activaion/Variable object 순서가 차례로 실행된다. 이때 exampleOuterFuntion 의 scope 는 new Activiation object 를 exampleOuterFunction 의 [[scope]](global object 만존재) chain 앞에더해서 할당된다.

이때 outer funciton 의 varaible instatiation 단계에서 inner function definion 을 실행하게 되는대 이때
inner function의 [[scope]] 에 해당 execution context 의 scope 가 할당된다.

이때의 scope 는 outerfunciton 의 activation object -> global object 로 구성되어 있다.

위에 기술된 부분은 소스 코드를 실행할때 자동으로 실행된다.

execution context 의 scope chian 은 함수를 생성할때 [[scope]] 속성에 할당되며 함수의 [[scope]]속성은 execution context 의 scope 를 정의하게 된다. (물런 [[scope]] 속성앞에 activation object 를 함쳐서 )

하지만 ECMAScript 의 with 문은 scope chain 을 변경할수 있다.

with 문은 expression 을 실행시킨다. 만약 expression 이 object 라면 현재 execution context 의 맨압부분에
(Activaion/Variable Object 앞에) 해당 오브젝트를 추가한다.
with 문 블락이 실행된 후에는 좀전에 변경된 execution context를 원래대로 복원한다.

function declartion 의 경우 variable instantiaion 단계에서 생성되기 때문에 영향을 받지 않을수 있지만
function express 의 경우 with statement 안에서 evaution 되기때문에 영향을 받을수 있다.

/ obcjet 를 가리키는 y를 만든다.)
var y =  {x:5};
function exampleFuncWith(){
 var z;
 /* 좀전의 y object 를 context scope chain 앞에 붙인다.)
 with(y){
  /* function expression 을 실행 시켜 funciton object 를 만들고 local 변수 z에 할당한다.
  */

  z = function(){
   // inner function expression body
  }

 }
 ....
}

/*execute the exampleFuncWith */
exampleFuncWith();

exampleFUncWith 는 [[scope]] 로 global object 를 가지고 있다 이때 해당 함수를 실해하면 [[scope]]에 해당 Activation object 를 만들어서 앞에 붙이고 execute context 에 scope 를 할당한다
wish 문을 실행시키면 글로벌 object y를 scope chain 앞에 붙이고 안에 바디문을 실행한다.
그러므로 안에서 실행된 function expressein 의 [[scope]] 에는 y->outer activation obj->global obj가 할당된다.

그후 with 문의 블락이 끝나면 원래대로 scope chain 을 복귀한다 하지만 with 문의 블락안에서 생성된 함수 객체의 [[scope]]속성에는 y object 가 맨앞에 있다.

Idetifier Resolution
(식별자 해석)

식별자는 scope chain 에 대항해서 resolve 된다. ECMA 262 에서는 this 를 속성보다는 keyword 로 분류한다.
이말은 this 가 scope chain 을 참조하지 않고 실행되는 execution context에 따라 정의 됨을 의미한다.

식별자 해석은 scope chain 의 맨 앞부분에서 시작된다. 일단 식별자에 대응하는 해당 이름 속성이 정의되어있는 확인한다.
왜냐하면 scope chain 은 object의 chain 이기 때문이다. 일단 object 의 prototype chain 을 포함해서 확인한다.
만약 scope chain 의 첫번째 object 에서 해당 값을 찾지 못하면 다음 chain 의 object 로 넘어간다.
넘어가면서 찾거나 아니면 scope chain 이 끝날때까지 찾는다.

identifier opertaion 은 위에서 언급한 propery acessor  와 동일하다.
만약 object 가 scope chain 에서 식별 되었다는 것은 해당 속성이 오브젝트를 가지고 있다 property accesor 에서 그리고 식별자는 그 오브젝트의 속성처럼 행동한다.  글로벌 오브젝튼 언제나 scope chain 의 끝이다.


functio 호출과 연관된 exection contexts 는 chain 맨앞에 Activation/Varaible object 를 가지고 있을것이다.

funcito body 에서 사용된 식별자들은 효과적으로 파라미터와 내부 함수 정의 이름 또는 로컬 변수에 대해서 효과적으로 첫번째로 체크할수있다. 그것을 아마도 Activation/Varaible object 로 쉽게 resolve 될수도 있을것이다.

... 뒷부분은 그냥 읽었습니다. :)

다른 분의 번역 http://nodejs-kr.org/insidejs/archives/508