2015년 4월 2일 목요일

[JavaScript] 자바스크립트 this 키워드의 모든것

this 키워드를 이해하기 위해서는 Execution Context를 이해해야하니, 혹시 Execution Context를 모른다면 이 글(클릭하기)을 읽어 봐 주길 바란다.

자바스크립트에서는 매번 함수의 호출마다 새로운 Execution Context가 생성된다. 이 Execution Context는 Lexical Environment, Variable Environment, 그리고 ThisBinding에 대한 정보를 가지고 있는데, 바로 this 키워드가 이 이 thisbinding이라고 할 수 있겠다. this값은 해당 함수가 현재 어느 Execution Context에서 구동하고 있는지를 알려준다.

Context는 함수 호출에 의해 새로이 생성되며, thisBinding 값은 함수 호출시점에 정해지게 된다. 정확히 말하자면 ECMAScript 내부에서 Function call 발생할 때, 함수의 Arguments List와 thisArg라 불리는 두 객체를 전달하게 되는데, ECMAScript® Language Specification 10.4.3에 따르면 여기서 아래의 과정을 거친후 thisBinding의 값이 결정 된다.
  1. 만약 함수가 strict mode에서 정의된 함수인지를 확인한 후, thisBinding 값은 전달된 thisArg값이 된다.
  2. (strict mode에서 정의된 함수가 아니고) thisArg 값이 null 이나 undefined라면면,  thisBinding 값은 global object가 된다.
  3. 전달된 Type(thisArg) 연산으로 얻어진 thisArg 값의 Typeobject(객체)가 아니라면, thisBinding 값은 toObject(thisArg) 오퍼레이션을 통해 객체화된 객체가 된다.
    • Type() 함수는 ECMA스크립트 내부 함수로, 주어진 변수의 Type을 연산합니다.
    • toObjecT() 함수는 주어진 변수를 Boolean, Number, String, Object 형으로 변환 합니다
  4. 위 모든 과정에서 thisBinding 값이 정해지지 않았다면, 전달된 thisArgthisBinding이 된다.

참고로 funciton.prototype.call, function.prototype.apply 와 같은 this 값을 수정 가능케 하는 기능들은 모두 thisArg 값을 사용자가 전달할 수 있게 함으로써 가능한 기능들이다.

그렇다면, 일반적으로는 이 thisArg 값이 어떻게 결정 되는 것 일까? 이는 함수 호출(Function Call) 과정을 설명한 ECMAScript® Language Specification 11.2.3 에 잘 정의 되어 있다 - 6번과 7번에 주목하자.

*우선 이전에 몇가지 알아야 될것들이 있다

  • 함수가 저장된 주소인 reference 값은 base value, referenced name, strict reference flag, 이 3가지 요소로 이루어져 있다
    • referenced namestring 값으로 저장된 function identifier이다
    • strict reference flagboolean 값으로, strict mode 에서 실행되는 코드인지를 표시한다
    • base valuereference가 속한 context object를 가리킨다

thisArg값은 함수 호출(Function Call)의 6번째 과정에서에서 결정되며, 함수 호출의 전체 과정은 아래와 같다.

  1. 함수 객체의 이름(MemberExpression)을 해석해서 함수가 저장된 레퍼런스 값인 ref를 얻는다.
  2. GetValue(ref) 오퍼레이션을 이용, 함수 func을 불러온다.
  3. 주어진 arguments 들로 argList를 생성한다.
  4. Type(func) 오퍼레이션을 이용, 함수 func의 타입을 확인한후, 객체타입이 아니라면 TypeError Exception 을 발생시킨다.
  5. IsCallable(func) 오퍼레이션을이용, 함수 func의 호출이 가능한지를 확인한 후, 불가능하다면 TypeError Exception을 발생시킨다.
  6. Type(ref)를 이용, ref의 타입을 확인한 후, reference 타입이라면
    1. IsPropertyReference(ref) 오퍼레이션을 이용, ref가 object 또는 primitive wrapper object(String, Boolean, Number 등의 객체)에 속한 property 라면 thisArg값은 getBase(ref) 오퍼레이션에서 얻어진 해당 객체 인스턴스가 된다.
    2. 그 이외의 경우에는 ref가 속한 Environment Record(Variable Environment Object)라면 getBase(ref)에서 얻어진 값의 ImplicitThisValue()가 바로 thisArg가 된다.
  7. 만약 Type(ref)를 이용해 확인한 ref의 타입이 reference 가 아니라면, thisArg값은 undefined가 된다.
  8. Call 오퍼레이션을 이용, thisArg 값과 argList를 전달해 함수를 호출한 후 그 결과값을 리턴한다.

결국 각각의 경우에 따라 thisBinding의 값이 달라지니 좀 헷깔린다. 그래서 자주 보게되는 상황들을 Case By Case 로 설명을 해보려 한다.

