Skip to content

Latest commit

 

History

History
328 lines (232 loc) · 13.3 KB

Javascript의_동작원리-변수객체(VariableObject).md

File metadata and controls

328 lines (232 loc) · 13.3 KB

자바스크립트의 동작 원리 - 변수 객체(Variable Object)

자바스크립트의 동작 원리 - 실행 컨텍스트(Execution Contexts)에 이어서...

변수 객체(Varaiable Object; VO)

변수 객체실행 컨텍스트의 프로퍼티로, 실행에 필요한 정보(어디에 어떤 데이터가 저장되고, 어떻게 호출할 수 있는지)를 담고 있다. 또한, 변수 객체는 코드가 실행될 때 엔진에 의해 참조되며 코드에서는 접근할 수 없다.

변수 객체의 구성요소

  • 변수 선언(variable declaration)
  • 함수 선언(function declaration; FD)
  • 매개변수(formal parameters)와 인수정보(arguments)

데이터 선언(Data Declaration)

변수나 함수를 선언하는 것은 변수 객체에 변수의 이름과, 이름에 할당된 값을 갖는 새로운 프로퍼티를 만드는 것과 같다.

var a = 10;
     
function test(x) {
  var b = 20;
};
     
test(30);

// 전역 콘텍스트의 변수 객체
VO(globalContext) = {
  a: 10,
  test: <reference to function>
};
     
// test 함수 콘텍스트의 변수 객체
VO(test functionContext) = {
  x: 30,
  b: 20
};

변수 객체의 value

변수 객체는 프로퍼티이기 때문에 값을 갖는데, 이 값은 다른 객체를 참조한다. 이 때, 전역 컨텍스트와 함수 컨텍스트는 참조하는 객체가 다르다. 전역코드와 함수 코드의 내용이 다르기 때문이다. 하지만, 모든 종류의 실행 컨텍스트에서 공통적으로 동작하는 작업이 있다.

e.g. 변수 초기화, 변수 객체의 동작

  • "From this viewpoint it is convenient to present the variable object as an abstract base thing"
AbstractVO // generic behavior of the variable instantiation process

╠══> GlobalContextVO // 전역 컨텍스트의 변수 객체는 전역 객체이다.
 (VO === this === global) 

╚══> FunctionContextVO // 함수 코드의 실행 컨텍스트에서 변수 객체는 활성 객체이다.
  (VO === AO // <arguments> object and <formal parameters> are added

ES5에서는 변수객체, 활성화 객체의 개념이 Lexical Enviroments(어휘적 환경)모델로 대체되었다.


전역 컨텍스트의 변수 객체

전역 컨텍스트의 변수 객체는 전역 객체이다.

var x = 'xxx';
    
function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();

ec-vo-global

변수객체는 코드에서 직접(directly) 접근이 불가능하다. 하지만, 전역 컨텍스트의 변수 객체에 있는 변수(variable)는 간접적으로 참조 가능하다. 전역 컨텍스트의 변수 객체는 전역 객체이다. 즉, 전역 객체(GO)에 있는 변수만 간접적으로 참조 가능(위의 예제에서 foo, x)하다. 변수를 선언하는 것은 VO에 프로퍼티를 만드는 것이기 때문에 변수 객체의 프로퍼티명을 통해서 참조 가능하다. 따라서, 전역 컨텍스트에서 변수를 선언하면 변수 객체의 프로퍼티를 통해서 간접적으로 접근할 수 있다.

// 위의 예제에 이어서
alert(x); // VO(globalContext)에 직접적으로 접근한다. "xxx" 출력 
alert(window['x']); // 전역 객체 === VO(globalContext)인 점을 이용해서 간접적으로 접근한다. "xxx" 출력
alert(x === this.x); // true 

var key = 'x';
alert(window[key]); // 동적인 프로퍼티 명으로 간접적으로 접근. "xxx" 출력

함수 컨텍스트의 변수 객체

마찬가지로, 함수 코드의 실행 컨텍스트에서 변수 객체는 직접 접근이 불가능하다. 전역 객체에서는 변수 객체를 간접적으로 참조하여 접근하지만, 함수 컨텍스트에서 이 역할은 활성화 객체(Activation objcet; AO)가 수행한다.

var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();

ec-vo-foo


활성화 객체(Activation Object)

활성화 객체는 함수 코드의 컨텍스트로 진입할 때 생성되고, 값이 Arguments 객체인 arguments 프로퍼티로 초기화된다.

AO = {
  arguments: <ArgO>
};

활성화 객체의 프로퍼티

  • Arguments 객체 - 활성화 객체의 프로퍼티
  • callee - 현재 함수에 대한 참조
  • length - 실제로 전달된 인자의 수
  • properties-indexes("num")
    • 함수 매개 변수의 값(왼쪽부터 오른쪽 까지 매개 변수의 리스트)
    • arguments.length와 개수가 같다.
    • arguments객체의 properties-indexes와 제공된 매 변수는 실제로 전달된 값을 공유한다.
foo(10, 20);

function foo(x, y, z) {

  alert(foo.length); // 정의된 함수 매개변수 x,y,z의 수량 : 3 
  alert(arguments.length); // 실제로 전달된 매개변수(x, y)의 수량 : 2
  alert(arguments.callee === foo); // 함수 자신에 대한 참조 : true
 
  // 매개변수 공유
  alert(x === arguments[0]); // true
  alert(x); // 10
 
  arguments[0] = 20;
  alert(x); // 20
 
  x = 30;
  alert(arguments[0]); // 30
 
  // 전달되지 않은 매개변수 z는 공유되지 않는다.  
  z = 40;
  alert(arguments[2]); // undefined
 
  arguments[2] = 50;
  alert(z); // 40 
}

컨텍스트 코드 실행 단계(Phases of processing the context code)

실행 컨텍스트 코드의 실행은 두 단계에 걸쳐서 이루어진다.

  1. 실행 컨텍스트 진입
  2. 코드 실행

이 단계에서 변수 객체의 프로퍼티가 채워지며, 값이 할당된다.


실행 컨텍스트 진입

실행 컨텍스트로 들어가면 VO는 아래의 프로퍼티로 채워진다.

  • 함수의 매개 변수를 위한 프로퍼티(함수 컨텍스트의 경우)
    • 매개변수(formal parameter)의 이름과 값을 갖는 변수 객체의 프로퍼티
    • 값(인수; arguments)이 전달되지 않으면 값이 undefined
  • 함수 선언을 위한 프로퍼티(FunctionDeclaration; FD)
    • 함수 객체의 이름과 값을 갖는 변수 객체의 프로퍼티
    • 이미 같은 이름의 변수가 있으면, 새로운 값으로 교체
  • 변수 선언을 위한 프로퍼티(VariableDeclaration; var)
    • 변수의 이름과 undefined 값을 갖는 변수 객체의 프로퍼티
    • 변수의 이름이 이미 선언된 매개변수나 함수의 이름과 같다면, 변수 선언은 무시된다.

실행 컨텍스트에 진입 하는 단계에서 VO에 프로퍼티는 설정되지만, 값이 할당되지는 않는다.(호이스팅)

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
 
test(10); // 호출

test의 함수 컨텍스트

AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};

