이미 알고 있듯이 JavaScript의 함수는 값입니다.
JavaScript의 모든 값에는 유형이 있습니다. 함수는 어떤 유형인가요?
JavaScript에서 함수는 객체입니다.
함수를 상상하는 좋은 방법은 호출 가능한 "액션 객체"입니다. 우리는 그것들을 호출할 수 있을 뿐만 아니라 객체로 취급할 수도 있습니다: 속성 추가/제거, 참조로 전달 등.
함수 객체에는 몇 가지 사용 가능한 속성이 포함되어 있습니다.
예를 들어, 함수 이름은 "name" 속성으로 액세스할 수 있습니다.
함수 sayHi() { Alert("안녕하세요"); } 경고(sayHi.name); //안녕하세요
재미있는 점은 이름 할당 논리가 똑똑하다는 것입니다. 또한 함수 없이 생성된 경우에도 함수에 올바른 이름을 할당한 다음 즉시 할당합니다.
let sayHi = function() { Alert("안녕하세요"); }; 경고(sayHi.name); //안녕하세요(이름이 있어요!)
할당이 기본값을 통해 수행되는 경우에도 작동합니다.
함수 f(sayHi = function() {}) { 경고(sayHi.name); //안녕하세요(작동합니다!) } 에프();
사양에서는 이 기능을 "컨텍스트 이름"이라고 합니다. 함수가 하나를 제공하지 않으면 할당에서 컨텍스트를 통해 파악됩니다.
객체 메소드에도 이름이 있습니다:
사용자 = { 안녕하세요() { // ... }, sayBye: 함수() { // ... } } 경고(user.sayHi.name); //안녕하세요 경고(user.sayBye.name); //안녕하세요
하지만 마법은 없습니다. 정확한 이름을 알아낼 방법이 없는 경우도 있습니다. 이 경우 name 속성은 다음과 같이 비어 있습니다.
// 배열 내부에 생성된 함수 let arr = [function() {}]; 경고( arr[0].name ); // <빈 문자열> // 엔진은 올바른 이름을 설정할 방법이 없으므로 아무것도 없습니다.
그러나 실제로는 대부분의 함수에 이름이 있습니다.
함수 매개변수의 수를 반환하는 또 다른 내장 속성 "length"가 있습니다. 예를 들면 다음과 같습니다.
함수 f1(a) {} 함수 f2(a, b) {} 함수 다수(a, b, ...more) {} 경고(f1.길이); // 1 경고(f2.길이); // 2 경고(다수.길이); // 2
여기서는 나머지 매개변수가 계산되지 않음을 알 수 있습니다.
length
속성은 때때로 다른 함수에 대해 작동하는 함수의 내부 검사에 사용됩니다.
예를 들어, 아래 코드에서 ask
함수는 질문할 question
과 호출할 임의 개수의 handler
함수를 허용합니다.
사용자가 답변을 제공하면 함수는 핸들러를 호출합니다. 우리는 두 종류의 핸들러를 전달할 수 있습니다:
사용자가 긍정적인 답변을 제공할 때만 호출되는 인수가 없는 함수입니다.
두 경우 모두 호출되어 답을 반환하는 인수가 있는 함수입니다.
handler
올바른 방법으로 호출하기 위해 handler.length
속성을 검사합니다.
아이디어는 긍정적인 경우(가장 빈번한 변형)에 대한 간단하고 인수 없는 처리기 구문을 가지고 있지만 범용 처리기도 지원할 수 있다는 것입니다.
함수 질문(질문, ...핸들러) { let isYes = 확인(질문); for(let 핸들러의 핸들러) { if (handler.length == 0) { if (isYes) handler(); } 또 다른 { 핸들러(isYes); } } } // 긍정적인 대답의 경우 두 핸들러가 모두 호출됩니다. // 부정 답변의 경우 두 번째 답변만 Ask("질문이 있으신가요?", () => Alert('예라고 답하셨습니다.'), result => Alert(result));
이는 소위 다형성의 특별한 경우입니다. 즉, 인수의 유형에 따라 또는 우리의 경우 length
에 따라 인수를 다르게 처리합니다. 이 아이디어는 JavaScript 라이브러리에서 사용됩니다.
우리 자신의 속성을 추가할 수도 있습니다.
여기서는 총 호출 수를 추적하기 위해 counter
속성을 추가합니다.
함수 sayHi() { Alert("안녕하세요"); // 몇 번이나 실행했는지 세어보자 sayHi.counter++; } sayHi.counter = 0; // 초기값 안녕하세요(); // 안녕 안녕하세요(); // 안녕 Alert( `${sayHi.counter}번 호출됨` ); // 2번 호출됨
속성은 변수가 아닙니다.
sayHi.counter = 0
과 같은 함수에 할당된 속성은 내부에 지역 변수 counter
정의하지 않습니다 . 즉, 속성 counter
와 변수 let counter
서로 관련이 없는 두 가지입니다.
함수를 객체로 취급하고 그 안에 속성을 저장할 수 있지만 실행에는 아무런 영향을 미치지 않습니다. 변수는 함수 속성이 아니며 그 반대도 마찬가지입니다. 이것은 단지 평행 세계입니다.
함수 속성은 때때로 클로저를 대체할 수 있습니다. 예를 들어 변수 범위, 클로저 장의 카운터 함수 예제를 함수 속성을 사용하도록 다시 작성할 수 있습니다.
함수 makeCounter() { // 대신: // 개수 = 0으로 둡니다. 함수 카운터() { return counter.count++; }; 카운터.카운트 = 0; 반납 카운터; } 카운터 = makeCounter(); 경고(카운터() ); // 0 경고(카운터() ); // 1
이제 count
는 외부 어휘 환경이 아닌 함수에 직접 저장됩니다.
클로저를 사용하는 것보다 더 나은가요, 아니면 더 나쁜가요?
주요 차이점은 count
값이 외부 변수에 있으면 외부 코드가 해당 값에 액세스할 수 없다는 것입니다. 중첩된 함수만 수정할 수 있습니다. 그리고 함수에 바인딩되어 있으면 다음과 같은 일이 가능합니다.
함수 makeCounter() { 함수 카운터() { return counter.count++; }; 카운터.카운트 = 0; 반납 카운터; } 카운터 = makeCounter(); counter.count = 10; 경고(카운터() ); // 10
따라서 구현 선택은 우리의 목표에 따라 달라집니다.
명명된 함수 표현식(NFE)은 이름이 있는 함수 표현식을 가리키는 용어입니다.
예를 들어 일반적인 함수 표현식을 살펴보겠습니다.
let sayHi = function(who) { Alert(`안녕하세요, ${who}`); };
그리고 이름을 추가하세요:
let sayHi = function func(who) { Alert(`안녕하세요, ${who}`); };
우리가 여기서 뭔가를 성취했나요? 추가 "func"
이름의 목적은 무엇입니까?
먼저 아직 함수 표현식이 있다는 점을 기억해 두세요. function
뒤에 "func"
이름을 추가해도 함수 선언이 되지 않습니다. 왜냐하면 함수 선언은 여전히 할당 표현식의 일부로 생성되기 때문입니다.
그러한 이름을 추가해도 아무런 문제가 발생하지 않았습니다.
이 함수는 여전히 sayHi()
로 사용할 수 있습니다.
let sayHi = function func(who) { Alert(`안녕하세요, ${who}`); }; sayHi("John"); //안녕하세요, 존
func
라는 이름에는 두 가지 특별한 점이 있는데, 그 이유는 다음과 같습니다.
이를 통해 함수가 내부적으로 자신을 참조할 수 있습니다.
함수 외부에서는 보이지 않습니다.
예를 들어, 아래의 sayHi
함수는 who
제공되지 않으면 "Guest"
로 다시 호출됩니다.
let sayHi = function func(who) { 만약 (누구) { Alert(`안녕하세요, ${who}`); } 또 다른 { func("게스트"); // func를 사용하여 자신을 다시 호출합니다. } }; 안녕하세요(); // 안녕하세요, 손님 // 하지만 이것은 작동하지 않습니다: 기능(); // 오류, func가 정의되지 않았습니다(함수 외부에서는 볼 수 없음).
왜 func
사용하나요? 중첩된 호출에 sayHi
사용하면 될까요?
실제로 대부분의 경우 다음을 수행할 수 있습니다.
let sayHi = function(who) { 만약 (누구) { Alert(`안녕하세요, ${who}`); } 또 다른 { sayHi("손님"); } };
해당 코드의 문제점은 sayHi
외부 코드에서 변경될 수 있다는 것입니다. 함수가 대신 다른 변수에 할당되면 코드에서 오류가 발생하기 시작합니다.
let sayHi = function(who) { 만약 (누구) { Alert(`안녕하세요, ${who}`); } 또 다른 { sayHi("손님"); // 오류: sayHi는 함수가 아닙니다. } }; 환영합니다 = 안녕하세요; 안녕하세요 = null; 환영(); // 오류, 중첩된 sayHi 호출이 더 이상 작동하지 않습니다!
이는 함수가 외부 어휘 환경에서 sayHi
가져오기 때문에 발생합니다. 로컬 sayHi
없으므로 외부 변수가 사용됩니다. 그리고 호출 순간에 외부 sayHi
null
입니다.
함수 표현식에 넣을 수 있는 선택적 이름은 이러한 종류의 문제를 정확하게 해결하기 위한 것입니다.
이를 사용하여 코드를 수정해 보겠습니다.
let sayHi = function func(who) { 만약 (누구) { Alert(`안녕하세요, ${who}`); } 또 다른 { func("게스트"); // 이제 모두 괜찮아요 } }; 환영합니다 = 안녕하세요; 안녕하세요 = null; 환영(); // 안녕하세요, 손님(중첩 호출이 작동함)
이제 "func"
라는 이름이 함수 로컬이므로 작동합니다. 외부에서는 가져오지 않습니다(그리고 거기에서도 보이지 않습니다). 사양은 항상 현재 함수를 참조하도록 보장합니다.
외부 코드에는 여전히 변수 sayHi
또는 welcome
이 있습니다. 그리고 func
함수가 자신을 안정적으로 호출할 수 있는 방법인 "내부 함수 이름"입니다.
함수 선언에는 그런 것이 없습니다
여기에 설명된 "내부 이름" 기능은 함수 선언이 아닌 함수 표현식에만 사용할 수 있습니다. 함수 선언의 경우 "내부" 이름을 추가하는 구문이 없습니다.
때로는 신뢰할 수 있는 내부 이름이 필요할 때 함수 선언을 명명된 함수 표현식 형식으로 다시 작성하는 이유가 됩니다.
함수는 객체입니다.
여기서는 해당 속성을 다루었습니다.
name
- 함수 이름입니다. 일반적으로 함수 정의에서 가져오지만 아무것도 없으면 JavaScript는 컨텍스트(예: 할당)에서 이를 추측하려고 시도합니다.
length
– 함수 정의의 인수 수입니다. 나머지 매개변수는 계산되지 않습니다.
함수가 (메인 코드 흐름이 아닌) 함수 표현식으로 선언되고 이름이 포함된 경우 이를 명명된 함수 표현식이라고 합니다. 이름은 재귀 호출 등을 위해 자체 참조를 위해 내부에서 사용될 수 있습니다.
또한 함수에는 추가 속성이 포함될 수 있습니다. 많은 잘 알려진 JavaScript 라이브러리는 이 기능을 효과적으로 활용합니다.
그들은 "메인" 기능을 생성하고 여기에 다른 많은 "도우미" 기능을 첨부합니다. 예를 들어 jQuery 라이브러리는 $
라는 함수를 생성합니다. lodash 라이브러리는 _
함수를 생성한 다음 _.clone
, _.keyBy
및 기타 속성을 추가합니다(자세한 내용은 문서를 참조하세요). 실제로 전역 공간의 오염을 줄이기 위해 이를 수행하므로 단일 라이브러리는 하나의 전역 변수만 제공합니다. 그러면 이름 충돌 가능성이 줄어듭니다.
따라서 함수는 그 자체로 유용한 작업을 수행할 수 있으며 속성에서 여러 다른 기능을 수행할 수도 있습니다.
중요도: 5
카운터도 감소하고 숫자를 설정할 수 있도록 makeCounter()
의 코드를 수정합니다.
counter()
(이전과 마찬가지로) 다음 숫자를 반환해야 합니다.
counter.set(value)
카운터를 value
로 설정해야 합니다.
counter.decrease()
카운터를 1씩 줄여야 합니다.
전체 사용 예는 샌드박스 코드를 참조하세요.
PS 클로저나 함수 속성을 사용하여 현재 카운트를 유지할 수 있습니다. 아니면 두 가지 변형을 모두 작성하세요.
테스트를 통해 샌드박스를 엽니다.
솔루션은 지역 변수에서 count
사용하지만 추가 방법은 counter
에 바로 기록됩니다. 이들은 동일한 외부 어휘 환경을 공유하며 현재 count
액세스할 수도 있습니다.
함수 makeCounter() { 개수 = 0으로 둡니다. 함수 카운터() { 반환 횟수++; } counter.set = 값 => 개수 = 값; counter.decrease = () => 개수--; 반납 카운터; }
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 2
다음과 같이 작동하는 함수 sum
작성하세요.
합계(1)(2) == 3; // 1 + 2 합(1)(2)(3) == 6; // 1 + 2 + 3 합(5)(-1)(2) == 6 합(6)(-1)(-2)(-3) == 0 합(0)(1)(2)(3)(4)(5) == 15
PS 힌트: 함수에 대한 기본 변환으로 사용자 정의 개체를 설정해야 할 수도 있습니다.
테스트를 통해 샌드박스를 엽니다.
어쨌든 모든 것이 작동하려면 sum
의 결과가 함수여야 합니다.
해당 함수는 호출 사이의 현재 값을 메모리에 유지해야 합니다.
작업에 따라 함수는 ==
에서 사용될 때 숫자가 되어야 합니다. 함수는 객체이므로 객체에서 원시 변환 장에 설명된 대로 변환이 발생하며 숫자를 반환하는 자체 메서드를 제공할 수 있습니다.
이제 코드는 다음과 같습니다.
함수 합계(a) { currentSum = a; 함수 f(b) { 현재합계 += b; f를 반환; } f.toString = 함수() { 현재 합계를 반환합니다. }; f를 반환; } 경고(sum(1)(2) ); // 3 경고( sum(5)(-1)(2) ); // 6 경고( sum(6)(-1)(-2)(-3) ); // 0 경고( sum(0)(1)(2)(3)(4)(5) ); // 15
sum
함수는 실제로 한 번만 작동한다는 점에 유의하세요. 함수 f
를 반환합니다.
그런 다음 각 후속 호출에서 f
해당 매개변수를 currentSum
합계에 추가하고 자신을 반환합니다.
f
의 마지막 줄에는 재귀가 없습니다.
재귀는 다음과 같습니다.
함수 f(b) { 현재합계 += b; f()를 반환합니다; // <-- 재귀 호출 }
그리고 우리의 경우에는 함수를 호출하지 않고 함수를 반환합니다.
함수 f(b) { 현재합계 += b; f를 반환; // <-- 자신을 호출하지 않고 자신을 반환합니다. }
이 f
다음 호출에서 사용될 것이며, 필요한 만큼 다시 그 자신을 반환할 것입니다. 그런 다음 숫자나 문자열로 사용되면 toString
은 currentSum
을 반환합니다. 변환을 위해 여기서 Symbol.toPrimitive
또는 valueOf
사용할 수도 있습니다.
샌드박스에서 테스트를 통해 솔루션을 엽니다.