[TIL] this

sooyoung choi·2023년 10월 17일
1

Javascript, Node.js

목록 보기
1/37

메서드는 자신이 속한 객체의 상태, 즉 프로퍼티를 참조하고 변경할 수 있어야 한다.
이때 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.

1. this?

 - 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-referencing variable)

 - this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.

 - 단, this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

 - 클래스 기반 언어에서 this는 언제나 클래스가 생성하는 인스턴스를 가리키는데

    , 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값
, 즉 this 바인딩이 동적으로 결정된다.

  - this는 코드 어디에서든 참조 가능하다. 전역에서도 함수 내부에서도 참조 가능

1-1. 객체 리터럴 메서드 내부의 this

 - 메서드를 호출한 객체를 가리킨다.

const circle = {
	radius: 5,
    getDiameter() {
    	// this는 메서드를 호출한 객체를 가리킨다.
    	return 1 * this.radius;
    }
}

1-2. 생성자 함수 내부의 this

 - 생성자 함수가 생성할 인스턴스를 가리킨다.

function Circle(radius) {
	// this는 생성자 함수가 생성할 인스턴스를 가리킨다.
	this.radius = radius;
}

Circle.prototype.getDiameter = function() {
	// this는 생성자 함수가 생성할 인스턴스 가리킨다.
	return 1 * this.radius
}

// 인스턴스 생성
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10

2. 함수 호출 방식과 this 바인딩

 - this에 바인딩 될 값은 함수 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정된다.

2-1. this 바인딩

 - 바인딩 : 식별자와 값을 연결하는 과정

 - this 바인딩 : this와 this가 가리킬 개체를 바인딩 하는 것이다.

렉시컬 스코프와 this 바인딩은 결정 시기가 다르다.
- 렉시컬 스코프(함수의 상위 스코프를 결정하는 방식) : 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프 결정
- this 바인딩 : 함수 호출 시점에 결정

함수 호출 방식 this 바인딩
일반 함수 호출전역 객체
메서드 호출메서드를 호출한 객체
생성자 함수 호출생성자 함수가 (미래에) 생성할 인스턴스
Function.prototype.apply / call / bind 메서드에 의한 간접 호출Function.prototype.apply / call / bind 메서드에 첫번째 인수로 전달할 객체

2-2. 일반 함수 호출

 - 기본적으로 Global Object(전역 객체) 바인딩 된다.

function foo() {
	console.log("foo => ", this); // window
    function bar() {
    	console.log("bar => ", this); // window
    }
    bar();
}
foo();

- 다만 this는 객체의 프로퍼티나 객체의 프로퍼치나 메서드를 참조하기 위한 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에는 this가 의미 없음.

 - strict mode가 적용된 일반 함수 내부의 this는 undefined 바인딩 된다.

function foo() {
	'use stricr';
    
	console.log("foo => ", this); // undefined
    function bar() {
    	console.log("bar => ", this); // undefined
    }
    bar();
}
foo();

 - 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩 된다.

var value = 1;

const obj = {
	value: 100,
    foo() {
    	console.log("foo => ", this);	// {value: 100, foo: f}
        console.log("foo value => ", this.value);	// 100
        
        // 메서드 내에서 정의한 중첩 함수
        function bar() {
        	console.log("bar => ", this);	// window
        	console.log("bar value => ", this.value);	// 1
        }
        // 메서드 내에서 정의한 중첩함수도 일반 함수로 호출되면 
        // 중첩 함수 내부의 This는 전역객체 바인딩 된다
        bar();
    }
};

obj.foo();

 - 콜백함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩 된다.

var value = 1;

const obj = {
	value: 100,
    foo() {
    	console.log('foo => ', this);	// {value: 100, foo: f}
        
        // 콜백 함수 내부의 this 전역 객체 바인딩 된다.
        setTimeout(function () {
			console.log('콜백함수 => ', this);	// window
            console.log('콜백함수값 => ', this.value);	// 1
		}, 100)
    }
};

obj.foo();

2-3. 화살표 함수 사용한 this 바인딩

 - 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.

var value = 1;

const obj = {
	value: 100,
    foo() {
    	setTimeout(() => {
        	console.log(this.value)
        }, 100)	// 100
    }
};

obj.foo();

2-4. 메서드 호출

 - 메서드 내부의 this에는 메서드를 호출한 객체, 즉 메서드를 호출할 때 메서드 이름 앞의 마침표(.) 연산자 앞에 객체가 바인딩 된다.

 - 주의!! 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩 된다.

const person = {
	name: 'Lee',
    getName() {
    	// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩 된다.
    	return this.name;
    }
};

// 메서드 getName 호출한 객체는 person!
// getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아닌
// , 독립적으로 존재하는 별도의 객체

// getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 
// 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.

console.log(person.getName());	// Lee

2-5. 프로토타입 메서드 내부에서 사용된 this

 - 일반 메서드와 마찬가지로 해당 메서드를 호출한 객체에 바인딩 된다.

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

Person.prototype.getName = function() {
	return this.name;
}

const me = new Person('Lee');

// getName 호출한 메서드 ==> me
console.log(me.getName());	// Lee

Person.prototype.name = 'kim';

// getName 호출한 메서드 ==> Person.prototype
console.log(Person.prototype.getName());	// Kim

2-6. 생성자 함수 호출

 - 생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩 된다.

 - 생성자 함수는 이름 그대로 객체(인스턴스) 생성하는 함수

 - 일반함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작

 - new 연산자와 함께 호출하지 않으면 일반 함수로 동작

function Circle(radius) {
	this.radius = raidus;
    this.getDiameter = function () {
    	return 2 * this.radius;
    }
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1);	// 10
console.log(circle2);	// 20


// new 없음, 일반함수 호출
const circle3 = Circle(15);

// undefined 반환
console.log(circle3); // undefined

// 일반 함수로 호출된 Circle 내부의 this는 전역객체를 가리킨다.
console.log(radius);	// 15

2-7. Function.prototype.apply / call / bind 메서드에 의한 간접 호출

 - apply와 call 메서드의 본질적인 기능은 함수를 호출하는것

 - 첫번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩 한다.

 - 유사 배열 객체에 배열 메서드를 사용하는 경우에 사용한다.

apply callbind
호출할 함수의 인수를 배열로 묶어 전달호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달apply, call 과 달리 함수를 호출하지 않는다.
// apply, call

function getThisBinding() {
	console.log(arguments);
    return this;
}

const thisArg = { a: 1 };


console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// { a: 1 }
console.log(getThisBinding.call(thisArg, [1, 2, 3]));
// Arguments(3) [Array(3), callee: f, Symbol(Symbol.iterator): f]
// { a: 1 }
// bind

function getThisBinding() {
	return this;
}

const thisArg = { a: 1 };

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg));	// getThisBinding

// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야한다.
console.log(getThisBinding.bind(thisArg)());	// { a: 1 }

- bind 메서드는 메서드의 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 사용한다.

const person = {
	name: 'Lee',
    foo(callback) {
    	// 콜백 함수 내부의 this를 외부 함수 내부의 this와 일치시켜야 한다.
    	setTimeout(callback.bind(this), 100);
    }
};

person.foo(function() {
	console.log(`Hello my name is ${this.name}`);
})

참조: 모던자바스크립트 deep dive 이웅모 저

0개의 댓글