본문 바로가기
FRONT-END/JavaScript

03. this (코어 자바스크립트)

by 랄라J 2024. 9. 22.

책을 읽은 지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해 놓았습니다.

 

이전 글 : [FRONT-END/JavaScript] - 02. 실행 컨텍스트 (코어 자바스크립트)

 

02. 실행 컨텍스트 (코어 자바스크립트)

책을 읽은지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해놓았습니다. 이전 글 : [FRONT-END/JavaScript] - 01. 데이터

rarla-j.tistory.com

 

this

this란?

함수와 객체(메서드)의 구분이 느슨한 자바스크립트에서 함수와 객체(메서드)를 구분하는 거의 유일한 기능입니다.

this는 앞서 공부한 실행 컨텍스트에 thisBinding이 가리키는 곳을 의미합니다. 즉, 실행컨텍스트가 생성될 때 함께 결정됩니다
실행컨텍스트는 함수가 호출될 때 생성되었죠. 즉, this는 함수를 호출할 때 결정됩니다.

 

전역 공간에서의 this

전역 객체를 가리킵니다. 전역 컨텍스트를 생성하는 주체가 전역 객체이기 때문입니다.
전역객체는 자바스크립트 런타임 환경에 따라 다른 이름을 가지는데, 브라우저 환경에서는 window, node.js 환경에서는 global입니다.

 

전역 객체라는 용어가 나왔으니, 전역 변수와 전역 객체의 차이도 알아보고 갑시다.

전역변수와 전역객체

var a = 1
console.log(a) // 1
console.log(window.a) // 1
console.log(this.a) // 1

var a에 1을 할당했을 뿐인데, a, window.a, this.a을 출력하면 모두 1이 나오는 것을 알 수 있습니다.

전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당하기 때문입니다.

더 나아가 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작합니다. 여기서 특정 객체는 실행 컨텍스트의 LexicalEnvironmnet를 의미합니다. 실행 컨텍스트는 변수를 수집해 LexicalEnvironment의 프로퍼티로 저장합니다.

단, 삭제 명령에서는 전역변수 선언과 전역객체의 프로퍼티 할당 사이에 차이가 존재합니다.

var a = 1
delete window.a // false
console.log(a, window.a, this.a) // 1 1 1

var b = 1
delete b // false
console.log(b, window.b, this.b) // 1 1 1

window.c = 1
delete window.c // true
console.log(c, window.c, this.c) // Uncaught ReferenceError : c is not defined

window.d = 1
delete d; // true
console.log(d, window.d, this.d) // Uncaught ReferenceError : d is not defined

처음부터 전역 객체의 프로퍼티로 할당한 부분은 delete 시 삭제가 되지만, 전역변수로 할당한 경우에는 삭제되지 않습니다.

이는 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역객체에 프로퍼티로 할당하는데 추가적으로 해당 프로퍼티의 configurable 속성(변경 및 삭제 가능성)을 false로 정의하기 때문입니다.

 

함수와 메서드의 this

우선 함수와 메서드란 미리 정의한 동작을 수행하는 코드 뭉치를 의미한다. 이 둘에는 독립성 부분에 차이가 존재합니다.
함수로서 호출하는 경우는 그 자체 독립적인 기능을 수행하나, 메서드로 호출하는 경우에는 자신을 호출한 대상 객체에 관한 동작을 수행하기 때문입니다.

함수와 메서드의 this는 함수 앞에 점(.) 또는 대괄호 표기법 여부로 판단합니다.
점 표기법이든 대괄호 표기법이든, 어떤 함수를 호출할 때 그 함수 이름 앞에 객체가 명시되면 메서드로 호출하는 것이고 . 앞이 this
그 외
의 경우는 함수로 호출하는 것이고 전역객체가 this가 됩니다.

단, 메서드는 어떤 함수를 객체의 프로퍼티에 할당한다고 그 자체로서 무조건 메서드가 되는 것이 아니라 객체의 메서드로서 호출한 경우에만 메서드로 동작하고 그렇지 않으면 함수로 동작한다는 걸 개념적으로 인지하고 있어야 합니다.

var func = function(x) {
    console.log(this, x);
}
func(1); // Window {...} 1

var obj = {
    method: func
};
obj.method(2) // {method: f} 2

위 코드처럼 동일한 함수이나 변수에 담아 호출한 경우와 obj 객체의 프로퍼티에 할당해 호출한 경우 this가 다름을 확인할 수 있습니다. 

 

아래 코드와 같이 메서드로서 호출하는 경우 호출 주체는 바로 함수명 앞의 객체로 마지막 점 앞에 명시된 객체가 this가 됩니다.

