Java
의 기능을 가져온 Javascript
에서 this
의 동작 방식은 Java
와 많이 다르다. 클래스 기반의 Java의 this
는 자기 자신, 인스턴스를 가르킨다. 하지만 Javascript의 this는 상황에 따라 달라진다.
class Student {
private String name;
public Student(String name) {
this.name = name;
}
public sayName() {
System.out.print("my Name is" + this.name);
}
}
class Main {
public static void main(String[] args) {
Student cheol = new Student("철현");
cheol.sayName();
}
}
var func = function() {
console.log(this); // window (전역객체(browser))
}
func();
Javascript를 이해하는데 this
의 이해가 꼭 필요하기 때문에 정리하고자 한다.
전역 컨텍스트에서 this
는 전역 객체를 가리킨다. 그 외 this
바인딩은 실행 컨텍스트가 생성되는 시점에 발생한다. 실행 컨텍스트의 실행 시점은 함수 실행 시점임으로, 함수를 정의할 때가 아니라 함수를 호출할 때, 바인딩 된다. 또한, this바인딩 이해의 중요 포인트는 함수로 호출 되었는지, 메서드로 호출 되었는지를 구분해야 한다는 점이다.
💡 함수와 메서드
Javascript에서 함수와 메서드의 차이는 독립성으로 구별할 수 있다. 함수는 주체(객체)에 의해서 호출되는 것이 아니라 독립적으로 호출되는 것을 의미하고, 메서드는 특정 주체에 의해서 호출이 되는 것을 의미한다.var func = function (x) { console.log(this, x); }; func(1); // 함수 호출 var obj = { method: func, }; obj.method(1); // 메서드 호출
좀 더 직관적인 구분은
.
나[]
로 구분하는 것이다..
의 앞부분이 호출 주체고 호출 주체가 this에 바인딩 되게 된다.
함수로서 호출하게 되면 this
는 전역 객체에 바인딩 하고 메서드로 호출하면 호출 주체에 바인딩 된다. 이는 callback 함수, 메서드 내부에 선언된 함수에도 해당 된다. 단, 제어권을 넘기는 callback의 경우(addEventListener)는 this가 해당 객체로 바인딩 되어 있다.
setTimeout(function() { console.log(this)}, 300); // window
[1,2,3,4,5].forEach(function(x) {console.log(this, x)}); // window
document.body.innerHTML += `<button id="a">클릭</button>`;
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(e, this); // this가 id a인 element
})
new
키워드로 생성한 함수의 경우 생성자로서 작동한다. 이 때 내부의 this
는 인스턴스를 가리키게 된다.
var Cat = function(name, age) {
this.name = name;
this.age = age;
this.bark = '야옹';
}
var choco = new Cat('초코', 7)
var navi = new Cat('나비', 5)
console.log(choco, navi); // Cat { name: "초코"....
this
바인딩을 명시적으로 하는 방법도 있다. call
,apply
,bind
를 사용한다면 해당 함수의 this
를 직접 명시할 수 있다.
// 일반함수
var func = function(a, b, c) {
console.log(this, a, b, c)
}
func.call({x: 1}, 1, 2, 3)
// method
var obj = {
a: 1,
method: function(b, c) {
console.log(this.a, b, c)
}
};
obj.method(2,3);
obj.method.call({a: 4}, 5,6);
var numbers = [10,20,30,40,50];
// ES6 이전
console.log(Math.max.apply(null, numbers)) // 50
console.log(Math.min.apply(null, numbers)) // 10
// ES6+
console.log(Math.max(...numbers))
console.log(Math.min(...numbers))
바인드는 함수의 생성과 적용을 분리하여 사용할 수 있어 유용하다. 또한, function의 name 프로퍼티의 이름에 "bound"가 붙어서, 어떤 함수를 바인드 했는지 확인할 수 있다. 이를 통해 디버깅에도 유용하다.
var func = function(a, b, c) {
console.log(this, a,b,c);
}
var x1bindFunc = func.bind({x:1});
console.log(x1bindFunc.name) // bound func
console.log(func.name) // func
x1bindFunc(3,5); // {x:1} 4 5 6
var obj = {
logThis: function() {
console.log(this)
},
logThisStart1: function() {
setTimeout(this.logThis, 500);
},
logThisStart2: function() {
setTimeout(this.logThis.bind(this), 500)
}
}
obj.logThisStart1(); // window
obj.logThisStart2(); // obj
유사배열객체(length 프로퍼티를 갖고, 0 또는 양수의 프로퍼티를 갖는 객체)는 배열이 아니기 때문에 Array
메소드를 사용할 수 없다. 하지만 위 메소드를 이용하여 push
, slice
와 같은 배열 메소드를 사용할 수 있다.
function a() {
var argv = Array.prototype.slice.call(arguments);
argv.forEach(function (arg) {
console.log(arg);
})
}
a(1,2,3,4,5,6);
document.body.innerHTML = `<div>a</div><div>b</div><div>c</div>`;
var nodeList = document.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function(node) {
console.log(node);
})
a
함수의 arguments
와 nodeList
는 대표적인 유사배열객체이다. 이를 slice
메소드를 call
로 호출하여 배열로 복사했다. 이를 통해 nodeArr
와 argv
는 forEach
문을 사용할 수 있게 됬다.