* 참고로 this 키워드는 객체이기 대문에 '.'(Dot Notation) 또는 '[', ']'(Bracket Notation) 를 이용해서도 this 객체의 변수나 함수를 호출 할 수 있다.
* 위 과정을 읽었으면 알겠지만 strict 모드에서는 함수 호출시 thisArg값은 non-strict mode 일때와는 다르다.


1. Declaration environment record에 존재하는 함수 호출(Function Call)


  • 위의 6-2번 과정이다. ImplicitThisValue() 는 모든 declaration에 대해 undefined를 리턴한다
  • 이 때문에 this 키워드는 strict mode에서는 undefined 값을, non-strict mode에서는 global object(window object)값을 참조하게 된다.


function invokeFunction() {
    console.log("invokeFunction");
    console.log(this);//Window
    demo1();
    function demo1() {
 console.log("demo1");
 console.log(this);//Window

 var demo2 = function() {
     console.log("demo2");
     console.log(this);//Window

     var demo3 = function() {
  console.log("demo3");
  console.log(this);//Window
     };
     demo3();
 };
 demo2();
    }
}
console.log(this);//Window
invokeFunction();


2. new 키워드를 이용해 객체(object)로 인스턴스화 될 때, this 값은 언제나 인스턴스화된 객체를 참조한다.

  • 위 과정의 6-1번에 속하는 경우이다. 호출된 함수가 객체의 property인 경우이다


function thisObject(name, property) {
    this.name = name;
    this.property = property

    this.thisValue = this;

    this.f = function() {
 console.log("this in function f \n" + this); //object
    }
}

var demo = new thisObject("testObject", 0);
console.log("demo.thisvalue is " + demo.thisValue); //object demo


3. 객체의 메소드로써 호출될 때, this 값은 언제나 인스턴스화된 객체를 참조한다.


  • 메소드란 객체의 프로퍼티(property)로 존재하는 함수를 한다.
  • 이 경우 또한 위 과정의 6-1번의 경우가 되겠다.


var a = {
    test : function() {
 return this;
    }
};

a.test();// object 'a'

var b = {};
b.test = a.test;

b.test()// object 'b'


4. function.prototype.call 또는 function.prototype.apply 메소드를 이용해 호출될 때


  • thisArg의 값은 사용자에 의해 주어진다.


function test() {
    return this;
}
test();// windows

var a = {};
test.apply(a);// object 'a'

test.call(a);// object 'a'

2015년 3월 10일 화요일

[JavaScript] 자바스크립트 Array(배열)

자바스크립트는 여러가지 built-in Object를 내장하고 있는데, 그중 Array(배열) 개체은 사실상 가장 기초적이며 가장 다양하게 쓰이는 개체일 것 이다.
같은 타입(Type)의 자료를 저장해야하는 타 프로그래밍 언어와는 달리, 자바스크립트(ECMAScript)는 배열에 다른 자료형들을 저장하는게 가능하다. 즉, Array(배열) 1개에, boolean 값, string 값, number 값, 등을 모두 저장할 수 있다는 말이다. Array 자료형은 총 4294967295개의 데이터를 저장할 수 있다. 만약 이 이상을 저장하려 한다면 에러가 발생하게 될 것이다.

Array(배열)은 2가지 방식으로 선언될 수 있다.

// Array Constructor 를 이용하는 방법

var arrayConstructorDemo1 = new Array(); // empty Array를 생성
var arrayConstructorDemo2 = new Array(3); // 3의 크기를 가진 Array를 생성
var arrayConstructorDemo3 = new Array("test", true, 33); // 원하는 데이터로 생성

// Array Literal Notation을 사용하는 방법
var arrayLiteralDemo1 = []; // empty Array를 생성한다.
var arrayLiteralDemo2 = [ "test", true, 33 ];
// "test", true, 33 3개의 데이터를 가진
// 어레이를 생성 ArrayConstructorDemo3와 같은 결과

// 아래의 Array Literal Notation 사용은 작동하나, 브라우져 환경에 따라 에러를 생성할 수 있으니, 사용을 자제하도록하자.

var arrayLiteralDemo3 = [ 1, 2, 3, ]; 
// 1,2,3값을 가진 크기 3 또는 4(네번째는 undefined 값)의 배열을 생성한다.

var arrayLiteralDemo4 = [ , , , ]; 
// 3개 또는 4개의 undefined 값을 가진 array를 생성한다

Array(배열)의 선언 후에 내부 값에 접근(읽기/쓰기)하기 위해서는 [,] 사각괄호안에 접근하고 싶은 값의 index를 넣으면 된다.
참고로 배열의 index는 언제나 0 부터 시작하며, 배열의 크기는 length프로퍼티에 접근하여 알 수 있다.


var arrayConstructorDemo = new Array("test", true, 33);

console.log(arrayConstructorDemo[0]); //"test"
console.log(arrayConstructorDemo[1]); //true
console.log(arrayConstructorDemo[2]); //33

//같은 방식으로 값을 변경할 수 있다.

