회원가입∴아이디/비번찾기
홈으로


[번역] javascript 의 this 란 무엇인가
12년 전
자바스크립트를 배우는 대부분의 사람들은 다른 언어를 배워본 경험이 있다. 자바스크립트는 다른 언어들과 다른 면들이 있는데, 이를 잘 이해하지 못하면, 자바스크립트를 잘 이용하기가 어렵다. 우리들만의 잘못은 아니다. 자바스크립트는 scheme 처럼 동작하도록 설계되었는데, 마치 c 처럼 생겼기 때문이다. 이 문서는 lexical scope 과 this 변수에 대해서 설명하고, 이들을 자바스크립트에서 어떻게 사용해야하는지에 대해서 얘기할 것이다.

It's all about where you are.

모든 프로그래밍 언어에는, 현재 scope 와 현재 context 라는 개념이 있다. 자바스크립트에는 lexical scope 과 this 라는 context 가 있다.

자바스크립트에서는 모든 새로운 scope 들은 function 의 정의로 인해 생성된다. 하지만 C 같은 언어와는 다르게, 이것이 scope 를 만드는 유일한 방법이다. 반복문도, if 문도, {} 블락 도 scope 를 만들지 않는다. 이런 단순함은 축복임과 동시에 저주다. 예를들어 scope 를 생성하는 것을 설명해보겠다.

global scope 의 예이다:
// global 변수를 선언한다.
var name = "Tim";
var age = 28;

// global scope 에서 이들을 접근한다.
name;

Output

=> 'Tim'

local scope 의 예이다:
// 함수에서 local 변수들을 생성한다.
function newScope() {
      var name = "tim";
      var age = 28;
    }
// global scope 에서 local 변수를 접근하려 하면
// 에러가 발생한다.
name;

Error
ReferenceError: name is not defined

Lexical Scope

Lexical scope 은 closure 를 가능하게하는 핵심 요소이다. wikipedia 의 closure 와 lexical scope 에 대한 글이다:


컴퓨터 공학에서, closure 는, lexical 환경에 bound 된 free 변수를 갖는 first-class 함수이다. 이런 함수는 free 변수들을 "closed over" 한다고 표현한다. 클로저는 이들 free 변수들의 scope 내에 정의된다. 그리고 이 변수들은 closure 가 살아있는 동안 살아있게 된다.

이게 무슨 소리인가? 다음 예제를 보라:
var name = "outer";
function one() {
    var name = "middle";
    var other = "findme";
    function two() {
        var name = "inner";
        // `name` 은 "inner" 이고, `other` 는 "findme" 이다
        console.dir({name: name, other: other});
    }
    two();
    // `name` 은 "middle" 이고 `other` 는 "findme" 이다
    console.dir({name: name, other: other});
}
one();
// `name` 은 "outer" 이고 `other` 는 undefined 이다
console.dir({name: name});
console.dir({other: other});

Output
{ name: 'inner', other: 'findme' } { name: 'middle', other: 'findme' } { name: 'outer' }

Error
ReferenceError: other is not defined

안쪽 scope 에 있는 local 변수가 바깥 scope 에 있는 같은 이름의 변수를 가려버린다는 것을 알 수 있다. 그리고 바깥쪽에서는 안쪽의 변수를 볼 수가 없다. lexical scope 이란 변수의 scope 이, 코드에서의 변수 위치에(얼마나 안쪽이냐) 따라 결정되는 것을 말한다. 안쪽 함수에 접근하기 위해 어떤 경로를 거치는 것과는 아무 상관이 없다.
// closure 함수를 리턴하는 함수를 만든다.
function myModule() {
    var name = "tim", age = 28;
    return function greet() {
        return "Hello " + name + ".  Wow, you are " + age + " years old.";
    }
}
// `myModule()` 을 호출하여 closure 를 얻는다.
var greeter = myModule();
// closure 를 호출한다
greeter();

Output

=> 'Hello tim. Wow, you are 28 years old.'

name 과 age 변수는 myModule 함수의 로컬 변수이다. 하지만 글로벌 scope 에서 greeter() 를 호출할 때 에러가 발생하지 않는다. 그 이유는 greet 함수가 name 과 age 를 자신의 lexical scope 에 가지기 때문이다. 따라서 마치 이 변수들이 자신의 local 변수인양 사용할 수가 있다. 기본적으로 변수를 찾을 때에는, 자신의 scope 에서 먼저 찾고 그 위의 scope 들로 차례 차례 찾는다.

