2. 명시적으로 this 바인딩하는 방법
1) call 메서드
-call 메서드는 호출 주체인 함수를 즉시 실행하도록 명령
var func = function(){...};
func.call();
-위와 같이 사용하여 call의 주체인 함수 func를 즉시 실행하도록 명령
-call 메서드에서 첫번째 매개변수를 객체 형태로 주면 그 객체가 함수의 this 객체가 됨
var func = function (a,b,c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // Window{...} 1 2 3
func.call({x: 1}, 4, 5, 6); // {x: 1} 4 5 6
-func(1, 2, 3)은 첫번째 매개변수를 객체 형태로 지정하지 않았으므로 암묵적으로 원래 함수 호출 시 할당되는 this(전역 객체) 할당
-func.call({x: 1}, 4, 5, 6)은 첫번째 매개변수를 객체 형태로 지정하여 명시적으로 func 함수의 this 객체를 지정해줌
-객체의 메서드 또한 call 메서드 없이 호출하면 this 객체 = 메서드를 호출한 객체이지만 call 메서드를 통해 특정 객체를 this로 지정할 수 있음
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({a: 4}, 5, 6}; // 4 5 6
-call 없이 this.a를 호출하면 method를 호출한 객체 obj가 this로 할당되어 obj내에서 선언한 a가 this.a로 출력됨
-call에서 {a: 4}를 지정하면 method의 this 객체를 명시적으로 {a: 4}로 지정하여 this.a에서 {a: 4}의 a값인 4가 출력됨
2) apply 메서드
-apply 메서드는 call 메서드와 기능적으로 동일하며 함수에 전달할 인자를 배열의 형태로 전달한다는 것만 다름
var func = function(a, b, c) {
console.log(this, a, b, c);
};
func.apply({x: 1}, [4, 5, 6]); // {x: 1} 4 5 6
var obj = {
a: 1,
method: function(x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({a: 4}, [5, 6]); // 4 5 6
3) call / apply 메서드의 활용(1): 유사배열객체에 배열 메서드 적용
-유사배열객체: 배열의 키 값이 0 또는 양의 정수인 프로퍼티가 존재 & length의 프로퍼티 값이 0 또는 양수인 객체(배열의 구조와 유사)
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd'); // 배열 메서드인 push를 통해 프로퍼티 3에 'd' 추가, length는 자동으로 1 추가
console.log(obj); // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}
var arr = Array.prototype.slice.call(obj); // 배열 메서드인 slice 메서드를 통해 유사배열객체의 얕은 복사본 반환
console.log(arr); // ['a', 'b', 'c', 'd']
-slice 메서드는 시작 인덱스값과 마지막 인덱스값을 매개변수로 받아 시작값부터 마지막 바로 전의 값까지의 배열 요소를 추출하는 메서드
-slice 메서드에 매개변수를 아무것도 주지 않으면 그냥 원본 배열의 얕은 복사본 반환
-위 예시에서 call 메서드를 통해 원본 유사배열객체의 얕은 복사를 수행하고, slice 메서드가 배열 메서드이기 때문에 배열로 반환
-배열 형태의 객체 외에 arguments, nodeList 등도 유사배열객체로서 작동
// arguments객체도 유사배열객체이므로 slice 메서드를 사용하여 배열로 전환 가능
function a() {
var argv = Array.prototype.slice.call(arguments);
argv.forEach(function(arg) { // argv의 인자들을 각각 순서대로 함수의 인자로 넣음
console.log(arg);
});
}
a(1, 2, 3) // 1 2 3
// querySelectAll, getElementByClassName 등의 Node 선택자를 선택한 결과인 nodeList도 유사배열객체로 slice 메서드를 통해 배열로 전환 가능
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
console.log(node);
});
※ 주의: 문자열로 인덱스와 length프로퍼티를 지니므로 유사배열객체처럼 사용할 수 있지만, 문자열의 length프로퍼티가 읽기 전용임
-원본 문자열에 변경을 가하는 메서드(push, pop, shift, unshift, splice 등)은 에러
-concat처럼 대상이 반드시 배열이어야 하는 경우는 제대로 된 결과 X
// 문자열 선언
var str = 'abc def';
// 문자열에 push 사용 불가능
Array.prototype.push.call(str, ', pushed string');
// 일반적인 배열이면 push를 통해 'abc def'뒤에 ', pushed string'이 추가될 것이지만 문자열은 원본에 변경을 가하는 메서드에 에러 출력
// 문자열에 concat 사용 시 제대로 된 결과 X
Array.prototype.concat.call(str, 'string');
// [ String{"abc def"}, "string" ]
// 문자열에 every와 some 메서드 사용
Array.prototype.every.call(str, function(char) {return char !== ' ' ;}); // false
// 문자열의 모든 문자에 대해 전부 ' '가 아니면 true, 하나라도 ' '이면 false
Array.prototype.some.call(str, function(char) {return char === ' ' ;}); // true
// 문자열의 모든 문자에 대해 하나라도 ' '이면 true, 전부 ' '가 아니면 false
// 문자열에 map 메서드 사용
var newArr = Array.prototype.map.call(str, function(char) {return char + '!';});
// 각 문자에 !를 더하여 배열의 요소로 하나씩 추가
// 문자열에 reduce 메서드 사용
var newStr = Array.prototype.reduce.apply(str, [
function(string, char, i) {return string + char + i;},
''
]);
console.log(newStr); // "a0b1c2 3d4e5f6"
// reduce 메서드의 매개변수는 앞에서부터 (callback 함수(function)의 반환값을 누적, 배열의 현재 요소, 인덱스, 호출할 배열)
// 처음 시행 시 callback 함수의 반환값은 없으므로 공백에서 시작하여 배열의 첫요소 부터 불러와 인덱스(0부터 시작하는 수)를 문자 뒤에 추가하고 반환
-ES6부터는 유사배열객체, 순회가능한 모든 종류의 데이터타입을 배열로 전환하는 Array.from 메서드 도입
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
var arr = Array.from(obj);
console.log(arr); // ['a', 'b', 'c']
4) call / appy 메서드의 활용(2): 생성자 내부에서 나른 생성자 호출
-생성자 내부에 다른 생성자와 공통된 내용이 있으면 call / apply를 이용해 다른 생성자를 호출하면 반복을 줄일 수 있음
// Student, Employee 생성자 내부 함수에서 Person 생성자 함수를 호출해 인스턴스의 속정을 정의
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender); // name과 gender를 반복적으로 속성으로 정의하지 않고 Person으로부터 호출하면 편리
this.school = school; // Person에는 없고 Student에만 있는 속성, school만 추가하면 됨
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]);
this.company = company;
}
var student1 = new Student('학생1', 'female', 'oo대');
var employee1 = new Employee('직원1', 'male', 'oo');
5) apply 메서드의 활용: 여러 인수를 묶어 하나의 배열로 전달
// apply 메서드를 적용하지 않으면
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0]; // numbers 배열의 첫번째 값(10)을 max와 min 변수의 초깃값으로 선정
numbers.forEach(function(number) { // numbers 배열의 각 값을 하나씩 초깃값과 비교하고, 더 큰지 작은지에 따라 max와 min 값을 바꿔가며 전체 배열 중 max와 min 탐색
if (number > max ) {
max = number;
}
if (number < min ) {
min = number;
}
});
console.log(max, min);
// apply 메서드를 적용하면
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers); // Math.max 메서드에 apply 메서드 적용
var min = Math.min.apply(null, numbers); // Math.min 메서드에 apply 메서드 적용
console.log(max, min);
// ES6에서는 펼치기 연산자를 적용하여 apply 메서드보다도 간편하게 작성
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);
6) bind 메서드
-call과 비슷하지만 함수를 즉시 호출하지 않고 넘겨받은 this와 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드
-다시 새로운 함수 호출 시 인수를 넘기면 기존 bund 메서드 호출 시 전달했던 인수 뒤에 추가되는 식으로 등록됨
-bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적
// bind 메서드로 호출할 함수
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window{ ... } 1 2 3 4
// call이나 apply나 bind 메서드 없이 함수를 호출할 때 this는 전역객체임
// bind 메서드로 this 객체 선언(call이나 apply와 같은 기능)
var bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 8); // {x: 1} 5 6 7 8
// bind 메서드로 인자를 일부 넘겨주고 이후에 함수를 호출할 때 추가 인자 넘겨주기
var bindFunc2 = func.bind({x: 1}, 4, 5); // this 객체의 선언과 인자 4개 중 앞의 두 개만 미리 선언
bindFunc2(6, 7); // {x: 1} 4 5 6 7
// 나머지 뒤의 두 개의 인자와 함께 함수 호출
bincFunc2(8, 9); // {x: 1} 4 5 8 9
// 함수의 인자를 바꿨을 때, 마지막 두 개의 인자만 값이 바뀐 것을 알 수 있음
-bind 메서드를 적용해서 만든 함수는 name 프로퍼티에 'bound(bind의 수동태)'라는 접두어가 붙음
-call이나 apply 메서드를 적용한 함수보다 추적이 편리
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를 내부 함수나 콜백 함수에 전달
// call 메서드 사용
var obj = {
outer: function() {
console.log(this);
var innerFunc = function() {
console.log(this);
};
innerFunc.call(this); // call 함수를 통해 이 줄을 실행할 때의 객체, obj 객체를 innerFunc 실행 시의 this 객체로 전달
}
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// bind 메서드 사용
var obj = {
outer: function() {
console.log(this);
var innerFunc = function() {
console.log(this);
}.bind(this);
innerFunc();
}
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// 콜백 함수에 bind 메서드를 통해 this 객체 전달
var obj = {
logThis: function() {
console.log(this);
},
// 따로 this 객체를 지정해주지 않은 콜백함수는 일반 함수와 마찬가지로 전역객체를 this 객체로 가짐
logThisLater1:function() {
setTimeout(this.logThis, 500);
},
// bind 메서드를 통해 this 객체를 지정하여 콜백함수로 넘겨줄 수 있음
// 바로 직전 객체(obj)를 this 객체로 넘겨줌
logThisLater2: function() {
setTimeout(this.logThis.bind(this), 1000);
}
};
obj.logThisLater1(); // Window{ ... }
obj.logThisLater2(); // obj {logThis: f, ...}
7) 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외, 함수 내부에 this가 없고 접근하면 가장 가까운 this에 접근
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
8) 콜백함수 내에서 this를 별도의 인자로 받기
var report = {
// sum과 count 두 개의 프로퍼티에 초깃값 0을 부여하며 선언
sum: 0,
count: 0,
// 합계를 구하는 메서드
add: function () {
var args = Array.prototype.slice.call(arguments); // 인자로 받은 arguments를 배열 형태로 바꿔서
args.forEach(function(entry) { // 각각 차례대로 함수에 넣음
this.sum += entry; // 함수 내에서는 초깃값이 0인 sum에 인자들을 더하며 반환하는 과정을 반복
++this.count; // 반복할 때마다 개수도 하나씩 늘려 총 몇개의 인자를 받았는지 계산
}, this); // forEach 내부의 콜백함수의 this는 두번째 인자로 전달해준 add메서드의 this(report 객체)가 바인딩됨
}, // 따라서, report.sum에 인자가 더해지고, report.count가 1씩 늘어나 저장됨
average: function () {
return this.sum / this.count; // average 메서드의 this 또한 report객체이며 this.sum, this.count는 각각 report.sum, report.count이며 add 메서드에서 더해져 저장된 값들임
}
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.averge()); // 240 3 80
-forEach 외에 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])
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 04. 콜백 함수(2) (0) | 2022.12.14 |
---|---|
[코어 자바스크립트] 04. 콜백 함수(1) (0) | 2022.12.12 |
[코어 자바스크립트] 03. this(1) (0) | 2022.12.05 |
화면 오픈소스 사이트, codepen (0) | 2022.12.05 |
[코어 자바스크립트] 02. 실행 컨텍스트 (0) | 2022.12.02 |