arrayConstructorDemo[0] = "complete";
arrayConstructorDemo[1] = 13;
arrayConstructorDemo[2] = false;

console.log(arrayConstructorDemo[0]); // complete
console.log(arrayConstructorDemo[1]); // 13
console.log(arrayConstructorDemo[2]); // false

console.log(arrayConstructorDemo.length);// 배열의 크기 확인

//배열의 new index에 새로운 값을 삽입한다.
arrayConstructorDemo[4] = "new Index"; // index 4에 새로운 값을 삽입한다
console.log(arrayConstructorDemo[4]); // new Index
console.log(arrayConstructorDemo.length);// 5

//배열의 크기를 2로 줄여보자.
arrayConstructorDemo.length = 2;
console.log(arrayConstructorDemo[4]); // undefined
console.log(arrayConstructorDemo[2]); // undefined

//다시 배열의 크기를 5로 늘릴경우
arrayConstructorDemo.length = 5;
console.log(arrayConstructorDemo[4]); // undefined
console.log(arrayConstructorDemo[3]); // undefined
console.log(arrayConstructorDemo[2]); // undefined

Array의 크기는 매우 동적이며, 만약 위의 코드처럼, 사이즈 3의 배열에 존재하지 않는 인덱스인 4에 새로운 값을 삽입하려 한다면, 자바스크립트 엔진은 자동으로 배열의 크기를 새로운 index에 알맞게 증가 시키고, 값을 삽입하게 된다.

length 프로퍼티는 read-only가 아니기 때문에, array의 크기를 조절하는데도 사용이 가능하다.
배열의 크기를 줄일 경우에는, 줄어든 배열에 포함되지 않는 index 값에 접근하게되면 언제나 undefined 값을 반환하게 된다.

배열의 크기를 늘리게 될 경우에도 마찬가지로 배열에 새로들어오게된 index들은 undefined 값을 가지게 된다.

Array.isArray() 메소드


Array.isArray() 메소드는 괄호안에 주어진 변수가 Array 개체인지를 확인하는 메소드 이다. 이 메소드는 ECMAScript 5에서부터 적용되기 시작했다. 이 메소드가 적용된 가장 큰 이유는 instanceof 연산자가 가지는 한계 때문이였는데, 1개의 global context를 전제하는 instanceof 연산자는 대부분 multi-frame DOM 환경에서 제대로 작동하지 않는다는 한계를 가지고 있었다. 이 때문에 총 2개의 global context를 가지는 경우에는, instanceof 연산자로 다른 global context의 Array 개채를 계산하게 되면, false 값을 얻게 된다. 이 한계를 넘기 위해 나온게 바로 Array.isArray 메소드 이다.

아래와 같이 사용할 수 있다.


if (Array.isArray(arrayConstructorDemo)) {
    console.log("This is Array");
}

Stack & Queue


자바스크립트 Array(배열)은 StackQueue 설정을 제공한다. 즉, 기본적으로 제공되는 메소드들로 배열을 Stack 또는 Queue로 사용하는게 가능하다. Stack 과 Queue는 LIFO, FIFO로 표현되는 자료 구조로, 그 컨셉을 자세히 알고 싶으면 앞의 링크를 클릭 해보도록 하자.

Stack

push 메소드를 이용해서 새로운 데이터를 삽입해 array 의 크기값을 반환하고, pop 메소드를 이용해서 가장 최근에 삽입된 데이터를 빼내온다.


var arrayStackDemo = new Array();

var size = arrayStackDemo.push("Demo1", "Demo2");

console.log(size);// 2

size = arrayStackDemo.push("Demo3");

console.log(size);// 3

var topStack = arrayStackDemo.pop();

console.log(topStack); // Demo3

console.log(arrayStackDemo.length); // 2


Queue

Stack과 같이 push 메소드를 이용해 배열의 끝에 새 데이터를 삽입한다.
원래 Queue의 값을 얻어오는 메소드도 이름이 "pop"이나 이미 Stack을 구현하는데 사용되버렸기 때문에 "shift"메소드를 이용해 배열에 값을 삽입힌다.
또한, "unshift" 메소드를 이용해 배열의 처음에 새 데이터를 삽입할 수도 있다


var arrayQueueDemo = new Array("Demo0");

var size = arrayQueueDemo.push("Demo1", "Demo2");

console.log(size);// 3

size = arrayQueueDemo.push("Demo3");

console.log(size);// 4

var firstQueue = arrayQueueDemo.shift();

console.log(firstQueue); // Demo0

console.log(arrayQueueDemo.length); // 3

size = arrayQueueDemo.unshift("newValue");

console.log(size);// 4

firstQueue = arrayQueueDemo.shift();

console.log(firstQueue); // newValue

Sort & Reverse


자바스크립트의 Array 개체는 기본적으로 배열을 재정렬할 수 있는 메소드들을 제공한다.

Reverse

Reverse 메소드는 이름에서도 알 수 있듯이, 배열을 뒤짚는 메소드 이다.


