[TIS]고차함수

Violet Lee·2020년 8월 16일
0

javascript

목록 보기
6/24

JS의 세계에서도 특별한 대우를 받는것들이 있다. 이런것들을 일급객체(first-class citizen)라고 한다!!
일급객체는 특별한 대우를 받으며, 그 중 하나가 함수(function)이다.

이유 1: 변수에 할당(assignment)이 가능해서.

: 그러므로 배열의 요소,객체의 속성값으로 저장하는것도 가능하다.

(1). 함수표현식(function expression)

  : 함수를 변수에 저장하는 방식이다. 함수 선언식(function declaration)과 다르게 
    호이스팅(hoisting)이 적용x!!
   

1) 변수에 함수를 할당하는 경우
ex)

    // 함수 표현식은 할당 전에 사용할 수 없습니다.
    // square(7); 
    // --> ReferenceError: Can't find variable: square

	const square = function (num) {
	  return num * num;
	};

	// sqaure에는 함수가 저장되어 있으므로 (일급 객체), 함수 호출 연산자 '()'를 사용할 수 있습니다.
	output = sqaure(7);
	console.log(output); // --> 49 
=>그러나, 함수 선언식의 호이스팅에 지나치게 의존하는것은 코드의 유지보수측면에서 안좋다. 코드리뷰or디버깅을 할때, 코드를 위아래로 왔다갔다하게 될수있기때문이다! 
함수선언식의 경우,어느위치에나 함수선언이 가능하고, 함수의 실행위치도 안 중요하기때문!
vs 반면에 함수표현식은, 함수의 할당과 실행위치가 아주 중요하기 때문에, 코드의 위치가 어느정도 예측이 가능하다. 함수가 변수에 저장될수있다는 사실을 좀더 분명히 드러낼수 있다.

비슷한 점: 호이스팅을 제외하면 둘의 차이는 크게 없다.

2. 다른 함수의 인자(argument)로 전달이 가능해서.

3. 다른 함수의 '결과'로써 리턴될수 있어서!

 : 변수에 저장된 데이터는, 함수의 인자로 전달되거나, 함수내에서 리턴값으로 전달이 가능하다.
  앞서 함수가 변수에 저장될 수 있다는 사실로부터 우리는, 
  '함수 역시, 다른 함수의 인자로 전달되거나, 다른 함수내에서 리턴될수 있다는것을 알수있다.'
  
  JS의 고급주제인 고차함수(higher order function)학습은 여기서 시작된다고 한다..!! 두근..!

함수 중 고차함수(higher order function)

: 다른 함수를 인자로 받거나, 다른 함수를 '리턴'하는 함수.

1. 콜백 함수(callback function) : '다른 함수의 인자로 '전달'되는 함수'를 말함.

: '콜백 함수를 전달받은 함수(caller)'는, 이 콜백 함수를 호출(invoke)하는것이 가능하다.

  • caller : 조건에 따라, 콜백함수의 실행여부 결정이 가능하다. 심지어 여러번 실행도 가능하다!!
    (콜백함수는 대부분 어떤작업이 완료되었을때 호출되는 경우가 많아서 답신전화를 뜻하는 콜백이라는 이름이 붙여졌다고 함!!!) <-이 경우 프리코스 이후에 잔뜩 만나볼수 있다고 함..

(1) 다른 함수를 인자로 받는 경우

  ex)
      function double(num){
      return num*2;
     }
     //함수 doubleNum은 다른 함수를 인수로 받는 고차함수(=caller)이다.
     //함수 doubleNum의 첫번째 인자 func에 함수가 들어올 경우, 그 함수 func는 함수 doubleNum의 콜백함수이다.
      //아래와 같은 경우. '함수 double은 함수 doubleNum의 콜백함수'이다.
	function doubleNum(func, num){
	    let doubleArr = [];
	    return func(num);
	}
	let output = doubleNum(double, 8);
	console.log(output);16 리턴.

> 나의 함수 작성 순서_

  • 나는 이걸 작성할 때 어떻게 먼저 작성하면 좋을까..고민해보자. 그러려면 우선 함수실행과정을 알아야겠지?

    먼저 2를 곱한값을 구하고싶은 함수를 작성하고 싶다..면 인자를 하나 받는 함수를 하나 작성해보는거다.
    근데? 우리는 이 함수에 바로 인자를 주지 않을것이다.왜? 다른 함수(caller)가 콜백함수에 인자를 줄거니까!
    그러면 이 콜백함수를 인자로 받으며, 또 다른 인자, 즉 콜백함수의 매개변수까지 받는 함수를 하나 생성을 해야
    우리 콜백함수에게 인자를 줄수있겠지?? 그렇다면 그걸 작성을 해보자! 2를곱하는 함수와 그에 필요한 인자를 받을것이니 두 인자를 표현하는 이름을 함수에 부여하면 알아듣기 쉽겠지? doubleNum같은! ㅎㅎ..
    그러므로 function doubleNum(func, num){} 등으로 작성한뒤, 리턴을 할때 인자로 받은 func(=double)을 리턴해주면, 나중에 함수밖에서 인자를 주어 호출할때, doubleNum의 함수값이 호출되겠지??