var obj = {
    methodA: function () { console.log(this)' },
    inner: {
    	methodB: function () { console.log(this)' },
    }
};

obj.methodA(); // {methodA: f, inner: {...}} -> this는 obj
obj['methodA'](); // {methodA: f, inner: {...}} -> this는 obj

obj.inner.methodB(); // {methodB: f} -> this는 obj.inner
obj.inner['methodB'](); // {methodB: f} -> this는 obj.inner
obj['inner'].methodB(); // {methodB: f} -> this는 obj.inner
obj['inner']['methodB'](); // {methodB: f} -> this는 obj.inner

 

함수로 호출할 때는 this가 지정되지 않습니다. 실행 컨텍스트를 활성화할 당시 this가 지정되어있지 않은 경우 this는 전역 객체를 바라보게 됩니다. 즉, 함수에서의 this는 전역 객체를 가리킵니다.

 

아래 코드를 보고 잘 이해했는지 확인해 봅시다. 멈춰서 정답을 고민해 보세요~!

var obj1 = {
    outer: function() {
    	console.log(this); // (1)
        
        var innerFunc = function() {
            console.log(this); // (2) (3)
        }
        innerFunc();

        var obj2 = {
            innerMethod: innerFunc
        };
        obj2.innerMethod();
    }
};

obj1.outer();

 

정답은 (1) obj, (2)Window, (3)obj2 입니다!

정리하자면 this 바인딩에 관해서는 함수는 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지를 확인하면 됩니다.

 

메서드의 내부 함수에서 this를 우회하는 방법

개발자인 입장으로서 호출 주체가 없을 경우 자동으로 전역객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 그대로 상속받아 사용하기 원하지만, 언어 개발이 그렇게 된 만큼 인지하고 고려하며 개발해야 합니다.

이를 우회해 원하는 대로 동작하게 하기 위해 자주 사용되는 방법으로는 여러 가지가 있습니다.

1. self 등의 변수를 활용해 상위 스코프의 this를 저장 후 내부 함수에서 활용하기

var obj = {
    outer: function() {
    	console.log(this); // this는 outer
        
        var innerFunc1 = function() {
            console.log(this); // this는 Window
        }
        innerFunc();
        
        var self = this;
        var innerFunc2 = {
            console.log(self); // this는 outer
        };
        innerFunc2();
    }
};

obj1.outer();

 

2. this를 바인딩하지 않는 화살표 함수 활용하기

화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있습니다.

var obj = {
    outer: function() {
    	console.log(this); // this는 outer
        
        var innerFunc1 = () => {
            console.log(this); // this는 outer
        }
        innerFunc();
    }
};

obj.outer();

 

3. call, apply, bind 메서드 활용하기 (명시적으로 this 바인딩)

call, apply

call과 apply는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령입니다 
첫 번째 인자로 this 바인딩을, 이후 인자들은 호출할 함수의 매개변수를 의미합니다.

둘의 차이는 인자를 넘기는 방식에 있습니다.
call과 달리 apply는 인자들을 배열로 받아 배열의 요소들을 호출할 함수의 매개변수로 지정하는 차이가 있습니다.

// call을 활용한 코드
var func = function (a, b, c) {
    console.log(this, a, b, c);
};

func(1, 2, 3); // Window 1 2 3
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6

// apply를 활용한 코드
var func = function (a, b, c) {
    console.log(this, a, b, c);
};

func(1, 2, 3); // Window 1 2 3
func.apply({ x: 1 }, [4, 5, 6]}; // { x: 1 } 4 5 6

추가로, apply는 여러 개의 인수를 묶어 하나의 배열로 전달하고 싶은 경우에도 활용할 수 있습니다.

 

bind

call과 유사하나 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드입니다.

즉시 호출하지 않기 때문에 1) 함수에 this를 미리 적용하거나 2) 부분 적용 함수를 구현하는 2가지 목적을 지니고 있습니다.

var func = function (a, b, c) {
    console.log(this, a, b, c);
};
func(1, 2, 3); // Window 1 2 3

var bindFunc1 = func.bind({ x: 1 }};
bindFunc1(1, 2, 3) // { x: 1 } 1 2 3

var bindFunc2 = func.bind({ x: 1 }, 4};
bindFunc2(5, 6) // { x: 1 } 4 5 6

console.log(bindFunc1.name); // bound func

bind 메서드를 적용해 새로 만든 함수는 name 프로퍼티에 bound 접두어가 붙습니다.

 

콜백 함수에서의 this

우선 콜백 함수란 함수 A의 제어권을 다른 함수나 메서드 B에게 넘겨주는 경우 함수 A를 콜백함수라고 합니다.

콜백함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조합니다.

setTimeout(function() { console.log(this); }, 300); // (1)

[1,2,3,4,5].forEach(function (x) {
    console.log(this, x); // (2)
});

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
    .addEventListener('click', function (e) {
    	console.log(this. e); // (3)
    });

정답은 1) 전역 객체, 2) 전역 객체, 3) 앞서 지정한 엘리먼트와 클릭 이벤트에 관한 정보가 담긴 객체입니다.

 

생성자 함수 내부에서의 this

생성자 함수는 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수를 말합니다. 생성자를 흔히 붕어빵 틀에 비유합니다.

자바스크립트에서는 함수에 생성자로서의 역할을 부여하는데, new 키워드와 함께 함수를 호출하면 해당 함수가 생성자로 동작합니다.

어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 새로 만들 구체적인 인스턴스 자신이 됩니다.

var Dog = function (name) {
	this.bark = "멍멍";
    this.name = name;
}

var bori = new Dog('보리', 3);
console.log(bori); // Dog { bark: "멍멍", name: "보리" }

 

생성자를 호출하면

1. 생성자의 prototype 프로퍼티를 참조하는 __proto__라는 프로퍼티가 있는 객체를 만들고

2. 미리 준비된 공통 속성 및 개성을 해당 객체에 부여합니다.

 

+. 생성자 내부에 다른 생성자와 공통된 내용이 있는 경우 call/apply를 활용해 다른 생성자를 호출하면 코드의 중복을 줄일 수 있습니다.

function Person(name) {
    this.name = name;
}

function Girl(name, gender) {
    Person.call(this, name);
    this.gender = 'female';
}

 

그 외로는 콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this를 지정할 객체를 인자로 지정할 수 있는 경우가 있습니다.

주로 배열 메서드에 많이 포진되어 있습니다.

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.every(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndx(callback[, thisArg])
Array.prototype.from(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])

 

다음 글 : [FRONT-END/JavaScript] - 04. 콜백 함수 (코어 자바스크립트)

 

04. 콜백 함수 (코어 자바스크립트)

책을 읽은 지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해 놓았습니다. 이전 글 : [FRONT-END/JavaScript] - 03. this (

rarla-j.tistory.com

 

728x90

댓글