var a = [ 0, 1, 2, 3, 4, 5 ];
a.reverse();

console.log(a);// [ 5, 4, 3, 2, 1, 0 ]

Sort

Sort 메소드는 Array를 정렬한다. 하지만 기본적으로 Sort 메소드는 배열의 개체들이 String 타입이라 전제하에 정렬하기 때문에, 수의 경우에도 마찬가지로 문자열처럼 [1, 12, 13, 2] 정렬된다.


var a = [ 0, 1, 12, 13, 2 ];
a.sort();

console.log(a);// [ 0, 1, 12, 13, 2 ]

이를 해결하기 위해서 sort메소드의 파라미터로 비교함수를 넣어 줄 수 있다.
사용법은 아래와 같다.


function comparator(a, b) {
    if (a > b) {
 return 1;
    } else
 (a < b)
    {
 return -1;
    }
    return 0;
}

var a = [ 0, 1, 12, 13, 2, 13 ];
a.sort(comparator);

console.log(a);// [ 0, 1, 12, 13, 13, 2 ]

Concat


각기 다른 여러개의 Array를 합쳐 새로운 Array 개체를 생성한다


var a = [ "a", "b", "c" ];
var b = [ "d", "e", "f" ];

var c = a.concat(b, "g", [ "e", "f", "g" ])

console.log(a);// [ 'a', 'b', 'c' ]
console.log(b);// [ 'd', 'e', 'f' ]
console.log(c);// [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'e', 'f', 'g' ]

Splice


Array 개체에서 주어진 index의 데이터들을 가져와 새로운 Array를 생성한다. 새로운 Array를 생성하며 추가적으로 원래 Array에 새로운에에 데이터를 삽입할 수도 있다. 즉 원래 Array에서는 데이터를 삭제하며, 새로운 삭제된 데이터들로 새로운 Array를 생성한다.


array.splice(start, deletecount, items)

start에 삭제할 범위의 첫번째 index
deletecount에는 첫번째 index인 start를 포함해 삭제할 엘리맨트의 갯수
items에는 삭제후 삭제된 index에 삽입할 데이터 어레이, 또는 데이터.

var a = [ "a", "b", "c" ];
var b = [ "d", "e", "f" ];

var c = a.splice(0, 2);
b.splice(0, 2)

console.log(a);// [ 'c' ]
console.log(b);// [ 'f' ]
console.log(c);// [ 'a', 'b' ]

c.splice(0, 0, "new VAL");

console.log(c);// [ 'new VAL', 'a', 'b' ]

indexOf & lastIndexOf


indexOf와 lastIndexOf 메소드는 둘다 array에서 주어진 데이터를 검색 후, 그 데이터가 저장된 위치, 즉 index를 리턴한다. 아무것도 찾을 수 없을시에는 -1값을 리턴한다.

*indexOf는 배열의 처음(0)부터 검색하지만, lastIndexOf는 배열의 끝(length-1)부터 검색하게 된다.


var a = [ "a", "b", "c", "a" ];

console.log(a.indexOf("a"));// 0
console.log(a.lastIndexOf("a"));// 3
console.log(a.indexOf("t"));// -1

반복문(Iterative Method)


자바스크립의 Array는 기본적으로 5가지 함수 반복문을 제공한다.

  • foreach()
    • Array의 모든 엘리멘트를 파라미터로 주어진 함수에 따라 계산한다. 하지만 어떠한 값도 반환하지 않는다
  • every()
    • Array의 모든 엘리멘트를 파라미터로 주어진 함수에 따라 계산한다. 만약 모든 계산에서 true 값이 나왔다면 true 값을 리턴한다
  • some()
    • Array의 모든 엘리멘트를 파라미터로 주어진 함수에 따라 계산한다. 그 중 1개의  계산에서 true 값이 나왔다면 true 값을 리턴한다
  • filter()
    • Array의 모든 엘리멘트를 파라미터로 주어진 함수에 따라 계산한다. 계산에서 true 값을 리턴하는 엘리멘트의 배열을 리턴한다
  • map()
    • Array의 모든 엘리멘트를 파라미터로 주어진 함수에 따라 계산한다. 각각 계산의 결과값으로 만들어진 배열을 리턴한다

파라미터로 주어지는 함수의 시그니쳐는 언제나


function name(item(데이터값), index(인덱스), array(배열))

이며 사용법은 아래와 같다.


function func(item, index, array) {
    if (item % 2 == 0) {
 return true;
    }
    return false;
}

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
console.log(a);// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

var b = a.every(func);
console.log(b);// false

var c = a.filter(func);
console.log(c); // [ 2, 4, 6, 8, 10 ]

var d = a.some(func);
console.log(d);// true

var e = a.map(func);
console.log(e);// [ false, true, false, true, false, true, false, true, false, true ]

var f = a.forEach(func);
console.log(f);// undefined
//아무것도 반환하지 않기 때문에 f의 값은 undefined 이다.