함수 x는 선언식이 아니라, 함수 표현(FunctionExpression; FE)이기 때문에 VO에 영향을 주지 않는다.

함수 _e 또한 표현식이지만, 변수 e에 할당되기 때문에 e를 통하여 접근할 수 있다.


코드 실행

코드가 해석되는 동안 AO/VO의 프로퍼티가 변경된다.

실행 컨텍스트로 진입하면 AO/VO가 프로퍼티로 채워지지만, 모든 프로퍼티에 실제 값이 할당되어있지는 않다.

// 위의 예제에 이어
AO['c'] = 10;
AO['e'] = <reference to FunctionExpression "_e">;

표현식 _e 가 선언된 변수e 에 저장되어 있기 때문에 메모리에 존재한다. 반면, 함수 x는 존재하지 않는다. x를 호출하여도 "x" is not defined라는 에러가 나오게 된다. 변수에 저장하지 않은 표현식은 정의된 곳에서 호출하지 않으면, 재귀적인 방법으로만 호출할 수 있기 때문이다.

예시, 코드가 해석되기 전과 후의 차이

alert(x); // function
 
var x = 10;

alert(x); // 10
 
x = 20; 

function x() {} 

alert(x); // 20

참고 - Hoisting(MDN Docs)


변수의 경우

변수는 오직 var 키워드를 이용하여 선언한다. 따라서, var 키워드를 이용하지 않으면 전역변수가 된다는 것은 틀린 말이다. var 키워드를 이용하지 않고 값을 할당하는 것은 전역 객체에 새로운 프로퍼티(변수가 아닌)를 추가하는 것이다.

변수가 아니라는 것은 값을 할당하고 변경하고 참조하는 등이 불가능하다는 것이 아니다. ECMAScript에서 말하는 변수의 개념이 아니라는 뜻이다.

alert(a); // undefined
alert(b); // "b" is not defined
 
b = 10;
var a = 20;

b는 변수가 아니므로, VO안에 없다(호이스팅이 이루어지지 않는다).

VO = {
  a: undefined
}; 
a = 10;

alert(window.a); // 10 
alert(delete a); // true 
alert(window.a); // undefined
 
var b = 20;
alert(window.b); // 20 
alert(delete b); // false 
alert(window.b); // 여전히 20

또한, 변수는 DontDelete 속성을 가지고 있다. 반면, 단순 프로퍼티는 그렇지 않기 때문에 delete 연산자를 이용하여 삭제할 수 있다.

ES5에서 DontDeleteConfigurable 로 이름이 변경되었고 Object.defineProperty 메서드를 이용해서 수동으로 조작할 수 있다.


eval의 경우

eval('var a = 10;');

alert(window.a); // 10 
alert(delete a); // true 
alert(window.a); // undefined

변수에 DontDelete 속성이 설정되지 않는다.

Firebug는 콘솔에서 코드를 실행하기 위해 eval 을 사용한다. 따라서 변수를 delete 로 삭제할 수 있다.


Reference