코어자바스크립트

[CoreJavascript] Chapter 03. this 내용 정리

Hyunsangs 2024. 9. 27. 16:08

Chapter 03 - this


3-1 상황에 따라 달라지는 this


→ this는 함수를 호출할 때 결정된다.

→ 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라지는 것

3-1-1 전역 공간에서의 this


전역 공간에서 this는 전역 객체를 가르킨다.

전역 객체는 자바스크립트 런타임 환경에 따라 이름과 정보를 가짐.

  • 전역객체 : 전역 컨텍스트 생성 주체(브라우저 : window , node.js : global)(참고) 자바스크립트의 모든 변수는 실은 특정 객체의 프로퍼티(실행 컨텍스트의 LexicalEnvironment) 로서 동작한다.

전역변수와 젼역객체(1)

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

→ 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로 동작

  • 사용자가 var 연산자를 이용해 변수를 선언하더라도 실제 자바스크립트 엔진은 어떤 특정 객체의 프로퍼티로 인식한다.

전역변수와 전역객체 선언

var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2

→ 전역 공간에서는 var로 변수 선언하는 대신 window의 프로퍼티에 직접 할당 가능

전역변수와 전역객체 삭제

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

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

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

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

→ delete 연산자 앞에는 (window.) 생략

→ 전역객체의 프로퍼티로 할당한 경우엔 삭제 가능 그러나 전역변수로 선언한 경우에는 삭제가 되지 않음

→ 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성 (변경 및 삭제 가능성) 을 false 정의

3-1-2 메서드로서 호출할 때 그 메서드 내부에서의 this


함수 Vs 메서드

→ 함수 실행 방법은 두가지 존재 : 함수로서 호출 Or 메서드로서 호출

→ 둘의 차이점은 독립성

→ 함수는 그 자체로 독립적인 기능 수행

→ 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행

함수로서 호출, 메서드로서 호출

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

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

→ 1번 func라는 변수에 익명함수는 4번째 줄에서 func 호출시 this로 전역객체 window 출력.

→ 2번째 obj변수는, method 호출시 this가 obj 이다.

** 즉 원래의 익명함수는 그대로이지만 이를 변수에 담아 호출 시 obj 객체는 프로퍼티에 할당해서 호출한 경우 this가 달라짐.

?? ‘함수로서 호출’과 ‘메서드로서 호출’ 구분 방법

→ 함수 앞에 점(.)이 있는지 여부로 간단히 구분 가능.

메서드로서 호출 - 점 표기법, 대괄호 표기법