Reduce & ReduceRight


Reduce 메소드란 ECMAScript 5에서 새로 추가된 기능으로 배열의 모든 데이터에 대해 파라미터로 주어진 함수를 반복해, 최종값을 얻어내는 메소드 이다.
Reduce 메소드는 Array의 처음부터 함수를 시작하며, ReduceRight 메소드는 Array의 끝부터 메소드를 시작한다.
Reduce 와 ReduceRight 메소드 둘 다 파라미터로 받는 함수는 다음과 같은 Function Signature를 가진다.


function func(prev, cur, index, array)
//prev 는 바로 전 반복문까지의 최종 값이며, cur 은 현재 반복문의 순서에 있는 배열내의 데이터값이다.
//index 는 현재 데이터값의 배열 인덱스를 의미하며, array는 현재 배열 개체를 의미한다.

사용법은 아래와 같다.


function func(prev, cur, index, array) {
    if (cur % 2 == 0) {
 return prev - cur;
    }
    return prev + cur;
}

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
console.log(a.reduce(func));// -5
// 위 reduce메소드의 계산과정은 아래와 같다.
// 1 - 2 - 1 + 3 + 2 - 4 - 2 + 5 + 3 - 6 - 3 + 7 + 4 - 8 - 4 + 9 + 5 - 10

console.log(a.reduceRight(func));// 15
// 위 reduceRight메소드의 계산과정은 아래와 같다.
// 10 + 9 + 19 - 8 + 11 + 7 + 18 - 6 + 12 + 5 + 17 - 4 + 13 + 3 + 16 - 2 + 14 + 1

2015년 3월 8일 일요일

[JavaScript] 실행 영역(Execution Context)과 스코프(Scope)

자바스크립트는 타 프로그래밍 언어에 비해 조금더 융통성있는 특별한 실행 영역(Execution Context)과 스코프(Scope)를 가지고 있다. 이로 인해 함수(Function)은 다양한 영역(Context)에서 호출될 수 있고, 그러한 스코프(Scope) 생성하고 유지할 수 있다. 이러한 컨셉은 "Closure"와 같은 특별한 디자인 패턴(Design Pattern)을 자바스크립트에서 구현 가능할 수 있게 만들었다

Execution Context(실행 영역)


흔히들 Context 라 부르기도 하는 Execution Context(실행 영역)는 스크립트가 실행되면서 생성(Global Context)되거나 Function Call에 의해 생성(Active Context) 되게 된다. 각각의 활동 컨텍스트(Active Context)들은 Function Call에 의해 생성되며 생성된 순서대로 Stack 메모리에 된삽입되게 되고, Stack의 FILO 원리대로 현재 실행중인 Context는 Stack의 최상위에 위치하게 된다. 각각의 Context는 언제나 1개의 변수 환경(Variable Environment), 1개의 Lexical Environment, 그리고 1개의 ThisBinding 속성, 총 3개의 부분으로 구성되 있다. 그리고 3 속성 모두 Object 형식으로 저장된다(자바스크립트의 Object 자료형으로 저장되는게 아니다. 그러니 사용자가 Access 할 방법은 전혀 없다).

Lexical Environment Object

한글로 번역(의역)하자면 "구성 환경 객체"이라고 할 수 있겠다. 해당 Context에서 서언된 변수/함수들의 Reference 값을 저장하는 객체이다.(내부 구성은 Variable Environment Object 에 저장된다). Identifiers(변수/함수이름)을 Reference(메모리 참조값)으로 변환할 때 사용된다. Execution Context의 생성 초기시점에는 아래에서 설명할 Variable Environment Object와 정확히 같은 값을 가지나, Context 내부에서 Scope Augmentation이 실행될 시, Variable Environment Object와는 달리 새로운 Identifier 와 그의 Reference 값이 추가된다.

Variable Environment Object

한글로 번역(의역)하자면 "변수 환경 객체"이라고 할 수 있다. 사실 변수 환경 또한 Lexical Environment(구성 환경)에 포함되는 개념이나, 위에서 설명한 Lexical Environment Object가 생성 후에 내부 값이 변할 수 있는 것과 달리
Variable Environment Object는 내부에서 선언된
  • 변수(Variables)
  • 함수 선언(Function Declarations)
  • 함수 매게 변수(Formal Parameters)
들을 저장하기 때문에 Hoisting등 this 키워드를 이용한 Expression에 의해 새로운 변수/함수가 등장하더라도 절대로 값이 변하지 않는다.

ThisBinding Object

ThisBinding 객체는 해당 Execution Context의 this 키워드의 반환 값을 저장한다.
Execution Context에서 사용자가 유일하게 접근(Access)가능한 부분이다. 참고로 this 키워드는 현재 Context가 참조하고 있는 객체를 가리키며 이 값은 어떻게 함수가 호출되었냐에 따라 갈린다