The context of "this"

자바스크립트는 this 라는 특별한 키워드를 이용한다. this 는 다른 여타 변수들과 동일하게 동작하나, 수정이 불가능하다는 점이 다르다. 이 변수는 context (역주: 환경) 을 가리키는 참조 객체로 쓰인다. 객체이기 때문에, . 이나 [] 을 이용하여 속성들을 접근할 수도 있다. 신기한 점은 여러분이 함수를 호출하는 context 에 따라 this 의 값이 달라진다는 것이다. 보통의 경우에는 context 는 message 를 받는 (역주: 호출받는) 객체이다. 예를들어:
var Person = {
    name: "Tim",
    age: 28,
    greeting: function () {
        return "Hello " + this.name + ".  Wow, you are " + this.age + " years old.";
    }
};

Person.greeting();

Output

=> 'Hello Tim. Wow, you are 28 years old.'

Person.greeting 에서 Person.name 과 Person.age 를 접근하였다.

"this" is where it bites

위 코드는 다른 언어의 클래스 객체처럼 보인다. 하지만 거기에 함정이 있다. this 가 꼭 Person 일 것이라는 보장이 없다. 예를들어 greeting 함수를 다음처럼 저장한다고 해보자:
  var greeting = Person.greeting;
  greeting(); // `this.name` 과 `this.age` 는 undefined 가 된다

Output

=> 'Hello undefined. Wow, you are undefined years old.'

위 greeting 함수에서 this 는 global 객체이지 Person 객체가 아니다. 또 다른 예를 보자:
var Dog = {
    name: "Alfred",
    age: 110,
    greeting: Person.greeting
}

Dog.greeting(); // 정상작동하며, Dog 의 데이터를 보여준다.

Output

=> 'Hello Alfred. Wow, you are 110 years old.'

Dog 과 Person 의 greeting 함수는 같은 함수이다. 이 두 함수는 메모리상의 같은 객체를 참조한다. 하지만 어디서 불렸는지에 따라 this 의 값이 변할 수 있다. 기본적으로 함수호출시 . 앞에 있는 객체가, 함수에서 this 가 된다. Dog.greeting() 에서는 this 가 Dog 이고, Person.greeting() 에서는 Person 이다. . 앞에 아무것도 없을 때에는 글로벌 객체가 this 가 된다.

Taming "this"

자바스크립트는 call 과 apply 라는 Function.prototype 의 함수들을 제공한다. 거의 기능이 같은데 인자를 다른 형태로 받는다. 이전 예제를 이어서, greeting 이 없는 새로운 객체를 만들어 보자. 하지만 이 객체로 greeting 함수를 호출할 수 있다.
var Alien = {
    name: "Zygoff",
    age: 5432
}

Person.greeting.call(Alien);

Output

=> 'Hello Zygoff. Wow, you are 5432 years old.'

여기서 우리는 Person.greeting 함수를 부르면서 this 로 Alien 을 쓰도록 하는 것이다. call 대신 apply 를 써도 무방하다. 전달하는 인자가 없기 때문이다.

age 와 name 속성을 갖는 아무 객체나 사용할 수 있는 함수를 만들어보자:
function makeOlder(years, newname) {
    this.age += years;
    if (newname) {
        this.name = newname;
    }
}

이 함수는 this 객체에 years 를 더하고, 선택적으로 이름도 변경한다. 이 함수는 어떤 특정 객체와 연결되어 있지 않다. (바깥 scope 의 변수를 사용하지 않는 함수는 독립적인 함수이다). call 이나 apply 를 이용하여 위 함수를 다음과 같이 이용한다:
makeOlder.call(Person, 2, "Old Tim");
makeOlder.apply(Dog, [1, "Shaggy"]);
console.dir({Person: Person, Dog: Dog});

Output
{ Person: { name: 'Old Tim', age: 30, greeting: [Function] }, Dog: { name: 'Shaggy', age: 111, greeting: [Function] } }

call 은 전달받은 인자들을 하나씩 함수로 전달된다. apply 는 배열 인자를 전달받아 함수로 전달한다.

Binding "this"

