es-discuss 메일 링리스트에서 다음 코드를 실행했습니다.
Array.apply(null, { length: 5 }).map(Number.call, Number);
이것은 생산
[0, 1, 2, 3, 4]
이것이 왜 코드의 결과입니까? 여기서 무슨 일이야?
답변
이 “핵”을 이해하려면 몇 가지 사항을 이해해야합니다.
- 왜 우리가하지 않는 이유
Array(5).map(...)
Function.prototype.apply
인수를 처리 하는 방법Array
여러 인수를 처리하는 방법Number
함수가 인수를 처리 하는 방법- 무엇
Function.prototype.call
합니까
그것들은 자바 스크립트의 고급 주제이므로 다소 길어질 것입니다. 위에서부터 시작하겠습니다. 안전 벨트 매세요!
1. 왜 안돼 Array(5).map
?
실제로 배열은 무엇입니까? 정수 키를 포함하고 값에 매핑되는 일반 객체입니다. 그것은 마법의 length
변수와 같은 다른 특별한 기능을 가지고 있지만 핵심 key => value
은 다른 객체와 마찬가지로 규칙적인 맵입니다. 우리는 배열을 조금 가지고 놀자.
var arr = ['a', 'b', 'c'];
arr.hasOwnProperty(0); //true
arr[0]; //'a'
Object.keys(arr); //['0', '1', '2']
arr.length; //3, implies arr[3] === undefined
//we expand the array by 1 item
arr.length = 4;
arr[3]; //undefined
arr.hasOwnProperty(3); //false
Object.keys(arr); //['0', '1', '2']
우리는 배열의 항목 arr.length
수와 key=>value
배열이 가진 매핑 수 사이에 고유 한 차이를 얻 습니다 arr.length
.
를 통해 배열을 확장 해도 새로운 매핑이 생성 arr.length
되지 않으므로key=>value
배열에 정의되지 않은 값이없고이 키가 없습니다 . 존재하지 않는 속성에 액세스하려고하면 어떻게됩니까? 당신은 얻을 undefined
.
이제 머리를 약간 들어 올려 왜 같은 기능 arr.map
이 이러한 속성을 거치지 않는지 볼 수 있습니다. 경우 arr[3]
단순히 정의되지 않은, 그리고 키가 존재하고, 모든 배열 함수는 다른 값처럼 이상 갈 것입니다 :
//just to remind you
arr; //['a', 'b', 'c', undefined];
arr.length; //4
arr[4] = 'e';
arr; //['a', 'b', 'c', undefined, 'e'];
arr.length; //5
Object.keys(arr); //['0', '1', '2', '4']
arr.map(function (item) { return item.toUpperCase() });
//["A", "B", "C", undefined, "E"]
의도적으로 메소드 호출을 사용하여 키 자체가 결코 존재하지 않았다는 점을 추가로 입증했습니다. 호출 undefined.toUpperCase
하면 오류가 발생했지만 그렇지 않았습니다. 그것을 증명하기 위해 :
arr[5] = undefined;
arr; //["a", "b", "c", undefined, "e", undefined]
arr.hasOwnProperty(5); //true
arr.map(function (item) { return item.toUpperCase() });
//TypeError: Cannot call method 'toUpperCase' of undefined
이제 우리는 내 요점에 도달합니다 Array(N)
. 15.4.2.2 절 에 프로세스가 설명되어 있습니다. 우리가 신경 쓰지 않는 많은 점보 점보가 있지만 줄 사이를 읽을 수 있다면 (또는 이것을 믿을 수는 있지만 모르는 경우) 기본적으로 다음과 같이 요약됩니다.
function Array(len) {
var ret = [];
ret.length = len;
return ret;
}
( len
임의의 값이 아니라 유효한 uint32 인 가정 (실제 사양에서 확인 됨)에서 작동 함 )
이제 왜 Array(5).map(...)
작동하지 않는지 알 수 있습니다 len
. 배열에서 항목을 정의 하지 않고 key => value
매핑을 만들지 않고 단순히 length
속성을 변경합니다 .
이제 우리는 그것을 막았으므로 두 번째 마술을 살펴 보겠습니다.
2. Function.prototype.apply
작동 원리
어떤 apply
일은 기본적으로 배열을하고, 함수 호출의 인수로 풀다된다. 즉, 다음은 거의 동일합니다.
function foo (a, b, c) {
return a + b + c;
}
foo(0, 1, 2); //3
foo.apply(null, [0, 1, 2]); //3
이제 특수 변수를 apply
간단히 기록하여 작동 방식을 쉽게 확인할 수 있습니다 arguments
.
function log () {
console.log(arguments);
}
log.apply(null, ['mary', 'had', 'a', 'little', 'lamb']);
//["mary", "had", "a", "little", "lamb"]
//arguments is a pseudo-array itself, so we can use it as well
(function () {
log.apply(null, arguments);
})('mary', 'had', 'a', 'little', 'lamb');
//["mary", "had", "a", "little", "lamb"]
//a NodeList, like the one returned from DOM methods, is also a pseudo-array
log.apply(null, document.getElementsByTagName('script'));
//[script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script]
//carefully look at the following two
log.apply(null, Array(5));
//[undefined, undefined, undefined, undefined, undefined]
//note that the above are not undefined keys - but the value undefined itself!
log.apply(null, {length : 5});
//[undefined, undefined, undefined, undefined, undefined]
마지막 두 번째 예에서 내 주장을 쉽게 증명할 수 있습니다.
function ahaExclamationMark () {
console.log(arguments.length);
console.log(arguments.hasOwnProperty(0));
}
ahaExclamationMark.apply(null, Array(2)); //2, true
(예, 말장난 의도). key => value
매핑은 우리가 넘어 갔다 배열에 존재하지 않았을 수 apply
있지만, 확실히 존재하는 arguments
변수입니다. 마지막 예제가 작동하는 것과 같은 이유입니다. 전달하는 객체에는 키가 없지만에 있습니다 arguments
.
왜 그런 겁니까? 에서 살펴 보자 절 15.3.4.3 , Function.prototype.apply
정의된다. 대부분 우리가 신경 쓰지 않는 것들이지만 흥미로운 부분은 다음과 같습니다.
- len은 인수 “length”로 argArray의 [[Get]] 내부 메소드를 호출 한 결과입니다.
기본적으로 다음을 의미 argArray.length
합니다. 그런 다음 스펙은 항목에 대해 간단한 for
루프 를 수행하여 해당 값을 length
만듭니다 list
( list
일부 부두이지만 기본적으로 배열 임). 매우 느슨한 코드 측면에서 :
Function.prototype.apply = function (thisArg, argArray) {
var len = argArray.length,
argList = [];
for (var i = 0; i < len; i += 1) {
argList[i] = argArray[i];
}
//yeah...
superMagicalFunctionInvocation(this, thisArg, argList);
};
따라서이 argArray
경우 모방에 필요한 것은 length
속성 이있는 객체입니다 . 이제 값이 정의되지 않은 이유를 알 수 있지만 키가 설정되어 있지 않은 경우 arguments
: key=>value
매핑을 만듭니다 .
휴, 그래서 이것은 이전 부분보다 짧지 않았을 것입니다. 그러나 우리가 끝나면 케이크가 생길 것이므로 인내심을 가지십시오! 그러나 다음 섹션 (짧은 약속) 후에 표현을 해부 할 수 있습니다. 잊어 버린 경우 문제는 다음과 같은 작동 방식입니다.
Array.apply(null, { length: 5 }).map(Number.call, Number);
3. Array
여러 인수를 처리하는 방법
그래서! 우리는에 length
인수를 전달할 때 어떤 일이 발생하는지 보았지만 Array
표현식에서 여러 가지를 인수 ( undefined
정확하게 는 5의 배열 )로 전달합니다. 15.4.2.1 절 에 수행 할 작업이 나와 있습니다. 마지막 단락은 우리에게 중요한 전부이며, 정말 이상하게 표현 되지만, 다음과 같이 요약됩니다.
function Array () {
var ret = [];
ret.length = arguments.length;
for (var i = 0; i < arguments.length; i += 1) {
ret[i] = arguments[i];
}
return ret;
}
Array(0, 1, 2); //[0, 1, 2]
Array.apply(null, [0, 1, 2]); //[0, 1, 2]
Array.apply(null, Array(2)); //[undefined, undefined]
Array.apply(null, {length:2}); //[undefined, undefined]
타다! 정의되지 않은 여러 값의 배열을 가져 와서 정의되지 않은 값의 배열을 반환합니다.
표현의 첫 부분
마지막으로 다음을 해독 할 수 있습니다.
Array.apply(null, { length: 5 })
우리는 5 개의 정의되지 않은 값을 가진 배열을 반환하고 키가 모두 존재한다는 것을 알았습니다.
이제 표현의 두 번째 부분으로
[undefined, undefined, undefined, undefined, undefined].map(Number.call, Number)
잘 알려지지 않은 해킹에 크게 의존하지 않기 때문에이 방법은 더 쉽고 복잡하지 않은 부분입니다.
4. Number
입력을 다루는 방법
Number(something)
( 섹션 15.7.1 )을 수행하면 something
숫자 로 변환 되며 그게 전부입니다. 그 방법은 특히 문자열의 경우 약간 복잡하지만 관심이있는 경우 섹션 9.3 에 작업이 정의되어 있습니다.
5. 게임 Function.prototype.call
call
인 apply
정의의 형제, 섹션 15.3.4.4는 . 인수 배열을 취하는 대신 수신 된 인수를 가져 와서 전달합니다.
둘 이상의 체인을 연결 call
하면 이상한 일이 발생합니다.
function log () {
console.log(this, arguments);
}
log.call.call(log, {a:4}, {a:5});
//{a:4}, [{a:5}]
//^---^ ^-----^
// this arguments
이것은 당신이 무슨 일이 일어나고 있는지 파악할 때까지 가치가 있습니다. log.call
다른 함수의 call
메소드 와 동등한 함수일 뿐이며 call
그 자체로 메소드도 있습니다.
log.call === log.call.call; //true
log.call === Function.call; //true
그리고 무엇을 call
합니까? 그것은 thisArg
많은 인수를 받아들이고 부모 함수를 호출합니다. 우리는 그것을 통해 정의 할 수 있습니다 apply
(다시 느슨한 코드는 작동하지 않습니다).
Function.prototype.call = function (thisArg) {
var args = arguments.slice(1); //I wish that'd work
return this.apply(thisArg, args);
};
이것이 어떻게 진행되는지 추적합시다.
log.call.call(log, {a:4}, {a:5});
this = log.call
thisArg = log
args = [{a:4}, {a:5}]
log.call.apply(log, [{a:4}, {a:5}])
log.call({a:4}, {a:5})
this = log
thisArg = {a:4}
args = [{a:5}]
log.apply({a:4}, [{a:5}])
후반부 또는 .map
전부
아직 끝나지 않았습니다. 대부분의 배열 메소드에 함수를 제공하면 어떻게되는지 보자 :
function log () {
console.log(this, arguments);
}
var arr = ['a', 'b', 'c'];
arr.forEach(log);
//window, ['a', 0, ['a', 'b', 'c']]
//window, ['b', 1, ['a', 'b', 'c']]
//window, ['c', 2, ['a', 'b', 'c']]
//^----^ ^-----------------------^
// this arguments
직접 this
인수를 제공하지 않으면 기본값은 window
입니다. 콜백에 인수가 제공되는 순서를 기록하고 다시 11로 이상하게합시다.
arr.forEach(log.call, log);
//'a', [0, ['a', 'b', 'c']]
//'b', [1, ['a', 'b', 'c']]
//'b', [2, ['a', 'b', 'c']]
// ^ ^
우와 우와 우와 .. 조금 백업하자. 무슨 일이야? 우리가 볼 수있는 섹션 15.4.4.18 , forEach
정의, 다음이 거의 발생 :
var callback = log.call,
thisArg = log;
for (var i = 0; i < arr.length; i += 1) {
callback.call(thisArg, arr[i], i, arr);
}
그래서 우리는 이것을 얻습니다.
log.call.call(log, arr[i], i, arr);
//After one `.call`, it cascades to:
log.call(arr[i], i, arr);
//Further cascading to:
log(i, arr);
이제 우리는 어떻게 .map(Number.call, Number)
작동 하는지 볼 수 있습니다 :
Number.call.call(Number, arr[i], i, arr);
Number.call(arr[i], i, arr);
Number(i, arr);
i
현재 인덱스 의 변환을 숫자로 반환합니다 .
결론적으로,
표현식
Array.apply(null, { length: 5 }).map(Number.call, Number);
두 부분으로 작동합니다.
var arr = Array.apply(null, { length: 5 }); //1
arr.map(Number.call, Number); //2
첫 번째 부분은 5 개의 정의되지 않은 항목으로 구성된 배열을 만듭니다. 두 번째는 해당 배열을 넘고 인덱스를 가져 와서 요소 인덱스 배열을 만듭니다.
[0, 1, 2, 3, 4]
답변
면책 조항 : 이것은 위의 코드에 대한 매우 공식적인 설명입니다. 이것이 내가 그것을 설명하는 방법입니다. 더 간단한 답변을 원하면 위의 Zirak의 훌륭한 답변을 확인하십시오. 이것은 당신의 얼굴에 더 깊이 있고 더 적은 “aha”입니다.
몇 가지 일이 여기서 일어나고 있습니다. 조금 해보자.
var arr = Array.apply(null, { length: 5 }); // Create an array of 5 `undefined` values
arr.map(Number.call, Number); // Calculate and return a number based on the index passed
첫 번째 줄 에서 배열 생성자는을 사용하여 함수 로 호출됩니다Function.prototype.apply
.
this
값은null
Array 생성자 (문제가되지 않는 것이 어떤this
동일하다this
15.3.4.3.2.a.에 따른 문맥으로- 그런 다음 속성이
new Array
있는 객체를 전달length
한다고합니다.이 객체.apply
는 다음 절의 다음과 같은 이유로 중요한 객체와 같은 배열이 됩니다.apply
.- len은 인수 “length”로 argArray의 [[Get]] 내부 메소드를 호출 한 결과입니다.
- 이와 같이,
.apply
0에서 인수를 통과.length
호출 이후[[Get]]
에{ length: 5 }
값으로 0~4 수율undefined
배열 생성자 값이 다섯 개 인자로 호출한다undefined
(물체의 속성 미표시 점점). - 배열 생성자 는 0, 2 개 이상의 인수로 호출 됩니다. 새로 구성된 배열의 길이 속성은 사양에 따른 인수 수와 값이 같은 값으로 설정됩니다.
- 따라서
var arr = Array.apply(null, { length: 5 });
5 개의 정의되지 않은 값의 목록이 작성됩니다.
주 : 여기 공지 간의 차이 Array.apply(0,{length: 5})
와 Array(5)
, 제 작성 다섯 번 프리미티브 값 유형 undefined
, 즉 길이 (5)의 빈 어레이를 생성하고, 후자 인해 .map
동작 (8.b) S ‘ 구체적와 [[HasProperty]
.
따라서 규격에 맞는 위의 코드는 다음과 같습니다.
var arr = [undefined, undefined, undefined, undefined, undefined];
arr.map(Number.call, Number); // Calculate and return a number based on the index passed
이제 두 번째 부분으로 넘어갑니다.
Array.prototype.map
Number.call
배열의 각 요소 에서 콜백 함수 (이 경우 )를 호출하고 지정된this
값 (이 경우this
값을`Number로 설정 )을 사용합니다.- 지도에서 콜백의 두 번째 매개 변수 (이 경우
Number.call
)는 색인이고 첫 번째는 this 값입니다. - 이는 as (배열 값) 및 인덱스를 매개 변수로 사용하여
Number
호출 됨을 의미합니다 . 따라서 기본적으로 배열 인덱스에 각각 매핑하는 것과 같습니다 ( 이 경우 인덱스를 변경하지 않고 숫자에서 숫자로 유형 변환을 수행하기 때문에 ).this
undefined
undefined
Number
따라서 위의 코드는 5 개의 정의되지 않은 값을 가져와 각각 배열의 해당 인덱스에 매핑합니다.
우리가 결과를 코드로 얻는 이유입니다.
답변
말했듯이 첫 번째 부분 :
var arr = Array.apply(null, { length: 5 });
5 개의 undefined
값으로 구성된 배열을 만듭니다 .
두 번째 부분은 map
2 개의 인수를 사용하고 동일한 크기의 새 배열을 반환하는 배열 의 함수를 호출하는 것입니다.
첫 번째 인수 map
는 실제로 배열의 각 요소에 적용되는 함수이며, 3 개의 인수를 가져 와서 값을 반환하는 함수일 것으로 예상됩니다. 예를 들면 다음과 같습니다.
function foo(a,b,c){
...
return ...
}
foo 함수를 첫 번째 인수로 전달하면 다음과 같이 각 요소에 대해 호출됩니다.
- 현재 반복 된 요소의 값으로 a
- 현재 반복 된 요소의 색인으로 b
- 전체 원래 배열로 c
두 번째 인수 map
는 첫 번째 인수로 전달한 함수에 전달됩니다. 그러나의 경우 A, B,도 C되지 않을 것 foo
, 그것이 될 것입니다 this
.
두 가지 예 :
function bar(a,b,c){
return this
}
var arr2 = [3,4,5]
var newArr2 = arr2.map(bar, 9);
//newArr2 is equal to [9,9,9]
function baz(a,b,c){
return b
}
var newArr3 = arr2.map(baz,9);
//newArr3 is equal to [0,1,2]
또 다른 하나는 더 명확하게하기 위해 :
function qux(a,b,c){
return a
}
var newArr4 = arr2.map(qux,9);
//newArr4 is equal to [3,4,5]
그렇다면 Number.call은 어떻습니까?
Number.call
는 2 개의 인수를 취하고 두 번째 인수를 숫자로 구문 분석하려고하는 함수입니다 (첫 번째 인수로 무엇을하는지 확실하지 않습니다).
map
전달 되는 두 번째 인수 는 인덱스이므로 해당 인덱스의 새 배열에 배치 될 값은 인덱스와 같습니다. baz
위 예제 의 함수와 같습니다 . Number.call
인덱스를 구문 분석하려고 시도합니다. 자연스럽게 동일한 값을 반환합니다.
map
코드 에서 함수에 전달한 두 번째 인수 는 실제로 결과에 영향을 미치지 않습니다. 내가 틀렸다면 정정 해주세요.
답변
배열은 단순히 ‘길이’필드와 일부 방법 (예 : 푸시)을 포함하는 객체입니다. 따라서 arr in var arr = { length: 5}
은 기본적으로 필드 0..4가 정의되지 않은 기본값을 갖는 배열과 같습니다 (즉, arr[0] === undefined
참).
두 번째 부분은 이름에서 알 수 있듯이 map은 한 배열에서 새로운 배열로 매핑됩니다. 원래 배열을 통과하고 각 항목에 대해 매핑 기능을 호출하여 그렇게합니다.
남은 것은 mapping-function의 결과가 인덱스임을 확신시키는 것입니다. 트릭은 첫 번째 매개 변수가 ‘this’컨텍스트로 설정되고 두 번째 매개 변수가 첫 번째 매개 변수가되는 예외를 제외하고는 함수를 호출하는 ‘call'(*)이라는 메서드를 사용하는 것입니다. 우연히도, 맵핑 기능이 호출 될 때 두 번째 매개 변수는 색인입니다.
마지막으로, 호출되는 메소드는 숫자 “클래스”이며, JS에서 알 수 있듯이 “클래스”는 단순히 함수이며이 숫자 (숫자)는 첫 번째 매개 변수가 값이 될 것으로 예상합니다.
(*)는 함수의 프로토 타입에 있으며 숫자는 함수입니다.
마시
답변