당연하겠지만, Execution Context 내부의 모든 코드가 실행이 완료된 후에는, Execution Context는 Stack에서 삭제되며, 그와 관련된 Lexical Environment Object, Variable Environment Object, ThisBinding Object 모든 구성 환경 값들 또한 삭제된다.

Global Context는 최상위이자 가장 밖의 Execution Context로 function 외부에서 선언된 모든 함수들과 변수들이 이에 포함된다.

웹 브라우져 환경에서 Global Context의 Variable Environment Object는 Window Object이며체, Global Context에서 선언된 함수들과 변수들은 모두 Window Object에 저장된다.이 Execution Context 내부의 모든 코드가 실행이 되고 난 후에는, Window Object에서 삭제되고, 그 안에 저장된 함수들과 변수들 또한 파괴된다.
Window Object 위의 Global Context는 웹페이지가 종료 되고 난 후에야 파괴된다.

Execution Context의 Variable Environment Object는 아래와 같은 형식으로 저장된다.


var a = "글로벌";
var b = 33;

function demo(x) {
  var c = "함수 컨텍스트";
};

demo(20);


위와 같은 자바스크립트 코드는 아래와 같은 Variable Object(변수 객체)를 가지게 된다.


// Variable object of the global context
변수객체(Global Context, 글로벌 컨텍스트) = {
//(웹브라우져 환경에서는 Global Context의 변수 객체가 Window Object이다)
  a: "글로벌",
  b: 33,
  demo: -> demo 함수가 저장된 메모리 주소
};
  
// Variable object of the "test" function context
VO(demo Function Context, 함수 내부의 컨텍스트) = {
  x: 20,
  c: "함수 컨텍스트"
};


Variable Scope(변수 스코프)


흔히들 스코프라 부르는게 바로 Variable Scope이다. Variable Scope는 변수에 접근이 가능한 유효 범위를 뜻한다. 즉 Variable(변수), Function(함수), 그리고 formal parameter(매게 변수)의 접근성과 생존 기간를 말하는 개념이다. Scope 은 Execution Context와 Execution Context의 Lexical Environment 개념과 긴밀한 관계에 있기 때문에, 위의 Execution Context를 잘 이해했다면 Scope도 쉽게 이해할 수 있다.

Scope은 크게 Global Scope 과 Local Scope, 두가지 종류로 나뉘는데, 이 중 Global Scope은 스크립트 실행시 생성되는 Execution Context의 Scope이고, local scope은 function 호출시 생성되는 Execution Context의 scope이다. 참고로 Scope은 if, for 문 같은 블럭에서는 생성되지 않고 오직 function 단위로만 생성된다. 하지만 with 문, 또는 try-catch 문으로 scope을 변경 할 수 있다.


function calculation() {
    var numToAdd = 3;
    var numToSubtract = 1;

    add();
    subtract();

    function add() {
 number = number + numToAdd;
    }

    function subtract() {
 number = number - numToSubtract;
    }
}

var number = 0;

calculation();

console.log(number); // 2
console.log(numToAdd); // 에러
console.log(numToSubtract); // 에러


위의 예제를 보자. 위 코드에서 함수 calculation()은 총 2개의 Variable Environment Object에 접근이 가능하다. 바로 본인의 context 와 연관된 variable environment object와, 본인의 상위 context인 global context의 variable environment object. 그리고 calculation() 함수 내부에서 선언된 함수들인 add 와 subtract는 각각 3개의 Variable Environment Object에 접근이 가능하다. 본인의 것, 그리고 2개의 상위 context의 것들.

이러한 이유로, number 라는 변수, numToAdd, numToSubtract라는 변수는 calculation 함수 내부뿐만이 아니라, add와 subtract함수에서도 접근이 가능해진다. 하지만 calculation 함수의 실행이 끝난 후, 즉 함수에서 벗어난 이후에는 함수의 context의 variable environment object에 접근이 불가능하다. 이러한 이유로 우리는 calculation 함수의 실행이 끝난후에 numToAdd 와 numToSubtract 함수에 접근할 수가 없게 된다.

반대로 함수의 실행이 끝나기 전이나 가장 최근 호출된 함수가 계속 사용될 경우, 즉 함수가 자신의 상위 Execution Context를 참조하고 있는 동안에는 Execution Context는 절대로 파기되지 않는다. 바로 이 점을 이용해 자바스크립트에서 Closure라는 기법을 사용할 수 있게 된다.

이게 바로 Variable Scope, 변수 유효 범위이다.

2015년 3월 3일 화요일

[JavaScript] 자바스크립트의 객체와 원시자료형, 그리고 함수의 매개변수(Function Argument)

자바스크립트(ECMAScript)의 변수는 타 프로그래밍 언어에 비해 매우 자유로운 선언과 사용이 가능하다. C#이 .Net Framework 3.5에서야 구현한 Var 키워들 자바스크립트(ECMAScript)는 태생부터 가지고 있었고, 이로 인해 var 키워드 하나로 각기 다른 타입의 데이터들을 쉽고 간단하게 다룰 수 있게 만들었다.
변수에 대해 잠시 다뤄보자. 우리가 선언하는 변수(Variable)들은 선언과 초기화시 특정 메모리(RAM) 영역을 가리키게 된다. 말인즉,