때때로, 우리는 OOP (객체지향프로그래밍) 스타일을 너무 좋아한 나머지, 자바스크립트도 OOP 처럼 동작하게 만들려 한다. 어떻게 함수를 호출하느냐에 따라 this 가 변한는 것이 싫다. 콜백 함수를 인자로 받는 이벤트를 설정할 때 이와 같은 일이 많이 발생한다.
Cart = {
    items: [1,4,2],
    onClick: function () {
        // this.items 로 무엇인가를 한다.
    }
}
$("#mybutton").click(Cart.onClick);

문제 없어 보이지만, 재앙이 될 수 있는 코드다. onClick 이 아닌 Cart.onClick 를 사용하더라도 말이다. jQuery click 함수는 Cart.onClick 함수를 받지만, 이 함수를 호출할 때에는, 이 함수가 Cart 로부터 왔다는 것을 모를 것이다. (역주: Cart.onClick() 이 아닌 var eventFunc = Cart.onClick; eventFunc() 인 셈이기 때문이다)

closure 와 lexical scope 지식을 이용하여, this 가 다른 OOP 언어에서처럼 동작하게 해보자.
  $("#mybutton").click(function () { Cart.onClick() });

closure 를 만들고 Cart.onClick() 을 호출한다. 문제는 (읽기 어렵다는 것외에도) 함수가 인자를 받지도 값을 리턴하지도 않는다는 것이다.
  $("#mybutton").click(function () { return Cart.onClick.apply(Cart, arguments) });

이렇게 하면 되지만, 더 읽기 어려운 코드가 되었다. arguments 는 현재 가장 안쪽의 함수에게 전달된 인자들을 가지고 있는 배열이다.

Cart 가 전역적으로 접근 가능한 singleton 객체라면, this 대신에 Cart 를 사용할 수 있을 것이다. 하지만 그렇지 않은 경우가 더 많다.

Cart.onClick 의 this 가 항상 Cart 를 가리키게 할 수는 없을까?
function bind(fn, scope) {
    return function () {
        return fn.apply(scope, arguments);
    }
}
Cart.onClick = bind(Cart.onClick, Cart);
$("#mybutton").click(Cart.onClick);

이 방법말고도 다른 방법들이 있지만, 보통 올바른 해결책은 아니다. javascript 를 다른 언어처럼 만들고 싶다면, 좋은 방법이지만, javascript 의 방법을 배우는 것이 더 좋다.

위에서 우리는 scope 을 포함하는 closure 를 만들었다. (역주: scope 보다는 context 가 이 글에서의 쓰임새에 더 맞는것이 아닌가 싶다) 그리고 Cart.onClick 을 이 closure 로 변경 하였다. 그리고 apply 를 이용하여 인자를 전달하고 값을 리턴하였다.

Var statements

var 문장은 단순히 어떤 변수가 어떤 scope 에 속하는지를 나타내는 키워드이다. var 를 사용하지 않는다면, 모든 변수는 글로벌 변수가 될 것이다.
global_var = true;
function someFunc() {
    another_global = 42;
    var local_var = 5;
}

다음 같은 반복문에서 매우 위험하다:
function sum(start, end) {
    var n = 0;
    for (i = start; i <= end; i++) {
        n += i;
    }
    return n;
}

function nested_sum(num) {
    var n = 0;
    for (i = 1; i <= num; i++) {
        n += sum(1, i);
    }
    return n;
}

nested_sum(3); // 10 을 원하지만, i 값이 꼬인다.

Output

=> 7

두개의 룹에서 i 변수는 같은 변수로, 안쪽에서 i 가 증가하여 바깥의 룹에 영향을 미치게 된다. (역주: (1) + (1+2+3) = 7) 또 n 앞에 var 를 붙이지 않아도, 서로 간섭하여 잘못된 값을 리턴할 것이다.

var 를 사용할 때 주의점이 있다. 함수 어디에 var 를 놓아도 상관없다. 어떤 함수에 var 를 놓느냐만 중요하다. 그리고 이렇게 선언한 변수의 scope 은 함수의 인자들 보다 안쪽이다 (역주: 응?)
name = "Tim";
function greet() {
    console.dir(name);
    // name 은 undefined 이다.
    // 코드 아래 쪽에 var 로 변수를 선언하였기 때문이다
    var name;
}
greet();

function greet2(name) {
    console.dir(name);
    // name 은 "Tim" 이다. 왜냐하면 함수의 인자들은 특별하기 때문이다.
    // 한참 후에, var name 이 있더라도 말이다
    var name;
}
greet2("Tim");

