JavaScript는 매우 기능 지향적인 언어입니다. 그것은 우리에게 많은 자유를 줍니다. 함수는 언제든지 생성되어 다른 함수에 인수로 전달된 다음 나중에 완전히 다른 코드 위치에서 호출될 수 있습니다.
우리는 함수가 외부 변수("외부" 변수)에 접근할 수 있다는 것을 이미 알고 있습니다.
하지만 함수가 생성된 이후 외부 변수가 변경되면 어떻게 될까요? 함수가 새로운 값을 얻나요, 아니면 이전 값을 얻나요?
그리고 함수가 인수로 전달되어 다른 코드 위치에서 호출되는 경우 새 위치에서 외부 변수에 액세스할 수 있나요?
이러한 시나리오와 더 복잡한 시나리오를 이해하기 위해 지식을 확장해 보겠습니다.
여기서는 let/const
변수에 대해 이야기하겠습니다.
JavaScript에는 변수를 선언하는 세 가지 방법이 있습니다: let
, const
(현대식), var
(과거의 잔재).
이 문서에서는 예제에서 let
변수를 사용합니다.
const
로 선언된 변수는 동일하게 동작하므로 이 기사도 const
에 관한 것입니다.
이전 var
에는 몇 가지 주목할만한 차이점이 있으며 이전 "var" 기사에서 이에 대해 다룰 것입니다.
변수가 코드 블록 {...}
내에서 선언되면 해당 블록 내에서만 표시됩니다.
예를 들어:
{ // 외부에서 볼 수 없는 지역 변수로 작업을 수행합니다. 메시지 = "안녕하세요"; // 이 블록에서만 볼 수 있음 경고(메시지); // 안녕하세요 } 경고(메시지); // 오류: 메시지가 정의되지 않았습니다.
이를 사용하여 자신에게만 속한 변수를 사용하여 자체 작업을 수행하는 코드 조각을 분리할 수 있습니다.
{ // 메시지 표시 메시지 = "안녕하세요"; 경고(메시지); } { // 다른 메시지 표시 메시지 = "안녕"; 경고(메시지); }
블록이 없으면 오류가 발생합니다.
별도의 블록이 없으면 기존 변수 이름과 함께 let
사용하면 오류가 발생합니다.
// 메시지 표시 메시지 = "안녕하세요"; 경고(메시지); // 다른 메시지 표시 메시지 = "안녕"; // 오류: 변수가 이미 선언되었습니다. 경고(메시지);
if
, for
, while
등의 경우 {...}
에 선언된 변수도 다음 내부에서만 표시됩니다.
만약 (참) { let 문구 = "안녕하세요!"; 경고(문구); // 안녕하세요! } 경고(문구); // 오류, 해당 변수가 없습니다!
여기서 if
완료 후에는 아래 alert
에 phrase
표시되지 않으므로 오류가 발생합니다.
if
분기에 특정한 블록 로컬 변수를 생성할 수 있으므로 훌륭합니다.
for
및 while
루프에서도 비슷한 내용이 적용됩니다.
for (let i = 0; i < 3; i++) { // 변수 i는 이 내부에서만 볼 수 있습니다. 경고(i); // 0, 1, 2 } 경고(i); // 오류, 해당 변수가 없습니다.
시각적으로 let i
{...}
외부에 있습니다. 그러나 여기서 for
구문은 특별합니다. 내부에 선언된 변수는 블록의 일부로 간주됩니다.
함수가 다른 함수 내부에 생성되면 "중첩"이라고 합니다.
JavaScript를 사용하면 이를 쉽게 수행할 수 있습니다.
이를 사용하여 다음과 같이 코드를 구성할 수 있습니다.
함수 sayHiBye(firstName, lastName) { // 아래에 사용할 도우미 중첩 함수 함수 getFullName() { return firstName + " " + lastName; } Alert( "안녕하세요, " + getFullName() ); Alert( "안녕, " + getFullName() ); }
여기서는 편의를 위해 중첩 함수 getFullName()
이 만들어졌습니다. 외부 변수에 액세스할 수 있으므로 전체 이름을 반환할 수 있습니다. 중첩된 함수는 JavaScript에서 매우 일반적입니다.
훨씬 더 흥미로운 점은 중첩된 함수가 새 객체의 속성으로 또는 그 자체의 결과로 반환될 수 있다는 것입니다. 그런 다음 다른 곳에서 사용할 수 있습니다. 어디에 있든 여전히 동일한 외부 변수에 액세스할 수 있습니다.
아래에서 makeCounter
각 호출에서 다음 숫자를 반환하는 “counter” 함수를 생성합니다.
함수 makeCounter() { 개수 = 0으로 둡니다. 반환 함수() { 반환 횟수++; }; } 카운터 = makeCounter(); 경고(카운터() ); // 0 경고(카운터() ); // 1 경고(카운터() ); // 2
단순함에도 불구하고 해당 코드의 약간 수정된 변형은 예를 들어 자동화된 테스트를 위한 임의의 값을 생성하는 난수 생성기로 실용적인 용도로 사용됩니다.
어떻게 작동하나요? 여러 개의 카운터를 생성하면 독립적인가요? 여기서 변수는 어떻게 되나요?
이러한 사항을 이해하는 것은 JavaScript에 대한 전반적인 지식에 도움이 되며 보다 복잡한 시나리오에 도움이 됩니다. 그럼 조금 더 자세히 살펴보겠습니다.
여기 용들이여!
심층적인 기술적 설명이 앞서 있습니다.
저수준 언어 세부 사항을 피하고 싶은 한, 그것들 없이는 이해가 부족하고 불완전할 것이므로 준비하십시오.
명확성을 위해 설명은 여러 단계로 나누어져 있습니다.
JavaScript에서는 실행 중인 모든 함수, 코드 블록 {...}
및 스크립트 전체가 Lexical Environment 라고 하는 내부(숨겨진) 관련 개체를 갖습니다.
Lexical Environment 객체는 두 부분으로 구성됩니다:
환경 레코드 – 모든 지역 변수를 속성(및 this
값과 같은 기타 정보)으로 저장하는 개체입니다.
외부 코드와 관련된 외부 어휘 환경 에 대한 참조입니다.
"변수"는 특수 내부 객체인 Environment Record
의 속성일 뿐입니다. "변수를 얻거나 변경한다"는 것은 "해당 객체의 속성을 얻거나 변경한다"는 뜻입니다.
함수가 없는 이 간단한 코드에는 단 하나의 어휘 환경만 있습니다.
이것은 전체 스크립트와 관련된 소위 전역 어휘 환경입니다.
위 그림에서 직사각형은 환경 기록(변수 저장소)을 의미하고 화살표는 외부 참조를 의미합니다. 전역 어휘 환경에는 외부 참조가 없으므로 화살표가 null
을 가리키는 이유입니다.
코드가 실행을 시작하고 계속 진행됨에 따라 어휘 환경이 변경됩니다.
다음은 조금 더 긴 코드입니다.
오른쪽의 직사각형은 실행 중에 전역 어휘 환경이 어떻게 변경되는지 보여줍니다.
스크립트가 시작되면 어휘 환경은 선언된 모든 변수로 미리 채워집니다.
처음에는 "초기화되지 않음" 상태입니다. 이는 특별한 내부 상태입니다. 이는 엔진이 변수에 대해 알고 있지만 let
으로 선언될 때까지 참조할 수 없음을 의미합니다. 변수가 존재하지 않는 것과 거의 같습니다.
그런 다음 let phrase
. 아직 할당이 없으므로 해당 값은 undefined
입니다. 이 시점부터 변수를 사용할 수 있습니다.
phrase
값이 할당됩니다.
phrase
값을 변경합니다.
지금은 모든 것이 단순해 보이죠?
변수는 현재 실행 중인 블록/함수/스크립트와 관련된 특수 내부 개체의 속성입니다.
변수 작업은 실제로 해당 개체의 속성을 사용하여 작업하는 것입니다.
Lexical Environment는 사양 객체입니다.
"어휘 환경"은 명세 객체입니다. 이는 사물이 어떻게 작동하는지 설명하기 위해 언어 명세에 "이론적으로"만 존재합니다. 우리는 코드에서 이 객체를 가져와서 직접 조작할 수 없습니다.
또한 JavaScript 엔진은 이를 최적화하고, 눈에 보이는 동작이 설명된 대로 유지되는 한 메모리를 절약하고 다른 내부 트릭을 수행하기 위해 사용되지 않는 변수를 삭제할 수 있습니다.
함수는 변수와 마찬가지로 값이기도 합니다.
차이점은 함수 선언이 즉시 완전히 초기화된다는 것입니다.
Lexical Environment가 생성되면 함수 선언은 즉시 사용 가능한 함수가 됩니다( let
달리 선언 전까지는 사용할 수 없습니다).
그렇기 때문에 선언 자체 이전에도 함수 선언으로 선언된 함수를 사용할 수 있습니다.
예를 들어, 함수를 추가할 때 전역 어휘 환경의 초기 상태는 다음과 같습니다.
당연히 이 동작은 함수 선언에만 적용되며 let say = function(name)...
과 같이 변수에 함수를 할당하는 함수 표현식에는 적용되지 않습니다.
함수가 실행되면 호출 시작 시 호출의 지역 변수와 매개변수를 저장하기 위해 새로운 어휘 환경이 자동으로 생성됩니다.
예를 들어, say("John")
의 경우 다음과 같습니다(실행은 화살표로 표시된 줄에서 이루어집니다).
함수 호출 중에는 두 가지 어휘 환경이 있습니다. 내부 환경(함수 호출용)과 외부 환경(전역)입니다.
내부 어휘 환경은 say
의 현재 실행에 해당합니다. 여기에는 함수 인수인 name
이라는 단일 속성이 있습니다. say("John")
호출했으므로 name
값은 "John"
입니다.
외부 어휘 환경은 전역 어휘 환경입니다. 여기에는 phrase
변수와 함수 자체가 있습니다.
내부 어휘 환경은 outer
어휘 환경에 대한 참조를 갖습니다.
코드가 변수에 접근하려고 할 때, 내부 Lexical Environment가 먼저 검색되고, 그 다음 외부 Lexical Environment가 검색되고, 그 다음에는 더 바깥쪽 Lexical Environment가 검색되며, 전역 Lexical Environment까지 계속됩니다.
변수가 어디에도 없으면 이는 엄격 모드에서 오류가 발생합니다( use strict
하지 않고 존재하지 않는 변수에 할당하면 이전 코드와의 호환성을 위해 새 전역 변수가 생성됩니다).
이 예에서는 검색이 다음과 같이 진행됩니다.
name
변수의 경우 say
내부 alert
는 내부 어휘 환경에서 즉시 이를 찾습니다.
phrase
에 접근하려고 할 때 로컬에는 phrase
없으므로 외부 Lexical Environment에 대한 참조를 따라가서 그곳에서 찾습니다.
makeCounter
예제로 돌아가 보겠습니다.
함수 makeCounter() { 개수 = 0으로 둡니다. 반환 함수() { 반환 횟수++; }; } 카운터 = makeCounter();
각 makeCounter()
호출이 시작될 때 이 makeCounter
실행을 위한 변수를 저장하기 위해 새로운 Lexical Environment 객체가 생성됩니다.
따라서 위의 예와 같이 두 개의 중첩된 어휘 환경이 있습니다.
차이점은 makeCounter()
를 실행하는 동안 return count++
단 한 줄의 작은 중첩 함수가 생성된다는 것입니다. 아직 실행하지 않고 생성만 합니다.
모든 함수는 해당 함수가 만들어진 어휘 환경을 기억합니다. 기술적으로 여기에는 마법이 없습니다. 모든 함수에는 함수가 생성된 어휘 환경에 대한 참조를 유지하는 [[Environment]]
라는 숨겨진 속성이 있습니다.
따라서 counter.[[Environment]]
{count: 0}
Lexical Environment에 대한 참조가 있습니다. 이것이 바로 함수가 호출된 위치에 관계없이 생성된 위치를 기억하는 방법입니다. [[Environment]]
참조는 함수 생성 시 한 번만 설정되고 영원히 설정됩니다.
나중에 counter()
가 호출되면 호출에 대한 새 어휘 환경이 생성되고 외부 어휘 환경 참조는 counter.[[Environment]]
에서 가져옵니다.
이제 counter()
내부의 코드가 count
변수를 찾을 때 먼저 자체 Lexical Environment(지역 변수가 없으므로 비어 있음)를 검색한 다음 외부 makeCounter()
호출의 Lexical Environment를 검색하고 변경합니다. .
변수는 해당 변수가 있는 어휘 환경에서 업데이트됩니다.
실행 후 상태는 다음과 같습니다.
counter()
여러 번 호출하면 count
변수는 같은 위치에서 2
, 3
등으로 증가합니다.
폐쇄
개발자가 일반적으로 알아야 하는 일반적인 프로그래밍 용어인 "클로저"가 있습니다.
클로저는 외부 변수를 기억하고 이에 접근할 수 있는 함수입니다. 일부 언어에서는 이것이 불가능합니다. 그렇지 않으면 함수를 특별한 방식으로 작성해야 합니다. 그러나 위에서 설명한 대로 JavaScript에서는 모든 함수가 자연스럽게 클로저입니다("새 함수" 구문에서 다루는 단 하나의 예외가 있습니다).
즉, 숨겨진 [[Environment]]
속성을 사용하여 생성된 위치를 자동으로 기억한 다음 코드에서 외부 변수에 액세스할 수 있습니다.
인터뷰에서 프론트엔드 개발자가 "클로저가 무엇인가요?"에 대한 질문을 받을 때 유효한 대답은 클로저에 대한 정의와 JavaScript의 모든 함수가 클로저라는 설명, 그리고 기술적 세부 사항에 대해 몇 마디 더 설명하는 것입니다. [[Environment]]
속성과 어휘 환경의 작동 방식.
일반적으로 어휘 환경은 함수 호출이 완료된 후 모든 변수와 함께 메모리에서 제거됩니다. 그 이유는 그에 대한 언급이 없기 때문입니다. 모든 JavaScript 객체는 접근 가능한 동안에만 메모리에 보관됩니다.
그러나 함수가 끝난 후에도 계속 연결할 수 있는 중첩 함수가 있는 경우 해당 함수에는 어휘 환경을 참조하는 [[Environment]]
속성이 있습니다.
이 경우 함수가 완료된 후에도 Lexical Environment에 계속 접근할 수 있으므로 계속 살아있습니다.
예를 들어:
함수 f() { 값 = 123으로 설정; 반환 함수() { 경고(값); } } g = f()라고 하자; // g.[[Environment]]는 어휘 환경에 대한 참조를 저장합니다. // 해당 f() 호출
f()
여러 번 호출되고 결과 함수가 저장되면 해당하는 모든 Lexical Environment 객체도 메모리에 유지됩니다. 아래 코드에서는 3가지가 모두 포함됩니다.
함수 f() { 값 = Math.random()을 설정합니다. return function() { 경고(값); }; } // 배열에 있는 3개의 함수, 모두 Lexical Environment에 연결됩니다. // 해당 f() 실행에서 arr = [f(), f(), f()];
Lexical Environment 객체는 (다른 객체와 마찬가지로) 접근할 수 없게 되면 죽습니다. 즉, 이를 참조하는 중첩 함수가 하나 이상 있는 동안에만 존재합니다.
아래 코드에서는 중첩된 함수가 제거된 후 해당 함수를 포함하는 어휘 환경(및 value
)이 메모리에서 정리됩니다.
함수 f() { 값 = 123으로 설정; 반환 함수() { 경고(값); } } g = f()라고 하자; // g 함수가 존재하는 동안 값은 메모리에 유지됩니다. g = 널; // ...이제 메모리가 정리되었습니다.
우리가 본 것처럼 이론적으로 함수가 살아있는 동안 모든 외부 변수도 유지됩니다.
그러나 실제로 JavaScript 엔진은 이를 최적화하려고 시도합니다. 변수 사용을 분석하고 코드에서 외부 변수가 사용되지 않는 것이 분명하면 제거됩니다.
V8(Chrome, Edge, Opera)의 중요한 부작용은 디버깅 시 해당 변수를 사용할 수 없게 된다는 것입니다.
개발자 도구를 연 상태에서 Chrome에서 아래 예제를 실행해 보세요.
일시 중지되면 콘솔에 alert(value)
입력하세요.
함수 f() { 값 = Math.random()을 설정합니다. 함수 g() { 디버거; // 콘솔에서: 경고(값)를 입력합니다. 그런 변수는 없습니다! } g를 반환; } g = f()라고 하자; g();
보시다시피 – 그러한 변수는 없습니다! 이론적으로는 접근이 가능해야 하지만 엔진이 이를 최적화했습니다.
이로 인해 재미있는(시간이 많이 걸리지는 않더라도) 디버깅 문제가 발생할 수 있습니다. 그 중 하나 – 예상되는 변수 대신 동일한 이름의 외부 변수를 볼 수 있습니다.
let value = "놀랍습니다!"; 함수 f() { let value = "가장 가까운 값"; 함수 g() { 디버거; // 콘솔에서: 경고(값)를 입력합니다. 놀라다! } g를 반환; } g = f()라고 하자; g();
V8의 이 기능은 알아두면 좋습니다. Chrome/Edge/Opera로 디버깅을 하다 보면 조만간 만나게 될 것입니다.
이는 디버거의 버그가 아니라 V8의 특별한 기능입니다. 아마도 언젠가는 바뀔 것입니다. 이 페이지의 예제를 실행하여 언제든지 확인할 수 있습니다.
중요도: 5
sayHi 함수는 외부 변수 이름을 사용합니다. 함수가 실행될 때 어떤 값을 사용하게 되나요?
이름 = "John"으로 설정; 함수 sayHi() { Alert("안녕하세요, " + 이름); } 이름 = "피트"; 안녕하세요(); // "John" 또는 "Pete" 중 무엇을 표시합니까?
이러한 상황은 브라우저 및 서버측 개발 모두에서 일반적입니다. 함수는 생성된 것보다 나중에(예: 사용자 작업 또는 네트워크 요청 후) 실행되도록 예약할 수 있습니다.
그래서 질문은: 최신 변경 사항을 선택합니까?
대답은 다음과 같습니다. 피트 .
함수는 현재 상태 그대로 외부 변수를 가져오며 가장 최근 값을 사용합니다.
이전 변수 값은 어디에도 저장되지 않습니다. 함수가 변수를 원하면 자체 어휘 환경이나 외부 어휘 환경에서 현재 값을 가져옵니다.
중요도: 5
아래 makeWorker
함수는 또 다른 함수를 만들어 반환합니다. 해당 새 함수는 다른 곳에서 호출될 수 있습니다.
생성 위치나 호출 위치 또는 둘 다에서 외부 변수에 액세스할 수 있습니까?
함수 makeWorker() { 이름 = "피트"로 설정; 반환 함수() { 경고(이름); }; } 이름 = "John"으로 설정; // 함수 생성 일하자 = makeWorker(); // 호출 일하다(); // 무엇을 보여줄까요?
어떤 값이 표시되나요? “피트” 또는 “존”?
대답은 다음과 같습니다. 피트 .
아래 코드의 work()
함수는 외부 어휘 환경 참조를 통해 원래 위치에서 name
가져옵니다.
따라서 결과는 여기에서 "Pete"
입니다.
그러나 makeWorker()
에 let name
없으면 검색은 외부로 나가서 위의 체인에서 볼 수 있듯이 전역 변수를 사용하게 됩니다. 이 경우 결과는 "John"
이 됩니다.
중요도: 5
여기서는 동일한 makeCounter
함수를 사용하여 counter
와 counter2
두 개의 카운터를 만듭니다.
그들은 독립적인가요? 두 번째 카운터에는 무엇이 표시되나요? 0,1
또는 2,3
또는 다른 것?
함수 makeCounter() { 개수 = 0으로 둡니다. 반환 함수() { 반환 횟수++; }; } 카운터 = makeCounter(); let counter2 = makeCounter(); 경고(카운터() ); // 0 경고(카운터() ); // 1 경고(counter2()); // ? 경고(counter2()); // ?
답은 0,1입니다.
counter
함수와 counter2
함수는 makeCounter
를 서로 다르게 호출하여 생성됩니다.
따라서 그들은 독립적인 외부 Lexical Environment를 가지며, 각각은 자신의 count
갖습니다.
중요도: 5
여기서는 생성자 함수의 도움으로 카운터 개체가 만들어집니다.
작동할까요? 그것은 무엇을 보여줄 것인가?
함수 카운터() { 개수 = 0으로 둡니다. this.up = 함수() { ++개수를 반환합니다. }; this.down = 함수() { return --count; }; } 카운터 = 새 카운터()를 설정합니다. 경고(counter.up()); // ? 경고(counter.up()); // ? 경고(counter.down()); // ?
확실히 그것은 잘 작동할 것이다.
두 중첩 함수 모두 동일한 외부 어휘 환경 내에서 생성되므로 동일한 count
변수에 대한 액세스를 공유합니다.
함수 카운터() { 개수 = 0으로 둡니다. this.up = 함수() { ++개수를 반환합니다. }; this.down = 함수() { return --count; }; } 카운터 = 새 카운터()를 설정합니다. 경고(counter.up()); // 1 경고(counter.up()); // 2 경고(counter.down()); // 1
중요도: 5
코드를보세요. 마지막 줄의 통화 결과는 어떻게 될까요?
문구 = "안녕하세요"; 만약 (참) { 사용자 = "John"으로 설정; 함수 sayHi() { Alert(`${phrase}, ${user}`); } } 안녕하세요();
결과는 오류 입니다.
sayHi
함수는 if
내부에 선언되어 있으므로 그 내부에만 존재합니다. 밖에는 sayHi
없습니다.
중요도: 4
다음과 같이 작동하는 함수 sum
작성하세요: sum(a)(b) = a+b
.
예, 정확히 이 방법으로 이중 괄호를 사용합니다(잘못 입력한 것이 아님).
예를 들어:
합(1)(2) = 3 합계(5)(-1) = 4
두 번째 괄호가 작동하려면 첫 번째 괄호가 함수를 반환해야 합니다.
이와 같이:
함수 합계(a) { 반환 함수(b) { a + b를 반환합니다. // 외부 어휘 환경에서 "a"를 가져옵니다. }; } 경고( 합계(1)(2) ); // 3 경고(sum(5)(-1) ); // 4
중요도: 4
이 코드의 결과는 어떻게 될까요?
x = 1이라고 하자; 함수 함수() { console.log(x); // ? x = 2라고 하자; } 기능();
PS 이 작업에는 함정이 있습니다. 해결책은 분명하지 않습니다.
결과는 error 입니다.
실행해보세요:
x = 1이라고 하자; 함수 함수() { console.log(x); // 참조 오류: 초기화 전에 'x'에 액세스할 수 없습니다. x = 2라고 하자; } 기능();
이 예에서 우리는 "존재하지 않는" 변수와 "초기화되지 않은" 변수 사이의 독특한 차이를 관찰할 수 있습니다.
변수 범위, 클로저 기사에서 읽었을 수도 있듯이, 변수는 실행이 코드 블록(또는 함수)에 들어가는 순간부터 "초기화되지 않은" 상태로 시작됩니다. 그리고 해당 let
문이 나올 때까지 초기화되지 않은 상태로 유지됩니다.
즉, 기술적으로 변수가 존재하지만 let
이전에는 사용할 수 없습니다.
위의 코드는 이를 보여줍니다.
함수 함수() { // 지역 변수 x는 함수 시작부터 엔진에 알려집니다. // 그러나 let("데드 존")까지 "초기화되지 않음"(사용할 수 없음) // 따라서 오류 console.log(x); // 참조 오류: 초기화 전에 'x'에 액세스할 수 없습니다. x = 2라고 하자; }
변수를 일시적으로 사용할 수 없는 영역(코드 블록의 시작 부분부터 let
까지)을 "데드 존"이라고 부르기도 합니다.
중요도: 5
배열을 위한 내장 메소드 arr.filter(f)
가 있습니다. f
함수를 통해 모든 요소를 필터링합니다. true
반환하면 해당 요소가 결과 배열에 반환됩니다.
"즉시 사용할 수 있는" 필터 세트를 만듭니다.
inBetween(a, b)
– a
와 b
사이 또는 둘과 같습니다(포함).
inArray([...])
– 주어진 배열에 있습니다.
사용법은 다음과 같아야 합니다.
arr.filter(inBetween(3,6))
– 3과 6 사이의 값만 선택합니다.
arr.filter(inArray([1,2,3]))
– [1,2,3]
의 멤버 중 하나와 일치하는 요소만 선택합니다.
예를 들어:
/* .. inBetween 및 inArray에 대한 코드 */ arr = [1, 2, 3, 4, 5, 6, 7]로 설정합니다. 경고( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 경고( arr.filter(inArray([1, 2, 10])) ); // 1,2
테스트를 통해 샌드박스를 엽니다.
함수 inBetween(a, b) { 반환 함수(x) { return x >= a && x <= b; }; } arr = [1, 2, 3, 4, 5, 6, 7]로 설정합니다. 경고( arr.filter(inBetween(3, 6)) ); // 3,4,5,6
함수 inArray(arr) { 반환 함수(x) { arr.includes(x)를 반환합니다. }; } arr = [1, 2, 3, 4, 5, 6, 7]로 설정합니다. 경고( arr.filter(inArray([1, 2, 10])) ); // 1,2
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 5
정렬할 개체 배열이 있습니다.
사용자 = [ { 이름: "John", 나이: 20, 성: "Johnson" }, { 이름: "피트", 나이: 18, 성: "피터슨" }, { 이름: "Ann", 나이: 19, 성: "Hathaway" } ];
이를 수행하는 일반적인 방법은 다음과 같습니다.
// 이름별(Ann, John, Pete) users.sort((a, b) => a.name > b.name ? 1 : -1); // 연령별(피트, 앤, 존) users.sort((a, b) => a.age > b.age ? 1 : -1);
이렇게 덜 장황하게 만들 수 있을까요?
users.sort(byField('이름')); users.sort(byField('나이'));
따라서 함수를 작성하는 대신 byField(fieldName)
입력하세요.
이를 위해 사용할 수 있는 byField
함수를 작성하세요.
테스트를 통해 샌드박스를 엽니다.
함수 byField(필드명){ return (a, b) => a[필드 이름] > b[필드 이름] ? 1: -1; }
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 5
다음 코드는 shooters
배열을 생성합니다.
모든 함수는 해당 숫자를 출력하도록 되어 있습니다. 하지만 뭔가 잘못되었습니다…
함수 makeArmy() { 저격수 = []; 내가 = 0이라고 하자; 동안 (i < 10) { letshooter = function() { //슈터 함수를 생성합니다. 경고(i); // 숫자가 표시되어야 합니다. }; Shooters.push(슈터); // 배열에 추가 나++; } // ...그리고 저격수의 배열을 반환합니다. 리턴 슈터; } 군대 = makeArmy(); // 모든 사수는 숫자 0, 1, 2, 3 대신 10을 표시합니다... 군대[0](); // 사수 번호 0에서 10 군대[1](); // 사수 번호 1에서 10 군대[2](); // 10 ... 등등.
왜 모든 사수는 동일한 값을 표시합니까?
의도한 대로 작동하도록 코드를 수정합니다.
테스트를 통해 샌드박스를 엽니다.
makeArmy
내부에서 정확히 무슨 일이 일어나는지 조사해 보면 해결책이 분명해질 것입니다.
빈 배열 shooters
생성합니다.
저격수 = [];
루프에서 shooters.push(function)
를 통해 함수로 채웁니다.
모든 요소는 함수이므로 결과 배열은 다음과 같습니다.
저격수 = [ 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); }, 함수() { 경고(i); } ];
배열은 함수에서 반환됩니다.
그런 다음 나중에 임의의 멤버(예: army[5]()
를 호출하면 배열(함수)에서 army[5]
요소를 가져와 호출합니다.
이제 이러한 모든 함수가 동일한 값인 10
표시하는 이유는 무엇입니까?
이는 shooter
함수 내부에 지역 변수 i
없기 때문입니다. 그러한 함수가 호출되면 외부 어휘 환경에서 i
가져옵니다.
그러면 i
의 값은 어떻게 될까요?
소스를 살펴보면 다음과 같습니다.
함수 makeArmy() { ... 내가 = 0이라고 하자; 동안 (i < 10) { let Shooter = function() { // 슈팅 함수 경고(i); // 번호를 표시해야 합니다. }; Shooters.push(슈터); // 배열에 함수 추가 나++; } ... }
makeArmy()
함수의 어휘 환경에서 모든 shooter
함수가 생성되는 것을 볼 수 있습니다. 그러나 army[5]()
가 호출되면 makeArmy
이미 작업을 완료했으며 i
의 최종 값은 10
입니다( i=10
에서 while
).
결과적으로 모든 shooter
함수는 외부 어휘 환경에서 동일한 값, 즉 마지막 값인 i=10
얻습니다.
위에서 볼 수 있듯이 while {...}
블록이 반복될 때마다 새로운 어휘 환경이 생성됩니다. 따라서 이 문제를 해결하려면 다음과 같이 i
값을 while {...}
블록 내의 변수에 복사하면 됩니다.
함수 makeArmy() { 저격수 = []; 내가 = 0이라고 하자; 동안 (i < 10) { j = i라고 하자; let Shooter = function() { // 슈팅 함수 경고(j); // 번호를 표시해야 합니다. }; Shooters.push(슈터); 나++; } 리턴 슈터; } 군대 = makeArmy(); // 이제 코드가 올바르게 작동합니다. 군대[0](); // 0 군대[5](); // 5
여기서 let j = i
"반복 지역" 변수 j
선언하고 i
여기에 복사합니다. 기본 요소는 "값별로" 복사되므로 실제로는 현재 루프 반복에 속하는 i
의 독립적인 복사본을 얻습니다.
i
의 값이 이제 조금 더 가까워지기 때문에 저격수가 올바르게 작동합니다. makeArmy()
어휘 환경이 아니라 현재 루프 반복에 해당하는 어휘 환경에 있습니다.
이러한 문제는 처음에 다음과 같이 for
사용하면 피할 수도 있습니다.
함수 makeArmy() { 저격수 = []; for(let i = 0; i < 10; i++) { let Shooter = function() { // 슈팅 함수 경고(i); // 번호를 표시해야 합니다. }; Shooters.push(슈터); } 리턴 슈터; } 군대 = makeArmy(); 군대[0](); // 0 군대[5](); // 5
for
각 반복에서 자체 변수 i
사용하여 새로운 어휘 환경을 생성하기 때문에 이는 본질적으로 동일합니다. 따라서 모든 반복에서 생성된 shooter
바로 그 반복에서 자체 i
참조합니다.
이제 여러분은 이것을 읽는 데 많은 노력을 기울였으며 최종 레시피는 매우 간단합니다. for
사용하면 됩니다. 그럴만한 가치가 있었는지 궁금할 것입니다.
글쎄요, 만약 당신이 질문에 쉽게 대답할 수 있다면 당신은 해결책을 읽지 않을 것입니다. 따라서 이 작업이 여러분이 상황을 좀 더 잘 이해하는 데 도움이 되었기를 바랍니다.
게다가 for
를 선호하는 while
와 그러한 문제가 실제로 발생하는 다른 시나리오도 있습니다.
샌드박스에서 테스트를 통해 솔루션을 엽니다.