자바 스크립트에서 범위 만들기-이상한 구문
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']]
// ^ ^
Whoa whoa whoa...let's back up a bit. What's going on here? We can see in section 15.4.4.18, where forEach
is defined, the following pretty much happens:
var callback = log.call,
thisArg = log;
for (var i = 0; i < arr.length; i += 1) {
callback.call(thisArg, arr[i], i, arr);
}
So, we get this:
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);
Now we can see how .map(Number.call, Number)
works:
Number.call.call(Number, arr[i], i, arr);
Number.call(arr[i], i, arr);
Number(i, arr);
Which returns the transformation of i
, the current index, to a number.
In conclusion,
The expression
Array.apply(null, { length: 5 }).map(Number.call, Number);
Works in two parts:
var arr = Array.apply(null, { length: 5 }); //1
arr.map(Number.call, Number); //2
The first part creates an array of 5 undefined items. The second goes over that array and takes its indices, resulting in an array of element indices:
[0, 1, 2, 3, 4]
Disclaimer: This is a very formal description of the above code - this is how I know how to explain it. For a simpler answer - check Zirak's great answer above. This is a more in depth specification in your face and less "aha".
Several things are happening here. Let's break it up a bit.
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
In the first line, the array constructor is called as a function with Function.prototype.apply
.
- The
this
value isnull
which does not matter for the Array constructor (this
is the samethis
as in the context according to 15.3.4.3.2.a. - Then
new Array
is called being passed an object with alength
property - that causes that object to be an array like for all it matters to.apply
because of the following clause in.apply
:- Let len be the result of calling the [[Get]] internal method of argArray with argument "length".
- As such,
.apply
is passing arguments from 0 to.length
, since calling[[Get]]
on{ length: 5 }
with the values 0 to 4 yieldsundefined
the array constructor is called with five arguments whose value isundefined
(getting an undeclared property of an object). - The array constructor is called with 0, 2 or more arguments. The length property of the newly constructed array is set to the number of arguments according to the specification and the values to the same values.
- Thus
var arr = Array.apply(null, { length: 5 });
creates a list of five undefined values.
Note: Notice the difference here between Array.apply(0,{length: 5})
and Array(5)
, the first creating five times the primitive value type undefined
and the latter creating an empty array of length 5. Specifically, because of .map
's behavior (8.b) and specifically [[HasProperty]
.
So the code above in a compliant specification is the same as:
var arr = [undefined, undefined, undefined, undefined, undefined];
arr.map(Number.call, Number); // Calculate and return a number based on the index passed
Now off to the second part.
Array.prototype.map
calls the callback function (in this caseNumber.call
) on each element of the array and uses the specifiedthis
value (in this case setting thethis
value to `Number).- The second parameter of the callback in map (in this case
Number.call
) is the index, and the first is the this value. - This means that
Number
is called withthis
asundefined
(the array value) and the index as the parameter. So it's basically the same as mapping eachundefined
to its array index (since callingNumber
performs type conversion, in this case from number to number not changing the index).
Thus, the code above takes the five undefined values and maps each to its index in the array.
Which is why we get the result to our code.
As you said, the first part:
var arr = Array.apply(null, { length: 5 });
creates an array of 5 undefined
values.
The second part is calling the map
function of the array which takes 2 arguments and returns a new array of the same size.
The first argument which map
takes is actually a function to apply on each element in the array, it is expected to be a function which takes 3 arguments and returns a value. For example:
function foo(a,b,c){
...
return ...
}
if we pass the function foo as the first argument it will be called for each element with
- a as the value of the current iterated element
- b as the index of the current iterated element
- c as the whole original array
The second argument which map
takes is being passed to the function which you pass as the first argument. But it would not be a, b, nor c in case of foo
, it would be this
.
Two examples:
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]
and another one just to make it clearer:
function qux(a,b,c){
return a
}
var newArr4 = arr2.map(qux,9);
//newArr4 is equal to [3,4,5]
So what about Number.call ?
Number.call
is a function that takes 2 arguments, and tries to parse the second argument to a number (I'm not sure what it does with the first argument).
Since the second argument that map
is passing is the index, the value that will be placed in the new array at that index is equal to the index. Just like the function baz
in the example above. Number.call
will try to parse the index - it will naturally return the same value.
The second argument you passed to the map
function in your code doesn't actually have an effect on the result. Correct me if I'm wrong, please.
An array is simply an object comprising the 'length' field and some methods (e.g. push). So arr in var arr = { length: 5}
is basically the same as an array where the fields 0..4 have the default value which is undefined (i.e. arr[0] === undefined
yields true).
As for the second part, map, as the name implies, maps from one array to a new one. It does so by traversing through the original array and invoking the mapping-function on each item.
All that's left is to convince you that the result of mapping-function is the index. The trick is to use the method named 'call'(*) which invokes a function with the small exception that the first param is set to be the 'this' context, and the second becomes the first param (and so on). Coincidentally, when the mapping-function is invoked, the second param is the index.
Last but not least, the method which is invoked is the Number "Class", and as we know in JS, a "Class" is simply a function, and this one (Number) expects the first param to be the value.
(*) found in Function's prototype (and Number is a function).
MASHAL
참고URL : https://stackoverflow.com/questions/18947892/creating-range-in-javascript-strange-syntax
'IT박스' 카테고리의 다른 글
ASP.NET에서 기본 페이지를 설정하는 방법은 무엇입니까? (0) | 2020.07.04 |
---|---|
WinForms에서 레이블을 중앙에 유지하려면 어떻게합니까? (0) | 2020.07.04 |
C ++ : bool이 8 비트 인 이유는 무엇입니까? (0) | 2020.07.04 |
파일을 삭제하지 않고 버전 관리에서 파일을 제거하는 방법은 무엇입니까? (0) | 2020.07.04 |
RestSharp JSON 매개 변수 게시 (0) | 2020.07.04 |