> 함수 실행과정_

  • 함수밖에서 인자를 주어 호출할때, 콜백함수는 바로 실행되지 않는다.

    caller에서 그 콜백함수를 호출하고, 함수밖에서 건네준 인자를 같이 매개변수로 받을때 비로소 콜백함수는 실행이 된다. 즉 이 예제의 경우는, 리턴이 될때, 해당 콜백함수를 변수에 담아 같이 받아온 num인자를 콜백함수에 줄때, 그러면 그때 콜백함수가 비로소 실행이 되는것이다. 콜백함수가 인자를 받아 실행이 되고, doubleNum에서 콜백함수의 실행값이 리턴이 되면, 함수밖에서 먼저 호출한 doubleNum()에 받아서, output 변수에 들어간다.


콜백 함수를 사용하는 방법의 예)

  1. filter()메소드
[기본 구문] arr.filter(callback(element[, index[, array]])[, thisArg])
  

'각 매개변수'

 callBack - 이 함수에서 true를 반환하게되면 해당 배열에서 유지되고, 그 요소가 반환이 될것이며,
            false를 반환하게된다면 그 배열에서 버려져요!
            그리고 해당 함수의 인자로는..
 element - 이 콜백함수가 true나 false값을 도출하여 버려지거나 유지하게될 현재 요소
 
 index - 처리하게될 현재 요소의 인덱스(optional)
 
 thisArg - callBack을 실행할때 this로 사용하는 값(optional)
 

그러므로 '이 메소드의 반환값'으로는, 처리된 요소로 이루어진 새로운 배열이 되며,
어떤 요소도 테스트를 통과하지 못했다면, 빈 배열을 반환하게 될것이다.< 즉 그 요소는 '건너뛰고' 새배열에 true의 요소만 들어가서 리턴된다.


2. 커리 함수(curry function): '함수를 리턴하는 함수'만을 말함.

이 함수를 고안해낸 하스켈 커리 논리학자의 이름을 땄다고 함.
이 용어를 사용하는 경우, 고차함수란 용어를 '콜백함수'에만 한정지어서 따로 사용하기도 함!
약간 커리함수가 좀더 고차함수 중에서도 특별취급? 받는듯.. 코드스테이츠에선 둘다 고차함수로 용어통일한다고 함.

(1). 함수 그 자체를 리턴하는 경우
(..나중에! ..ing)


--0814_office hour--

  • reduce()메소드 : 수많은 값들을 안의 함수를 실행하여 ,하나의 값으로 환원해주는것.
기본 구문: arr.reduce(acc,cur[, index[, array]])[, initialValue] 

< 여기서 이니셜발루(= 초깃값)는 옵셔널한 인자라서 반드시 넣지않아도 됨. arr,cur만이 반드시 넣어줘야 하는 인자임. 'acc는 누적되는 부분'임, 그러므로 이 함수끝에 initialValue로 초깃값(예: 0등)을 부여해주면, 저 초깃값부터 arr에 누적이 됨!
--0814_office hour--


..그 밖에 고차함수에 응용되는 메소드들

1. map객체 : 키-값의 쌍을 저장하는 동시에, 그 객체가 삽입되는 순서도 저장하는 콜렉션.

기본 생성자: Map() // 새로운 map객체를 생성한다.

'object와 Map비교'

  • 의도치 않은 키: Map은 기본키 x / object는 기본키 o
  • 키 자료형: Map은 함수,객체 포함 '어떤 값'이든 가능 / object는 반드시 string or Symbol만 가능
  • 키 순서: 'Map의 키는 순서대로 정렬' / 'Object의 키는 정렬 x' '하지만 키가 문자열이라면, 순서대로 순환가능'
  • 크기: Map의 항목수는 size라는 속성을 이용해 쉽게 셀수있음 / object는 직접 for문등을 이용해 귀찮게 세야함..
  • 순회: Map은 순서대로 바로 순회가능 / object는 먼저 모든 키를 알아낸후, 그 다음에 키의 배열을 순회해야함
  • 성능: Map은 반복적으로 키-값 쌍의 추가,삭제에서 간편함 / object는 그런 부분이 최적화되어있지 않다.
  • 속성
    · Map.prototype.size : 객체의 키-값 쌍의 수를 반환.
    · Map.prototype.clear : Map객체에서 모든 쌍을 제거해줌.
    · Map.prototype.get(key) : Map객체안에서 '키가 연결되어있다면 그 값', '키가 없다면 undefined'를 반환.
    · Map.prototype.delete(key) : 해당 객체의 '삭제'여부를 true or false값으로 반환.
    · Map.has(key) : 해당 객체의 '키 존재'여부를 true or false값으로 반환.(이제 귀찮으니 프로토타입 안씀)
