자바스크립트에서의 this

자바스크립트에서 this는 선언이 아닌 호출에 의해 결정된다.

1. 단독으로 쓴 this

  • 묻지도 따지지도 않고 this를 호출하는 경우엔 global object를 가리킨다.
  • 브라우저에서 호출하는 경우 [object Window]가 호출된다.
  • ES5에서 추가된 strict mode(엄격모드)에서도 마찬가지
'use strict';

var x = this;
console.log(x); // window

strict mode란 ?

2. 함수 안에서 쓴 this

함수 안에서 this는 함수의 주인에게 바인딩된다.
함수 주인은? window 객체

function  myFunction() {
	return  this
}
console.log(myFunction());
var num = 0

function  addNum(){
	this.num  =  100;
	num++;

	console.log(num); // 101
	console.log(window.num); // 101
	console.log(num  ===  window.num); // true
}
addNum()

위 코드에서 this.num의 this는 window객체를 가리킨다. 따라서 num은 전역변수를 가리키게 된다.

다만, strict mode(엄격모드)에서는 조금 다르다.
함수 내의 this에 디폴트 바인딩이 없기 때문에 undefined가 된다.

'use strict'

function  myFunction() {
	return  this
}
console.log(myFunction()); // undefined
'use strict'
var num = 0

function  addNum(){
	this.num  =  100; // Uncaught TypeError: Cannot set property 'num' of undefined
	num++;

	console.log(num);
	console.log(window.num);
	console.log(num  ===  window.num);
}
addNum()

따라서 this.num을 호출하면 undefined.num을 호출하는 것과 마찬가지이기 때문에 에러가 난다.

3. 메서드 안에서 쓴 this

일반 함수가 아닌 메서드라면?

메서드 호출 시 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.

var person = {
	firstName:'John',
	lastName:'Doe',
	fullName: function(){
		console.log(this.firstName + '  ' + this.lastName);
	}
}
person.fullName() // John Doe
var num = 0;

function  showNum(){
	console.log(this.num);
}

showNum() // 0

var obj = {
	num : 200,
	func:showNum,
}

obj.func() // 200

4. 이벤트 핸들러 안에서 쓴 this

이벤트 핸들러에서 this는 이벤트를 받는 HTML 요소를 가리킨다.

var btn = document.querySelector('button');

btn.addEventListener('click',function(){
	console.log(this); // button dom element
})

5. 생성자 안에서 쓴 this

생성자 함수가 생성하는 객체로 this가 바인딩 된다.

function  Person(name) {
	console.log(this); // Person
	this.name  =  name
}

var kim = new  Person('kim');
var lee = new  Person('lee');

console.log(kim.name);
console.log(lee.name);

하지만 new 키워드를 빼먹는 순간 일반 함수 호출과 같아지기 때문에, 이경우는 this가 window에 바인딩된다.

var name = 'window'

// 생성자
function  Person(name) {
	this.name  =  name
}

var kim = Person('kim');
console.log(window.name); // kim

7. 명시적 바인딩을 위한 this

명시적 바인딩은 짝을 지어주는 것. 이 this는 내꺼다. 라는 의미

apply()call() 메서드는 Function Object에 기본적으로 정의된 메서드이다.

function  whoIsThis(){
	console.log(this);
}
whoIsThis() // window

var obj = {
	x : 123
}
whoIsThis.call(obj) // { x : 123 }

apply()에서 매개변수로 받은 첫번째 값함수 내부에서 사용되는 this에 바인딩되고, 두번째 값배열은 자신을 호출한 함수의 인자로 사용한다.

function  Character(name,level) {
	this.name  =  name;
	this.level  =  level;
}

function  Player(name,level,job) {
	this.name  =  name;
	this.level  =  level;
	this.job  =  job;
}

이렇게 두 생성자 함수가 있다고 가정해보자. this.namethis.level을 받아오는 부분이 똑같다. 이럴때 apply()를 쓸 수 있다.

function  Character(name,level) {
	this.name  =  name;
	this.level  =  level;
}

function  Player(name,level,job) {
	Character.apply(this,[name,level])
	this.job  =  job;
}
var me = new  Player('joo',10,'developer');

call()도 apply()와 거의 같다.

차이점이 있다면 call()은 인수 목록을 받고, apply()는 인수 배열을 받는다는 차이가 있다.

위 코드를 call()로 바꿔 쓴다면?
둘 다 일단 함수를 호출한다는 것에 주의하자.

function  Character(name,level) {
	this.name  =  name;
	this.level  =  level;
}

function  Player(name,level,job) {
	Character.call(this,name,level)
	this.job  =  job;

}
var me = {};
Player.call(me, 'joo', 10, 'developer')

apply()나 call()은 보통 유사배열 객체에게 배열 메서드를 쓰고자 할 때 사용한다.
예를 들어 arguments 객체는 함수에 전달된 인수를 Array 형태로 보여주지만 배열 메서드를 쓸 수가 없다.

function  func(a,b,c) {
	console.log(arguments);
	arguments.push('hi') // Uncaught TypeError: arguments.push is not a function
}
func(1,2,3)
function  func(a,b,c) {
	console.log(arguments);
	var args = Array.prototype.slice.apply(arguments);
	args.push('hi')
	console.dir(args)
}
func(1,2,3)
var list = {
	0 : 'kim',
	1 : 'Lee',
	2 : 'Park',
	length:3
}
Array.prototype.push.call(list,'Choi');
console.log(list);

추가로 ES6부터 Array.from()이라는 메서드를 쓸 수 있다. 유사배열객체를 얕게 복사해 새 Array 객체로 만든다.

var children = document.body.children; // HTMLCollection

children.forEach(function(el){
	el.classList.add('on') // ERROR
})
var children = document.body.children; // HTMLCollection

Array.from(children).forEach(function(el){
	el.classList.add('on')
})

6. 화살표 함수로 쓴 this

왜 함수 안에서 this를 호출했을떄 전역 객체가 되는거야? 싶을때는 화살표 함수를 사용하면 된다.

화살표 함수는 전역 컨텍스트에서 실행되더라도 this를 새로 정의하지 않고 바로 바깥 함수나 클래스의 this를 쓴다.

var Person = function (name,age) {
	this.name = name;
	this.age = age;
	this.say = function(){
		console.log(this); // Person { name: 'NaNa', age: 28}

		setTimeout(function(){
			console.log('일반함수',this);
		},100)
	}
}
var me = new  Person('Nana',28)
me.say()

위 코드는 내부함수에서 this가 전역 객체를 가리키는 바람에 의도와는 다른 결과가 나온다.

var Person = function (name,age) {
	this.name = name;
	this.age = age;
	this.say = function(){
		console.log(this); // Person { name: 'NaNa', age: 28}

		setTimeout(() => {
			console.log('위치는 setTimeout',this);
			console.log(this.name + ' is '+ this.age + ' years old');
		}, 100);
	}
}
var me = new  Person('Nana',28)
me.say()

출처

profile
프론트엔드 주니어 개발자 🚀

0개의 댓글