일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 빅오표기법
- 코어자바스크립트
- 도커
- 프리코스
- DevOps
- 젠킨스
- 백준
- 이벤트버블링
- MSA
- 쿠버네티스
- 파이썬
- 객체지향의 사실과 오해
- 리스트복사
- 10869번
- 배열파티션
- LeetCode
- 2588번
- v-if
- v-for
- v-model
- Python
- 우테코
- vue
- v-on
- 이벤트캡쳐링
- JavaScript
- 실행 컨텍스트
- 10926번
- 3003번
- hoisting
Archives
- Today
- Total
새오의 개발 기록
코어 자바스크립트: 3. this 본문
01. 상황에 따라 달라지는 this
- 다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미하지만 자바스크립트에서 this는 상황별로 달라짐.
- 자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정
- 실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있음
- 즉 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라짐
1-1. 전역 공간에서의 this
- 전역공간에서 this는 전역 객체를 가리킴
- 개념상 전역 컨텍스트를 생성하는 주체가 바로 전역 객체이기 때문
- 전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가지고 있는데 브라우저 환경에서 전역객체는 window이고 Node.js 환경에서는 global임
- 전역변수를 선언하면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로도 할당함. 변수이면서 객체의 프로퍼티이기도 한 셈
// 전역변수와 전역객체
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
- 전역공간에서 선언한 변수 a에 1을 할당했을 뿐인데 window.a와 this.a 모두 1이 출력되는데 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작하기 때문임
- 여기서 특정 객체란 실행 컨텍스트의 LexicalEnvironment
- 실행 컨텍스트는 변수를 수집해서 L.E의 프로퍼티로 저장함
- 즉, 전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당한다.
- window.a나 this.a 뿐만 아니라 a를 직접 호출할 때도 1이 나오는 까닭은 변수 a에 접근하고자 할 때 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 L.E, 즉 전역객체에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문
- 전역 공간에서는 var로 변수를 선언하는 대신 window의 프로퍼티에 직접 할당하더라도 결과적으로 var로 선언한 것과 똑같이 동작할 것이라는 예상 가능함
- 삭제의 경우는 다름
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
var c = 3;
delete window.c; // true
console.log(c, window.c, this.c); // uncaught ReferenceError: c is not defined
var d = 4;
delete d; // true
console.log(d, window.d, this.d); // uncaught ReferenceError: d is not defined
- 처음부터 전역객체의 프로퍼티로 할당한 경우에는 삭제가 되는 반면 전역변수로 선언한 경우에는 삭제가 되지 않음
- 사용자가 의도치 않게 삭제하는 것을 방지하는 차원에서 마련한 방어 전략으로 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성(변경 및 삭제 가능성)을 false로 정의하는 것
- var로 선언한 전역변수와 전역객체의 프로퍼티는 호이스팅 여부 및 configurable 여부에서 차이를 보임
1-2. 메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
- 함수를 실행하는 방법
- 함수로서 호출
- 메서드로서 호출
- 함수: 그 자체로 독립적인 기능을 수행
- 메서드: 자신을 호출한 대상 객체에 관한 동작 수행
- 어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작함
- 함수 앞에 점(.) 여부 또는 대괄호 표기법으로 '함수로서 호출'과 '메서드로서 호출' 구분
메서드 내부에서의 this
- this에는 호출한 주체에 대한 정보가 담김
- 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 함수명(프로퍼티명) 앞의 객체
- ex) obj.method();라면 호출 주체는 obj
1-3. 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
- 어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않으며 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 가리킴
메서드의 내부함수에서의 this
- 내부함수에서 이를 함수로서 호출했는지 메서드로 호출했는지만 파악하면 this의 값 알 수 있음
- 함수를 실행하는 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지로 판단하면 됨
메서드의 내부함수에서의 this를 우회하는 방법
- 매서드 내부 함수에서 this 구분 명확하지만 this의 직관적인 의미와는 차이 생기기 때문에 호출 주체가 없을 때 자동으로 전역객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 그대로 상속받아 사용하고 싶음 -> 변수 활용하는 방법
var obj = {
outer: function () {
console.log(this);
var innerFunc1 = function () {
console.log(this);
};
innerFunc1();
var self = this;
var innerFunc2 = function () {
console.log(self);
};
innerFunc2();
}
}
obj.outer();
- innerFunc1 내부에서 this는 전역객체를 가리킴
- outer 스코프에서 self라는 변수에 this를 저장한 상태에서 호출한 innerFunc2의 경우 self에 obj가 출력됨
- 변수명은 꼭 self는 아니어도 됨
- 상위 스코프의 this를 저장해서 내부함수에서 활용하려는 수단임
this를 바인딩하지 않는 함수
- ES6에서 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입함
- 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어 상위 스코프의 this를 그대로 활용할 수 있음
- 위의 변수 사용하는 방법 필요 없어짐
1-4. 콜백 함수 호출 시 그 함수 내부에서의 this
- 콜백 함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 됨
setTimeout(function () { console.log(this); }, 300); // (1)
[1, 2, 3, 4, 5].forEach(function(x) { // (2)
console.log(this, x);
});
document.body.innerHTML += '<button id="a">클릭</button>'; // (3)
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
- (1)의 setTimeout 함수와 (2)의 forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않음. 따라서 콜백 함수 내부에서의 this는 전역객체를 참조함
- (3)의 addEventListner 메서드는 콜백 함수를 호출할 때 자신의 this를 상송하도록 정의됨
1-5. 생성자 함수 내부에서의 this
- 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 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 }
*/
02. 명시적으로 this를 바인딩하는 방법
상황별 기본적으로 this에 바인딩 되는 규칙을 깨고 별도의 대상을 바인딩 하는 방법도 있음
2-1. call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
- 첫 번째 인자를 this로 바인딩
- 이후의 인자들을 호출할 함수의 매개변수로 사용
- 호출 주체인 함수를 즉시 실행
2-2. apply 메서드
Function.prototype.apply(thisArg[, argsArray])
- 첫 번째 인자를 this로 바인딩
- 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정
- 호출 주체인 함수를 즉시 실행
2-3. call / apply 메서드의 활용
유사배열객체에 배열 메서드를 적용
- 객체에는 배열 메서드를 직접 적용할 수 없는데 유사 배열 객체의경우 가능함
- 유사배열객체: 키가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체. 즉 배열의 구조와 유사한 경우 유사배열객체라고 함
// 유사배열객체에 배열 메서드를 적용
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'];
- slice 메서드에 매개변수를 아무것도 넘기지 않을 경우에 원본 배열의 얕은 복사본을 반환함
- call 메서드를 이용해 원본인 유사배열객체의 얕은 복사를 수행한 것이며 slice 메서드가 배열 메서드이기 때문에 복사본은 배열로 반환하게 됨
- 함수 내부에서 접근할 수 있는 arguments 객체도 유사배열객체이므로 위의 방법으로 배열로 전환해 활용 가능함
- qureySelectorAll, getElementByClassName 등 Node 선택자로 선택한 결과인 NodeList도 마찬가지임
// call/apply 메서드의 활용
function a () {
var argv = Array.prototype.slice.call(arguments);
argv.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.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
console.log(node);
});
- 그 밖에도 유사배열객체에는 call/apply 메서드를 이용해 모든 배열 메서드를 적용할 수 있으며 배열처럼 인덱스와 length 프로퍼티를 지니는 문자열에 대해서도 마찬가지임
- 단 문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에 원본 문자열에 변경을 가하는 메서드(push, pop, shift, unshift, splice 등)는 에러를 던지며, concat 처럼 대상이 반드시 배열이어야 하는 경우에는 에러는 나지 않지만 제대로 된 결과를 얻을 수 없음
- 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, company) {
Person.apply(this, [name, gender]);
this.company = company;
}
var by = new Student('보영', 'female', '단국대');
var jn = new Employee('재난', 'male', '구골');
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min); // 45 3
spread 연산자 이용해서 apply 적용하는 것보다 간결하게 작성 가능함
const numbers = [10, 20, 3, 16,45];
const max = Math.max(...numbers);
const mine = Math.min(...numbers);
console.log(max, min); // 45 3
2-4. bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
- call 과 비슷하지만 즉시 호출하지 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드
- 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 지님
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를 내부함수나 콜백 함수에 전달하기
- self 등의 변수를 활용한 우회법 대신 call, apply 또는 bind 메서드를 이용하면 깔끔하게 사용할 수 있음
2-5. 화살표 함수의 예외사항
- 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외되어 함수 내부에 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
// 내부함수를 화살표 함수로 변경함
var obj = {
outer: function() {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
};
};
obj.outer();
2-6. 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
- 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 함
- 이런 형태는 여러 내부 요소에 대해 같은 동작을 반복 수행해야 하는 배열 메서드에 많이 포진돼 있으며, 같은 이유로 ES6에서 새로 등장한 Set, Map 등의 메서드에도 일부 존재
// thisArg를 받는 경우 예시 - forEach 메서드
var report = {
sum: 0,
count: 0,
add: function() {
var args = Array.prototype.slice.call(arguments);
args.forEach(function (entry) {
this.sum += entry;
++this.count;
}, this);
},
average: function () {
return this.sum / this.count;
}
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average()); // 240 3 80
// 콜백 함수와 함께 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])
'Javascript > 코어 자바스크립트' 카테고리의 다른 글
코어 자바스크립트: 2. 실행 컨텍스트 (0) | 2022.12.23 |
---|---|
코어 자바스크립트: 1. 데이터 타입 (0) | 2022.12.21 |