⦿ iterator 속성
    - Map.prototype[@@iterator]() : Map객체안의 모든 요소들을 순서대로,[key,value]형태의 배열로 리턴해준다.
    - Map.values() : 주어진 키와 값을, 순서대로 리턴해줌.
    - Map.keys() : 주어진 키를, 순서대로 리턴해줌.
    - Map.forEach() : 순서대로, Map 객체에있는 각 키-값 쌍에 대해 콜백함수를 한 번씩 호출. forEach에 thisArg 매개 변수가 제공되면, 각 콜백에 대해 this 값으로 사용된다!!

ex) Map()의 객체, get(), set() 사용 예

let myMap = new Map()

let keyString = 'a string'
let keyObj    = {}
let keyFunc   = function() {}

// setting the values
myMap.set(keyString, "value associated with 'a string'")
myMap.set(keyObj, 'value associated with keyObj')
myMap.set(keyFunc, 'value associated with keyFunc')

myMap.size              // 3

// getting the values
myMap.get(keyString)    // "value associated with 'a string'"
myMap.get(keyObj)       // "value associated with keyObj"
myMap.get(keyFunc)      // "value associated with keyFunc"

myMap.get('a string')    // "value associated with 'a string'"
                       // because keyString === 'a string'
myMap.get({})            // undefined, because keyObj !== {}
myMap.get(function() {}) // undefined, because keyFunc !== function () {}

알게된 점: 속성에 '빈 객체' 및 '빈 함수'를 사용할 시에, 값을 부여시, 해당 속성에 값이 들어가버리므로, 해당 속성에 부여한 값을, 처음의 속성인 빈 오브젝트로 '호출할수없다'.


ex) NaN을 Map key로 사용예

let myMap = new Map()
myMap.set(NaN, 'not a number')

myMap.get(NaN) 
// "not a number"

let otherNaN = Number('foo')
myMap.get(otherNaN) 
// "not a number"

알게된점: 속성으로 NaN을 부여하면, 문자열 'NaN'으로 받아들인다. 저 값이 'not a number'가 아니라 4여도,
NaN => 4 이렇게 잘 할당이 되어서 출력된다.


ex) for..of 구문으로 iterating map을 사용할때

let myMap = new Map()
myMap.set(0, 'zero')
myMap.set(1, 'one')

for (let [key, value] of myMap) { //[key, value] <이 양식을 사용하면, 키,값을 페어로 리턴할수있다.
  console.log(key + ' = ' + value)
}
// 0 = zero
// 1 = one

for (let key of myMap.keys()) { //key들만 리턴할수있다.
  console.log(key)
}
// 0
// 1

for (let value of myMap.values()) { //key-value의 페어일시, value리턴이 가능하다.
  console.log(value)
}
// zero
// one

for (let [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value)
}
// 0 = zero
// 1 = one

**여기서, 이 예제 중 마지막 예제는 entries()메소드를 사용하는것인데,
이 메소드를 사용한 예제의 결과값이 그저 [key,value]양식으로 돌며 리턴한 결과값과 동일하다.

  • entries()사용안하고 iterator양식을 [key,value]양식으로 순환시,
    결과는 그저 키-값 쌍 페어 리턴이 된다.
    근데,만약 해당 예제에 [key,value]양식이 아니라, pair라는 임의의 변수명을 지어 순환해도, 'Map()메소드는
    객체의 키-값 쌍 페어를 순서대로 순환해주는 메소드'이기 때문에 리턴되는 양식이 동일하다.
    즉, [key,value] === pair 가 된다.
    근데 Map()은, 변수명으로 돌면 사용시 변수명으로 명시해야되며, 키,값 따로 사용불가능하고, 키-값 쌍으로 돌면, 키-값 쌍으로 명시해야하되, '키-값 쌍 따로 사용가능하다.'
   '''
  • entries()메소드 사용시
    그런데, entries()메소드가 어떤 메소드냐면,
    '삽입 순서대로 Map 개체의 각 요소에 대한 [key, value] 배열을 포함하는 새 Iterator 개체'를 반환해주는 메소드인데,
    솔직히, 안 사용하고 사용하고 차이를 계속 공부해봐도 모르겠다.
    어차피 for문돌때 순환가능한 객체를 다 돌아주는거 아닌가?
    entries()메소드가 순환한 요소들을, 새 배열로 리턴해주는게 차이인걸까..? 공부!!

ex) Array object와의 관계

let kvArray = [['key1', 'value1'], ['key2', 'value2']]
let myMap = new Map(kvArray)

1. console.log(Array.from(myMap)) > [ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]

2. console.log([...myMap]) > [ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]  

알게된점: 훨씬 간단한 방법이라는걸 알게됨.

응용 ex)
console.log(Array.from(myMap.keys())) > [ 'key1', 'key2' ]

++ 간단 메모장 키보드 단축키 모음

  • ctrl + f = 문자열 검색
  • ctrl + g = 문자열찾아서 바꿔주는 기능 실행
  • ctrl + i = 줄 번호 찾기
  • ctrl + t = 새 텍스트문서 이 안에서 열기
  • ctrl + q = 메모장 자동종료

++지적 환영합니다. 감사합니다.

     
profile
예비개발자

0개의 댓글