Output
undefined 'Tim'

이것이 jslint 이 모든 var 선언을 함수의 젤 위에 놓으라는 이유이다. 나는 한때 이문제로 골머리를 썩인적이 있다.

--------------------------------------------------------------------------------

Conclusion

자바스크립트에서 scope 을 이해하려면 다음을 이해해야 한다:
•새로운 scope 은 function 을 통해서만 만들 수 있다. for ... in 등 다른 어떤 것도 scope 을 만들지 않는다.
•var 문장은 변수를 현재의 scope 에 로컬 변수로 선언한다. var 문장을 사용한 이후가 아닌, var 문장을 포함하는 scope 에 선언하는 것이다. 이 로컬 변수는 바깥 scope 의 같은이름 변수를 가린다.
•this 와 arguments 를 제외한 모든 변수는 lexical scope 을 따른다. 즉 이들 변수의 의미는 코드상의 위치 에 따라 정해진다.
•this 와 arguments 변수는 모든 중첩 마다 변화한다. 이 변수들을 closure 에 보관하려면, lexical scope 을 따르는 다른 변수를 이용하여 this 나 arguments 들을 참조하여야 한다.
•this 의 값은 함수가 호출된 방법 에 의해 정해진다. apply 와 call 을 이용하여 이를 조절할 수 있다.

위 룰에 예외가 있다. function decomposition, eval, with 키워드를 이용할 때이다. (역주: 움 뭔지 모르는 것들이다) 이 때에도, 위 룰들은 따르지만, 예상과는 다른 방법일 것이다. function decompilation + eval 은 함수를 새로운 lexical scope 으로 이동시킨다. 그리고 with 를 이용하여 this.name 이 local lexical 변수인 것처럼 만들 수도 있다.
추천추천 : 670 추천 목록
번호 제목
2,891
입력 필드에서 특정단어(예:#err)가 포함되었을 때 실시간 감지 및 경고창 띄우기
2,890
데이터베이스 최적화와 쿼리 효율성을 높이 검색 성능을 개선하는 방법
2,889
간단한 게시판 만들기
2,888
PHP의 php.ini 파일에서 설정할 수 있는 주요 항목들을 카테고리별로 정리
2,887
유튜브 동영상의 썸네일 이미지를 체크하여 유효한 영상이아닐때 연결된 체크박스를 자동으로 체크
2,886
이미지 URL이 유효하지 않을 때, 해당 이미지와 연결된 체크박스를 자동으로 체크
2,885
HTTPS로 접속한 사용자를 강제로 HTTP로 리디렉션 하려면
2,884
PHP에서 MP3 파일을 직접 읽고 스트리밍 하기
2,883
현재 페이지가 location.reload()에 의해 새로고침되었는지
2,882
텍스트 파일을 읽고, 각 줄의 끝에서 6글자를 삭제한 후, 결과를 새로운 파일에 저장합니다.
2,881
cURL을 사용하여 리다이렉트를 따라가 최종 URL 가져오기
2,880
[PHP] $_SERVER 환경변수
2,879
10진수 <-> 16진수 변환기 PHP소스
2,878
텍스트에 직접 그라데이션 색상을 적용하려면?
2,877
CSS를 사용하여 요소의 내용물에 따라 width를 조정하는 방법
2,876
웹서버 ip 확인
2,875
웹호스팅의 절대경로를 확인
2,874
input 입력 필드 앞뒤 공백 실시간 제거
2,873
Placeholder 포커스시 감추기
2,872
MySQL 중복된 데이터를 삭제
2,871
MySQL 중복 데이터 확인
2,870
sessionStorage.getItem 와 sessionStorage.setItem
2,869
제이쿼리 랜덤으로 배경색 변경
2,868
preg match에 관한 정규식
2,867
Stream an audio file with MediaPlayer 오디오 파일 스트리밍 하기
2,866
Audio Streaming PHP Code
2,865
PHP $ SERVER 환경 변수 정리
2,864
Vimeo (비메오) API 를 사용하여 플레이어 컨트롤하기
2,863
iframe 사용시 하단에 발생하는 공백 제거방법
2,862
아이프레임(iframe) 전체화면 가능하게 하기
목록
뮤직트로트 부산광역시 부산진구 가야동 ㅣ 개인정보취급방침ㅣ모바일버전
Copyright ⓒ musictrot All rights reserved.