var a = 1;
var b = "TEST";


위와 같이 변수 a 와 b 를 선언할 때, 메모리 영역에서는 ECMAScript 코어 엔진이 사용되지 않는 영역에 1과 "TEST"라는 값을 저장하게 되고, a 와 b 같은 변수 이름은 그져 그 메모리 영역을 가리키는데 사용될 뿐인 것이다. 이 때문에 변수의 값과 또는 타입은 스크립트가 실행되는 와중에도 언제든 변경이 가능하게 되는것이다.

여기서 문제는, RAM 메모리가 무한하지 않다는 것인데, 그렇기 때문에 메모리가 할당되기만 하고 할당된 메모리를 다시 풀어주지 않는다면 스크립트가 실행되고 새로운 변수가 선언될 때마다 메모리가 서서히 차버리고 결국에는 "Out of Memory"가 생기고 실행중인 프로그램이 크래쉬 되버릴게 분명하다. 여기서 바로 Scope(변수 영역)의 개념이 등장하게 된다. 변수 영역(Scope)이란 변수가 사용되는 영역을 가리킨다, 즉 이러한 영역의 스크립트 실행이 모두 끝나게 되면, 당연하게도 그 변수는 더 이상 사용되지 않게되니, 할당된 메모리를 풀 수 있게 된다.

다시 자바스크립트로 돌아가자. 자바스크립트(ECMAScript)의 Data Type은 크게 2가지 종류로 나뉜다, Primitive Data Type(원시 자료형)과 Composite/Reference Data Type(객체). Primitive Data Type 에는 String(문자열), Number(숫자), Undefined, Null, Boolean, 이렇게 총 5개의 자료형이 존재한느데 이 자료형들의 공통점은 모두 다 변수 선언(Declaration), 초기화(Initialization), 어싸인(Assign)시 값이 저장된 메모리 영역에 직접적으로 접근한다는건데, 즉 변수에 새 값이 어싸인 될 시에 변수에 할당된 메모리 블럭에 저장된 값을 바로 변경한다는 뜻이다, 이 때문에 이런 Primitive Data Type 들은 Access By Value의 속성을 가지게 된다. 아래 코드를 보자


var a = 33;
var b = a;
b = 44;

console.log(a); // 33
console.log(b); // 44


위의 코드에서는 총 3개의 변수가 선언됬다. a, b, 그리고 c. 자바스크립트에서의 메모리 할당은 변수 초기화(Initialization)시 일어나게 된다. 즉 = 이라는 키워드가 변수에 처음 사용될 때만 메모리가 할당된다는 말이다. 우선 a 라는 변수에 33이라는 숫자를 저장했다. 이는 a 라는 변수에 할당된 메모리에 바로 저장되게 된다.

그렇다면 b 라는 변수는 어떨까, "var b = a" 구문은
b라는 변수가 a 라는 변수를 저장하게 된다고 말하고 있다. 하지만 실제로 변수 b는 자신에게 할당된 메모리 블럭에 변수 a(또는 변수 a가 저장된 메모리 블럭 주소)를 저장하는것이 아닌 변수 a의 메모리 블럭에 저장된 값을 저장한다. 즉 변수 a 에게 할당된 메모리 블럭을 복사해서 자신의 메모리 블럭에 덮어 씌운다는 말이다.

이때문에 b = 44 라는 구문을 이용해서 b에 새로운 값을 어싸인 해주거나, a 의 값을 변경하더라도 다른 변수의 값은 변하지 않게 된다. 즉 변수 a 와 변수 b를 저장하는데 2개의 메모리 블럭이 할당 된것이다.

하지만 Composite/Reference Data Type, 주로 객체라 불리는 이 자료형은 어떻게 될까?

Primitive Data Type 과 Composite/Reference Data Type의 가장 큰 차이는 바로 Primitive Data Type 이 변수의 값을 저장할 때, Composite/Reference Data Type은 변수의 값이 저장된 힙(Heap) 메모리의 주소값을 저장한다는 것이다. 즉 Primitive Data Type이 변수에 할당된 메모리 블럭과 바로 연결되있다면, Composite/Reference Data Type은 변수의 값이 저장된 메모리 블럭의 주소를 가지고 있고, 자바스크립트 엔진이 변수가 가지고 있는 메모리 주소를 이용해서 변수의 값에 접근한다는 뜻이다. 이렇한 이유로, Composite/Reference Data Type은 Access By Reference 의 속성을 가지고 있다.


var a = {
 name : "Object a",
 message : "This is Object a"
};
var b = a;
b.name = "Object a changed to Obejct b";

