IT박스

자바 스크립트에서 범위 만들기-이상한 구문

itboxs 2020. 7. 4. 11:18
반응형

자바 스크립트에서 범위 만들기-이상한 구문


es-discuss 메일 링리스트에서 다음 코드를 실행했습니다.

Array.apply(null, { length: 5 }).map(Number.call, Number);

이것은 생산

[0, 1, 2, 3, 4]

이것이 왜 코드의 결과입니까? 여기서 무슨 일이야?


이 "핵"을 이해하려면 몇 가지 사항을 이해해야합니다.

  1. 왜 우리가하지 않는 이유 Array(5).map(...)
  2. Function.prototype.apply인수를 처리 하는 방법
  3. Array여러 인수를 처리하는 방법
  4. Number함수가 인수를 처리 하는 방법
  5. 무엇 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정의된다. 대부분 우리가 신경 쓰지 않는 것들이지만 흥미로운 부분은 다음과 같습니다.

  1. 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

callapply정의의 형제, 섹션 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 is null which does not matter for the Array constructor (this is the same this as in the context according to 15.3.4.3.2.a.
  • Then new Array is called being passed an object with a length 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 yields undefined the array constructor is called with five arguments whose value is undefined (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 case Number.call) on each element of the array and uses the specified this value (in this case setting the this 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 with this as undefined (the array value) and the index as the parameter. So it's basically the same as mapping each undefined to its array index (since calling Number 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

반응형