var obj = {
    method : function (x) { console.log(this, x); }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

메서드 내부에서의 this

this에서는 호출한 주체에 대한 정보가 담김.

→ 어떤 함수를 메서드로서 호출한 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체.

→ 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 되는 것

3-1-3 함수로서 호출할 때 그 함수 내부에서의 this


함수 내부에서의 this. 함수를 함수로서 호출한 경우에는 호출 주체의 정보를 알 수 없기 때문에 this가 지정되지 않았기 때문에 this는 전역 객체를 가리킨다.

메서드의 내부 함수에서의 this

내부 함수에서의 this

var obj = {
    outer: function() {
        console.log(this); // obj1
        var innerFunc = function (){
            console.log(this); // window
        }
        innerFunc(); // 함수로서 호출

        var obj2 = {
            innerMethod: innerFunc // obj2
        };
        obj2.innerMethod(); // 메서드로서 호출
    }
};
obj1.outer(); // 메서드로서 호출

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

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

var obj = {
    outer: function() {
        console.log(this);  // {outer: f}
        var innerFunc1 = function (){
            console.log(this); // window
        }
    };
    innerFunc1();

    var self = this;
    var innerFunc2 = function() {
        console.log(self); // {outer: f}
    };
};
obj.outer();

this를 바인딩 하지 않는 함수

this를 바인딩 하지 않는 화살표 함수(arrow function) 도입 (화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용 가능)

3-1-4 콜백 함수 호출 시 그 함수 내부에서의 this


콜백 함수 : 함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 제어권을 넘겨준 함수 A

  • 함수 A는 함수 B의 내부 로직에 따라 실행

콜백 함수 내부에서의 this

setTImeout(function() { console.lgo(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')
    .addEventListner('click', function (e) {
        console.log(this, e);
    }); // (3)

(1) : 0.3초뒤 전역객체 출력

(2): 전역객체와 배열의 각 요소가 총 5회 출력

(3): 버튼 클릭하면 앞서 지정한 엘리먼트와 클릭 이벤트에 관한 정보가 담긴 객체 출력

3-1-5 생성자 함수 내부에서의 this


생성자 함수

: 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수

객체지향 언어 ⇒ 클래스(생성자), 인스턴스(클래스를 통해 만든 객체) ⇒

생성자

: 구체적인 인스턴스를 만들기 위한 틀

사용법 : new 명령어와 함께 함수를 호출 → 생성자의 prototype 프로퍼티를 참조하는 proto라는 프로퍼티가 있는 객체 생성 → 미리 준비된 공통 속성 및 개성을 해당 객체(this)에 부여

생성자 함수

var Cat = function (name, age) {
    this.bark = '야옹'
    this.name = name;
    this.age = age;
};

var choco = new Cat("초코", 7);
var nabi = new Cat("나비", 5);
console.log(choco, nabi); 

// Cat{ bark: '야옹', name:'초코', age:7 }
// Cat{ bark: '야옹' name:'나비', age:5 }

3-2 명시적으로 this를 바인딩하는 방법

3-2-1 call 메서드

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어

→ 함수를 그냥 실행하면 this 전역객체를 참조하지만 cal 메서드를 이용하면 임의의 객체를 this로 지정.

call 메서드 예시 (1) - 함수로서 호출시

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

call 메서드 예시 (2) - 메서드로서의 호출 시

var obj = {
    a : 1,
    method: function (x,y) {
        console.log(this.a, x, y);
    }
};

obj.method(2,3); // 1 2 3
obj.method.call({ a: 4}, 5, 6) // 4 5 6

3-2-2 apply 메서드

function.prototype.apply(thisArg[, argsArray])

→ apply 메서드는 call 메서드와 기능적 완전 동일

  • call 메서드와 apply 차이점
    • call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정
    • apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정.

apply메서드

var func = function (a,b,c) {
    console.log(this, a, b,c);
};
func.apply({ x: 1}, [4,5,6]); // { x: 1} 4 5 6

var obj = {
    a: 1,
    method: function (x,y) {
            console.log(this.a, x, y);    
    }
};

obj.method.apply({ a: 4}, [5,6]); // 4 5 6

3-2-3 call / apply 메서드의 활용

유사배열객체(array-liek object) 에 배열 메서드를 적용

var obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};
Array.prototype.push.call(obj,'d');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

var arr = Array.prototype.slice.call(obj);
console.log(arr) // [ 'a', 'b', 'c', 'd' ]

유사 배열 객체 : 키가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체 (배열의 구조와 유사한 객체의 경우)

arguments, NodeList에 배열 메서드 적용

function a () {
    var argv = Array.prototype,slice.call(arguments);
    agrv.forEach( function (arg) {
        console.log(arg);
    });
}
a(1,2,3);

document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>
var nodeList = document.querySelectAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node)) {
    console.log(node);
}

ES6의 Array.from 메서드 적용

var obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};
var arr = Array.from(obj);
console.log(arr); // [ 'a', 'b', 'c' ];

생성자 내부에서 다른 생성자를 호출

생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 또는 apply를 이용해 다른 생성자를 호출하여 간단하게 반복을 줄일 수 있게 함.

생성자 내부에서 다른 생성자를 호출

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

function Student(name,gender,school) {
    Person.call(this,name,gender);
    this.school = school;
}

function Employee(name,gender, compaany) {
    person.apply(this, [name,gender]);
    this.company = company;
}

var by = new Stduent('보영', 'female', '단국대');
var jn = new Employee('재난', 'male', '구골');

ES6의 펼치기 연산자 활용

const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);  // 45 3

3-2-4 bind 메서드

Function.prototype.bind(thisArg[, arg1[, ag2[, ...]]])

call과 비슷하지만 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드

→ 즉 bind 메서드는 함수에 this를 미리 적용한 것과 부분 적용 함수를 구현하는 두가지 목적을 지님.

bind 메서드 - this 지정과 부분 적용 함수 구현

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

var bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 7);  // { x: 1 } 5 6 7 8

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

name 프로퍼티

name 프로퍼티에 동사 bind의 수동태인 ‘bound’ 라는 접두어가 붙는다

var func = function (a, b, c, d){
    console.log(this, a, b, c,d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
console.log(func.name);  // func
console.log(bindFunc.name);  // bound func

상위 컨텍스트의 this를 내부 함수나 콜백 함수에 전달하기

메서드의 내부 함수에서 메서드의 this를 그대로 바라보게 하기 위한 방법으로 self를 사용하는 것보다 call, apply, bind 메서드를 이용해 더 깔끔하게 처리 가능

상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기

메서드의 내부 함수에서 메서드의 this를 그대로 바라보게 하기 위한 방법으로 self를 사용하는 것보다 call, apply, bind 메서드를 이용해 더 깔끔하게 처리 함.

3-2-5 화살표 함수의 예외사항

화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외되는데, 이 함수 내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근

화살표 함수 내부에서의 this

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

→ 내부함수를 화살표 함수로 바꿈.

→ 별도의 변수로 this 우회하거나 call/ apply / bind를 적용 할 필요 x

3-2-6 별도의 인자를 this를 받는 경우 (콜백 함수 내에서의 this)

콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우가 있는데, 이러한 메서드의 thisArg 값을 지정하면 콜백 함수 내부에서 this 값을 원하는 대로 변경 가능.

콜백 함수와 함께 thisArg를 인자로 받는 메서드

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.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])