console.log(a);
// { name: 'Object a changed to Object b',
//  message: 'This Object is changed to b' }
console.log(b);
// { name: 'Object a changed to Object b',
//  message: 'This Object is changed to b' }


변수 a 는 초기화(Initialization)시 Heap 메모리에 생성된 객체의 메모리 주소를 저장하게 된다. 그리고 var b = a 라는 구문에서, 변수 a 초기화시 생성된 객체를 변수 b 에 복사하는게 아닌, a가 가진 메모리 주소, 즉 그 객체를 가리키는 메모리 주소를 복사하게 되는 것 이다.
이 때문에 변수 a 와 b 는 모두 같은 메모리 위치를 가리키게 된다, 즉 둘다 같은 객체를 가리키게 되는 것이다. 이 때문에 변수 a 에 적용되는 변화나, b 에 적용되는 변화는, 모두 같은 객체에 적용되게 된다.

이러한 자료형의 속성은 함수(Function)으로 아규먼트(Argument)를 넘겨줄 때 두드러지게 되는데

위에서 다룬것 처럼, Primitive Data 는 Value 를 넘겨주게 되고, Object는 Reference, 메모리 주소를 넘겨주게 된다.
그렇기 때문에 아래의 코드와 같은 결과가 나오게 된다.


function test(a, b) {
    a = 44;
    b.name = "name changed to c";
}

var a = 33;
var b = {
    name : "test object"
};

test(a, b);

console.log(a); // 33
console.log(b); // { name: 'name changed to c' }


이 차이를 잘 기억하도록 해, 후에 Argument passing 을 제대로 못해서 잘못된 스크립트를 적는 일이 없도록 하자

2015년 2월 26일 목요일

[JavaScript] Hoisting(호이스팅) 이란?

부제 : 함수 선언과 함수 표현의 차이
Function Declaration vs Function Expression


함수 선언과 함수 표현의 가장 큰 차이는 바로 Hoisting이다.
이걸 제대로 이해하기 위해서는 Execution Context에 대한 이해가 필수이니 이 글(클릭하기)를 읽고 와주길 바란다.

우선 Hoisting에 대해 간단히 말하자면, 후선언된 변수나, 함수들이 해당 Scope에서 최상위에 위치하는걸 뜻한다.
자바스크립트엔진은 해당 Execution Context의 생성시, 즉 Runtime 시점에서 변수선언문이나 함수선언문을 읽기 전에 선언된 변수와 함수들을 다른 무엇보다도 먼져 읽어 Scope의 최상위에 위치시킨다. 이 덕에, 훨씬 뒤에서 선언된 함수들과 변수들을 그 전에 사용이 가능하다.

*참고로 변수의 경우에는 변수 선언(Variable Declaration)만 Hoisting 된다, 즉 Variable Initialization이 있다면 변수가 선언은 되나, 변수에 어떤 값도 들어가지 않는다는 뜻.

*또한 함수의 Execution Context의 생성은 함수 호출시 이뤄지므로, 함수 내부에서 선언된 변수들은 함수 호출시에서야 Hoisting 된다.


console.log(internalFunction);// 함수가 Hoisting 됐을뿐, 아직 Execution Context는 생성되지 않았으므로, 에러가 발생한다.

functionDeclaration();// 함수가 성공적으로 호출되며 Function Declaration가 프린트 된다.

console.log();// Undefined 가 프린트 된다. 변수에 "vart"라는 값이 어싸인되는것은 아래 코드가 읽히는 시점에서기 때문.

function functionDeclaration() {
    console.log("Function Declaration");
    
    function internalFunction(){
    }
}

var t = "vart";

functionExpression(); // 에러가 발생

console.log(novalue); // Context에서 선언된적 없는 변수인 novalue를 호출할 수가 없어 에러가 발생

var functionExpression = function() {
    console.log("Function Expression");
}


그럼 이제 Hoisting을 제대로 이해했는지 확인도 할겸 아래 코드의 결과를 알아 맞춰보자.


function test1() {
    function internalFunction() {
 return true;
    }

    return internalFunction();

    function internalFunction() {
 return false;
    }
}

console.log(test1());

function test2() {
    var internalFunction = function() {
 return true;
    }

    return internalFunction();

    var internalFunction = function() {
 return false;
    }
}

console.log(test2());

function test3() {
    return internalFunction();

    var internalFunction = function() {
 return true;
    }

    var internalFunction = function() {
 return false;
    }
}

console.log(test3());

function test4() {
    var internalFunction = function() {
 return true;
    }

    return internalFunction();

    function internalFunction() {
 return false;
    }
}

console.log(test4());






정답은


  • test1 = false
  • test2 = true
  • test3 = TypeError: undefined is not a function
  • test4 = true
    • 참고로 test4는 hoisting 되었던 함수가 변수 표현문으로 다시 정의되어 true값을 반환하게 된다.
    • 기억하자, 자바스크립트에서는 함수 또한 객체에 불과하다. 그러니 재정의될 수 있다.