1. call 메서드
메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령
- call 의 첫번째 인자를 this로 바인딩
- 그냥 실행하면 this === 전역객체 but call 메서드를 사용하면 임의의 객체를 this로 지정 가능
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
//원래는 함수호출로 window {...} 출력이나 첫번째 인자로 this에 {x:1} 전달
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 출력
객체의 메서드 를 call 메서드로 호출할 때,
var obj = {
a:1,
method: function (x,y) {
console.log(this.a , x, y);
}
};
// 메서드로서의 호출이므로 obj가 this가 된다. obj.a === 1
obj.method(2,3); // 1 2 3 출력
obj.method.call( {a:4}, 5, 6); // 4 5 6 출력
2. apply 메서드
call 메서드와 기능적으로 완전히 동일
- call: 첫번째 인자를 제외한 나머지 인자들을 호출할 함수의 매개변수로 지정
- apply: 두번째 인자를 배열로 받아, 그 배열 요소들을 호출할 함수의 매개변수로 지정
Function.prototype.apply(thisArg[, argsArray])
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 출력
이를 call로 쓴다면,
var func2 = function(a,b,c){
console.log(this,a,b,c);
}
func2.call({x:1},4,5,6);
// {x: 1} 4 5 6
var obj2 = {
a:1,
method: function(x,y){
console.log(this.a, x,y);
}
};
obj2.method.call({a:4}, 5,6);
// 4 5 6 출력
3. call/apply 메서드의 활용
3-1-1) 유사배열객체(array-like object)에 배열 메서드 적용
- 유사배열(객체)이란? 배열의 형태를 가지고 있지만 배열이 아닌것
- 유사배열(객체)의 조건?
1.반드시 length가 필요해야한다. 이 조건은 필수, 없으면 유사배열이라고 인식하지 않는다.
( = length 프로퍼티 값이 0 또는 양의 정수)
2. index번호가 0번부터 시작해서 1씩 증가해야한다. ( = 키 이름이 0 이상의 양의 정수인 프로퍼티 존재)
3. 실제 배열의 네이티브 메소드( push, forEach ...)는 존재하지 않는다.
- 유사배열의 예시: string, arguments, HTML Collection, NodeList 등
- 유사배열객체는 왜 쓰나요?
객체에 배열 메서드를 사용하고 싶어서(어떤 함수에서 실행 결과로 배열값을 돌려주고 싶을때, 원래의 배열 객체가 가지고 있는 기능(함수)를 제공하고 싶지 않거나 원래의 배열 객체에 없는 기능을 제공하고 싶을때)
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}
// slice메서드의 경우 매개변수가 없을때 원본 배열의 얕은 복사본을 반환
// slice 메서드가 배열 메서드이므로 복사본이 배열로 반환
var arr = Array.prototype.slice.call(obj);
console.log(arr);
// ["a","b","c","d"] 출력
✔🙄질문: 그렇다면 arr의 타입은?
정답 object
3-1-2) arguments, NodeList에 배열 메서드 적용
- nodeList는 array가 아니다. 순회 가능한 유사배열이다.
- slice에 매개변수를 아무것도 넘기지 않았으므로 유사배열을 얄은복사하여 배열로 내뱉는다 = nodeArr
- forEach로 배열을 순회하며 div를 하나씩 뱉어낸다.
function a (){
var argv = Array.prototype.slice.call(arguments);
// argv = [1,2,3];
argv.forEach(function (arg) {
console.log(arg);
});
}
a(1,2,3);
//출력
//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);
// var nodeArr = [div, div, div]; 배열 메서드 사용 가능 (배열로 전환 가능)
nodeArr.forEach(function (node) {
console.log(node);
//아래와 같이 출력
//<div>a</div>
//<div>b</div>
//<div>c</div>
});
3-1-3) 문자열에 배열 메서드 적용
🙄🥔🥬🧅❗❗❓
- 원본 문자열에 변경을 가하는 메서드(push, pop, shift, unshift, splice 등)은 에러 발생
var str = "abc def";
Array.prototype.push.call(str, ", pushed string");
//Uncaught TypeError: Cannot assign to read only property 'length' of object '[object String]'
Array.prototype.concat.call(str, "string");
//[String {"abc def}, "string"]
//every는 배열안의 모든 요소가 주어진 판별함수를 통과하는지 테스트한다. 불리언 반환
Array.prototype.every.call(str, function(char) {return char !== " ";});
//false
// 인덱스 3번은 " " 빈값이기 때문에 false임
//some은 배열안의 어떤 요소라도 주어진 판별 함수를 통과하는지 테스트한다. 빈배열은 무조건 false
Array.prototype.some.call(str, function(char) {return char === " ";});
//true
// 인덱스 3번은 " "; 빈값이기 때문에, 하나라도 true 이므로 true 반환
var newArr = Array.prototype.map.call(str, function(char) {return char + "!";});
console.log(newArr);
// ["a!", "b!", "c!", " !", "d!", "e!", "f!"]
//이해 못함
var newStr = Array.prototype.reduce.apply(str, [
function(string, char, i) {return string + char + i;}, ""]);
console.log(newStr);
//a0b1c2 3d4e5f6 출력
3-1-4) ES6의 Array.from 메서드
- ES6에서 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 복사해서 새로운 배열 객체를 생성
- Array.from(배열로 전환할 객체)
var obj = {
0: "a";
1: "b";
2: "c";
length: 3
};
var arr = Array.from(obj);
console.log(arr);
// ["a","b","c"] 출력
var arr = Array.from("ABC");
console.log(arr);
//어떻게 나올까요?
document.body.innerHTML = "<div>a</div><div>b</div><div>c</div>";
var nodeList = document.querySelectorAll('div');
console.log(nodeList); //객체
Array.from(nodeList).forEach(function(node) { console.log(node) });
//아래와 같이 출력
//<div>a</div>
//<div>b</div>
//<div>c</div>
3-2) 생성자 내부에서 다른 생성자를 호출
생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 혹 apply를 이용해 다른 생성자를 호출하면 반복을 줄일 수 있음
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school){
//여기서 this는 Student 객체에 바인딩 된다
// name, gender을 Person 생성자에 전달해서 사용
Person.call(this, name, gender);
this.school = school;
}
function Employee(name, gender, company){
Person.apply(this, [name, gender]);
this.company = company;
}
var gamja = new Student("syong2", "male", "서울대");
var yangpa = new Employee("jiyoung", "female", "kakao");
// Student {name: "syong2", gender: "male", school: "서울대"}
// Employee {name: "jiyoung", gender: "female", company: "kakao"}
3-3) 여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 apply 활용
3-3-1) 배열에서 최대/최솟값을 구해야 할 경우 apply를 사용하지 않을 때
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number){
if(number > max){
max = number;
}
if(number < min) {
min = number;
}
});
console.log(max, min) //45, 3 출력
3-3-2) 배열에서 최대/최솟값을 구해야 할 경우 apply를 사용할 때
- apply(this, 배열) 에서, Math.max를 구하는데에 this는 역할이 없으므로 null이 되며,
numbers의 배열을 각각의 인자로 받아서 계산 가능하게 해준다.
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
//null 자리에 아무거나 써도 상관없음. 숫자를 쓰던, undefined를 쓰던 max값 산출
//만약 call을 사용하고 싶다면
var max = Math.max.call(null, ...numbers);
var min = Math.min.call(null, ...numbers);
console.log(max, min) //45, 3 출력
//만약 Math.max(numbers)를 하면
// undefined 출력
// 숫자만 arguments로 받을 수 있기 때문에(배열x)
3-3-3) ES6에서 배열에서 최대/최솟값을 구해야 할 경우 (...) spread operator을 이용할 때
const numbers = [10, 20, 3, 16, 45];
// 객체나 배열의 원소들을 하나씩 꺼내어서 펼쳐서 리턴
// ... numbers = 10, 20, 3, 16, 45
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min); //45, 3 출력
4. bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
- call과 비슷 but 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드
- 따라서 변수를 할당하여 호출하는 형태로 사용된다.
var func = function (a,b,c,d) {
console.log(this,a,b,c,d);
};
func(1,2,3,4); // window {...} 1 2 3 4
var bindFunc1 = func.bind({x:1});
bindFunc1(5, 6, 7, 8); // {x:1} 5 6 7 8
var bindfunc2 = func.bind({x:1}, 4, 5);
//새로운 함수를 호출할때 넘기는 인자(6,7)는
// 기존 bind 메서드를 호출할때 전달했던 인수(4,5) 뒤에 이어서 등록된다.
bindFunc2(6,7); // {x:1} 4 5 6 7
bindFunc2(8,9); // {x:1} 4 5 8 9
4-1) name 프로퍼티
- func이라는 원본 함수에 bind 메서드를 적용한 새로운 함수(bindFunc)라는 의미 (추적이 용이하다)
- bound xxx 라는 뜻은 xxx 라는 함수에 bind 메서드를 적용한 함수라는 뜻
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
4-2) 상위 컨텍스트 this를 내부 함수 혹은 콜백함수 전달( self 라는 "변수"를 사용하지 않고 처리하기)
4-2-1) call 사용하기
var obj = {
outer: function(){
console.log(this); // obj === {outer: ƒ}
var innerFunc = function() {
console.log(this); // {outer: ƒ}
};
innerFunc.call(this);
//원래 함수호출이라 window {...} 출력이지만 this를 상위 컨텍스트의 this로 바인딩 함
}
};
obj.outer();
4-2-2) bind 사용하기
var obj = {
outer: function(){
console.log(this); // {outer: ƒ}
var innerFunc = function(){
console.log(this); // {outer: ƒ}
}.bind(this);
//innerFunc 라는 새로운 함수를 반환하며 bind 메서드를 통해 this를 미리 적용
// window {...} => obj인 {outer : f} 출력된다
innerFunc();
}
};
obj.outer();
4-2-3) 콜백함수에서의 bind 메서드 사용하기
- 콜백함수도 함수이기 때문에 기본적으로 this는 전역객체를 참조
- 제어권을 받은 함수 B ( setTimeout) 에서 this를 bind로 별도 지정해줌
-> 상위 컨텍스트의 this 인 obj를 가리킨다
var obj = {
logThis: function(){
console.log(this);
},
logThisLater1: function(){
setTimeout(this.logThis, 500);
},
logThisLater2: function(){
setTimeout(this.logThis.bind(this), 1000);
}
};
obj.logThisLater1(); // window {...}
obj.logThisLater2(); // obj === {logThis: f, ...}
5) 화살표 함수의 예외사항
- ES6에서 도입된 화살표 함수에서는 실행 컨텍스트 생성시(함수 호출시) this를 바인딩하는 과정이 제외됨
- 함수 내부에는 this가 존재X, 접근하고자하면 스코프 체인상 가장 가까운 this에 접근한다.
(화살표 함수는 자신을 둘러싸고 있는 상위 환경의 this를 그대로 따른다 === Lexical this )
- 즉 별도의 변수로 this를 우회하거나, call/ apply/ bind를 적용할 필요가 없어 아주 간결하고 편리하다!
- 화살표 함수는 call, applay, bind 메소드를 사용하여 this를 변경할 수 없다.
var obj = {
outer: function(){
console.log(this); // {outer: ƒ}
//화살표 함수를 사용했다. 함수 호출은 window를 출력하나, 상위 this를 계승하여 obj가 출력
var innerFunc = () => {
console.log(this); // {outer: ƒ}
};
innerFunc();
}
}
obj.outer();
⭐⭐화살표 함수를 사용해서는 안되는 경우를 꼭 공부해보자⭐⭐
(메소드, prototype, 생성자함수, addEventListener의 콜백함수)
1. 메소드 : 메소드로 정의한 화살표 함수 내부의 this는 메소드를 소유한 객체 = 메소드를 호출한 객체를 가리키지 않고 상위 컨텍스트인 전역객체 window를 가리킨다.
const person = {
name: "mushroom",
myName: () => console.log(`${this.name} no study`)
};
person.myName();
// no study 출력
2. prototype: 화살표 함수 내부 this는 전역객체 window를 가리키므로, prototype에 메소드를 할당할때는 일반함수로 할당해야함
const person = {
name: "mushroom",
};
Object.prototype.myName = () => console.log(`${this.name} 는 공부를 안한다`);
person.myName();
// 는 공부를 안한다
3. 생성자함수: 화살표 함수는 생성자 함수로 사용할 수 없음. 생성자 함수는 prototype 프로퍼티를 가지며, prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor을 사용하지만 화살표 함수는 prototype 프로퍼티가 없음
4. addEventListener의 콜백함수: addEventListener 함수의 콜백 함수를 화살표 함수로 정의하면 this가 상위 컨택스트인 전역 객체 window를 가리킨다.
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector("#a").addEventListener("click", () => {
console.log(this === window);
})
6) 별도의 인자로 this를 받는 경우 (콜백함수 내에서의 this)
- 콜백함수를 인자로 받는 메서드 중 일부는 추가로 this를 지정할 객체(thisArg)를 인자로 지정할 수 있다.
- 이러한 메서드의 thisArg값을 지정하면 콜백 함수 내부에서 this 값을 원하는 대로 변경할 수 있다.
- 이런 형태는 내부 요소에 대해 같은 동작을 반복 수행하는 배열 메서드에 많이 포진,
ES6에서 추가된 Set, Map에도 일부 존재
🔎 forEach 메서드 예시
Array.prototype.forEach(callback[, thisArg])
◾ callback -> 각 요소에 대해 실행할 함수. 다음 세 가지 인수를 받습니다.
1. currentValue : 처리할 현재 요소
2. index (Optional) : 처리할 현재 요소의 인덱스
3. array (Optional) : forEach()를 호출할 배열
◾ thisArg (Optional) -> callback을 실행할 때 this로 사용할 값
1) add라는 메서드는 arguments를 call 메서드를 통해 배열로 반환하여 args라는 변수에 담았다.
2) this로 사용할 값을 add 메서드의 this로 바인딩하였다 = report
var report = {
sum: 0,
count: 0,
add: function(){
var args = Array.prototype.slice.call(arguments); // args = [60, 85, 90]
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
'개발공부 > 자바스크립트' 카테고리의 다른 글
[JavaScript] 스터디 6일차_ 클로저 (1) 개념 및 메모리 관리 (0) | 2021.06.28 |
---|---|
[JavaScript] 스터디 5일차_ 콜백 함수 (0) | 2021.06.23 |
[JavaScript] 스터디 3일차_ this (1) (0) | 2021.06.20 |
[JavaScript] 스터디 2일차_ 실행컨텍스트 (0) | 2021.06.16 |
[JavaScript] 스터디 1일차_ 데이터 타입 (0) | 2021.06.15 |