-
[JavaScript] this, call(), apply(), bind()오로지 개발/JavaScript 2020. 9. 18. 23:53
현재 실행 문맥을 알려주는 this
Java의 this와는 다르다. (그래서 헷갈렸다.)✿ JS에서는 함수가 호출될 때, 암묵적으로 arguments 객체와 this를 전달받는다.✿ 함수 호출방식에 따라 this가 연결되는 객체가 달라진다. 즉 동적으로 결정된다.
❀ 여러 방식들을 관통하는 가장 핵심은 ' this 는 자신이 소속된 객체를 가리킨다'
✿ 함수의 상위 스코프를 결정하는 방식을 렉시컬 스코프(Lexical scope)라고 하는데, 함수를 선언할 때 결정된다.
✿ 웹 브라우저의 가장 상위 객체는 window 이다. (이유는 없다. 그냥 JS는 그렇게 설계가 된 언어)
✿ 서버에서 가장 상위 객체는 global 이다. (Node.js)
❀ 웹 브라우저 콘솔창에 this를 입력하면 window 객체가 출력되고, node.js에서 this를 입력하면 global 객체가 출력된다.
메소드와 this
//함수의 this는 window 객체이다. function foo() { if(window === this) { console.log("window === this"); } } foo(); // "window === this" //객체의 소속된 메소드의 this는 그 객체를 가리킨다. let x = { func : function() { if(x === this) { console.log("x === this"); } } } x.func(); // "x === this"
얼핏보면 함수는 전역객체 window를 this로 받고,
객체 안의 메소드는 해당 객체를 this로 가지는구나- 할 수 있지만
이 둘은 결국 같은 것을 관통한다.
어떤 전역변수나 전역객체, 즉 어떤 메소드의 속해있지 않은 객체의 경우에는 window(or global)가 앞에 생략되어 있기 때문이다.
위의 예시 foo() 함수도 결국 아래의 func() 함수 처럼 window 라는 객체에 속해있는 메소드라는 의미이다.
*하지만 위의 메소드에 만약 또 내부함수가 있는 경우, 이 경우에도 this는 전역객체를 가리키게 된다*
window.foo(); // "window === this"
생성자에서의 this
let currThis; function Func() { currThis = this; } //그냥 함수로 호출한 상황 let test1 = Func(); //currThis === window //함수를 호출하면 그 함수는 window라는 객체의 메소드가 되기 때문에 this는 Window가 된다 //new 로 만들어 주었기 때문에 생성자로 호출했다. let test1 = new Func(); //currThis === test1 //new를 이용하여 생성자를 호출하게되면 자바스크립트는 비어있는 객체를 만들고, //새로 생성되는 객체가 생성자 안에서 this가 된다.
this를 잘 사용하는 방법
객체안의 메소드일 경우라도 그 안에 내부함수 this는 전역객체를 가리킨다고 했다.
사실 내부함수의 this는 일반함수, 메소드, 콜백함수 등 상관없이 전역객체를 가리킨다.
이는 자바스크립트 설계 단계의 결함이라고 하는데, (더글라스 크락포드의 자바스크립트 핵심 가이드 중 언급)
이를 해결하기 위해 많은 방법이 등장했다.
call() / apply() / bind()
this를 명시적으로 바인딩 할 수 있는 메소드, 모두 함수 객체의 프로토타입 객체인 Function.prototype 객체의 메소드이다.
//1) call Function.call(this, arguments1, arguments2,...) //Function: call이 호출할 함수 //this: 함수 내부의 this에 바인딩할 객체 //arguments: 함수에 전달할 arguments //2) apply Function.apply(this, [arguments1, arguments2...]) //Function: apply가 호출할 함수 //this: 위와 마찬가지로 바인딩할 객체 //arguments: 함수에 전달할 arguments의 배열
//call 예제 let JS = function(object) { this.type = object; } let js = {}; //빈 객체 js를 call()을 이용하여 JS 함수를 호출할 것이다. JS.call(js, 'object'); console.log(js); {type: 'object'} //call() 첫 번째 매개변수에 빈 객체 js //call() 두 번째에는 전달할 매개변수를 넣어 JS를 호출 //JS 함수의 this는 js객체가 되었고, //JS 함수는 this의 type 프로퍼티에 매개변수 object에 들어온 값을 할당하기 때문에 //js 객체에 type: 'object'가 할당되었다.
call(), apply() 의 대표적인 용도는 arguments와 같은 유사 배열 객체에 배열 메소드를 사용하는 경우이다.
유사 배열에는 배열 메소드를 사용하기 어려운데,
Array.prototype에 apply()나 call()을 이용하여 배열 메소드를 사용할 수 있다.
마치 주어와 동사를 바꾸어 사용하는 것과 같은 느낌이다.
let here = 'global'; let obj = { here: 'object', func: function() { console.log("func's this: ", this); //obj console.log("func's this.here: ", this.here); //"object" function innerFunc(x, y) { console.log("innerFunc's this: ", this); //obj console.log("innerFunc's this.here: ", this.here); //"object" } innerFunc.call(obj, 10, 20); innerFunc.apply(obj, [10, 20]); innerFunc.bind(obj)(10, 20); } } obj.func();
bind()는 함수에 인자로 전달한 this가 바인딩된 새로운 함수를 리턴한다.
정리하면 1. 지정한 this값으로 호출되는 함수를 만드는 것 2. 미리 지정된 초기 인수가 있는 함수를 만드는 것이다.
//1. this를 바로 적용하기 const novel = { title: 'Harry Potter', series: [ { title: "Philosopher's Stone", pub: 2001 }, { title: 'Chamber of Secrets', pub: 2002 }, { title: 'Prisoner of Azkaban', pub: 2004 }, { title: 'Goblet of Fire', pub: 2005 }, { title: 'Order of the Phoenix', pub: 2007 } ], novelName: function () { return this.series.forEach(function(v) { console.log(`${this.title} and the ${v.title}(${v.pub})`); }.bind(this)); } }; novel.novelName();
//2. 부분 적용 함수 (미리 초기 인수를 지정하여 함수를 만드는 것) function arr() { return Array.prototype.slice.call(arguments); } let arr1 = arr(1,2,3) // [1,2,3] 위에서 다뤘던 arguments를 arr형태로 받는 예제 //초기 인수로 73을 미리 설정하여 arr 함수를 생성하기 let alreadyArr = arr.bind(null, 73); let arr2 = alreadyArr(); //[73] let arr3 = alreadyArr(1,2,3); //[73, 1, 2, 3]
bind()는 이런 특징 때문에 많은 곳에 활용되는데,
대표적으로 setTimeout() 과 같이 메소드 내부에서 this를 전역객체로 설정하는 경우 등에서 빛을 발한다.
이는 이전 포스트에 있던 화살표 함수에서 다루었던 예제로 살펴볼 수 있다.
let clock = { time: 0; tiktok: function() { this.time++; } } setInterval(clock.tiktok.bind(clock), 1000);
'오로지 개발 > JavaScript' 카테고리의 다른 글
[JavaScript] Arrow function expressions 화살표 함수 (0) 2020.09.18 [JavaScript] 객체 지향 프로그래밍이란 무엇인가 (0) 2020.09.09 [Data Structure] 스택오버플로우로 알아보는 stack과 recursion (0) 2020.09.09 [Data Structure] Binary Search Tree 이진 탐색 트리 (0) 2020.09.08 자바스크립트 배열 메소드 정리 (0) 2020.08.20