用JavaScript创建范围 - 奇怪的语法 [英] Creating range in JavaScript - strange syntax
问题描述
我在es-discuss邮件列表中遇到了以下代码:
I've run into the following code in the es-discuss mailing list:
Array.apply(null, { length: 5 }).map(Number.call, Number);
这会产生
[0, 1, 2, 3, 4]
为什么这是结果的代码?这里发生了什么?
Why is this the result of the code? What's happening here?
推荐答案
理解这个黑客需要理解几件事:
Understanding this "hack" requires understanding several things:
- 为什么我们不做
Array(5).map(...)
- 如何
Function.prototype.apply
处理参数 - 如何
数组
处理多个参数 -
Number
函数如何处理参数 - 什么
Function.prototype.call
确实
- Why we don't just do
Array(5).map(...)
- How
Function.prototype.apply
handles arguments - How
Array
handles multiple arguments - How the
Number
function handles arguments - What
Function.prototype.call
does
他们在javascript中是相当高级的主题,所以这将是超过相当长的时间。我们将从顶部开始。扣上!
They're rather advanced topics in javascript, so this will be more-than-rather long. We'll start from the top. Buckle up!
什么是数组,真的吗?包含整数键的常规对象,映射到值。它有其他特殊功能,例如神奇的长度
变量,但在它的核心,它是一个常规的 key => value
map,就像任何其他对象一样。让我们稍微玩一下阵列,不管吗?
What's an array, really? A regular object, containing integer keys, which map to values. It has other special features, for instance the magical length
variable, but at it's core, it's a regular key => value
map, just like any other object. Let's play with arrays a little, shall we?
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
的数量映射array has,可以不同于 arr.length
。
We get to the inherent difference between the number of items in the array, arr.length
, and the number of key=>value
mappings the array has, which can be different than arr.length
.
通过扩展数组arr.length
没有创建任何新的 key =>值
映射,所以并不是数组未定义值,它没有这些键。当您尝试访问不存在的属性时会发生什么?你得到 undefined
。
Expanding the array via arr.length
does not create any new key=>value
mappings, so it's not that the array has undefined values, it does not have these keys. And what happens when you try to access a non-existent property? You get undefined
.
现在我们可以抬起头来看看为什么这样的功能如 arr.map
不要遍历这些属性。如果 arr [3]
仅仅是未定义的,并且密钥存在,那么所有这些数组函数都会像其他任何值一样覆盖它:
Now we can lift our heads a little, and see why functions like arr.map
don't walk over these properties. If arr[3]
was merely undefined, and the key existed, all these array functions would just go over it like any other value:
//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
会引发错误,但它没有'吨。要证明 :
I intentionally used a method call to further prove the point that the key itself was never there: Calling undefined.toUpperCase
would have raised an error, but it didn't. To prove that:
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
现在我们到达我的观点:如何数组(N)
做的事情。 第15.4.2.2节描述了该过程。有一堆我们不关心的mumbo jumbo,但是如果你设法在各行之间阅读(或者你可以相信我这个,但不要),它基本归结为:
And now we get to my point: How Array(N)
does things. Section 15.4.2.2 describes the process. There's a bunch of mumbo jumbo we don't care about, but if you manage to read between the lines (or you can just trust me on this one, but don't), it basically boils down to this:
function Array(len) {
var ret = [];
ret.length = len;
return ret;
}
(根据假设(在实际规格中检查)运行 len
是一个有效的uint32,而不仅仅是任意数量的值)
(operates under the assumption (which is checked in the actual spec) that len
is a valid uint32, and not just any number of value)
现在你可以看到为什么要做 Array(5).map(...)
不起作用 - 我们没有在数组上定义 len
项,我们不创建 key =>值
映射,我们只需更改长度
属性。
So now you can see why doing Array(5).map(...)
wouldn't work - we don't define len
items on the array, we don't create the key => value
mappings, we simply alter the length
property.
现在我们有了让我们看看第二个神奇的事情:
Now that we have that out of the way, let's look at the second magical thing:
什么申请
它基本上是一个数组,并将其作为函数调用的参数展开。这意味着以下几乎是相同的:
What apply
does is basically take an array, and unroll it as a function call's arguments. That means that the following are pretty much the same:
function foo (a, b, c) {
return a + b + c;
}
foo(0, 1, 2); //3
foo.apply(null, [0, 1, 2]); //3
现在,我们可以轻松了解如何应用
只需记录参数
特殊变量:
Now, we can ease the process of seeing how apply
works by simply logging the arguments
special variable:
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]
很容易证明我在倒数第二个例子中的主张:
It's easy to prove my claim in the second-to-last example:
function ahaExclamationMark () {
console.log(arguments.length);
console.log(arguments.hasOwnProperty(0));
}
ahaExclamationMark.apply(null, Array(2)); //2, true
(是的,双关语意图)。 键=>值
映射可能不存在于我们传递给 apply
的数组中,但它确实存在于参数中
变量。这与上一个示例的工作原理相同:我们传递的对象上不存在键,但它们确实存在于参数
中。
(yes, pun intended). The key => value
mapping may not have existed in the array we passed over to apply
, but it certainly exists in the arguments
variable. It's the same reason the last example works: The keys do not exist on the object we pass, but they do exist in arguments
.
为什么?我们来看看第15.3.4.3节,其中 Function.prototype .apply
已定义。主要是我们不关心的事情,但这里是有趣的部分:
Why is that? Let's look at Section 15.3.4.3, where Function.prototype.apply
is defined. Mostly things we don't care about, but here's the interesting portion:
- 让len是使用参数length调用argArray的[[Get]]内部方法的结果。
这基本上意味着: argArray.length
。然后规范继续为做一个简单的循环
length
项目,制作一个列表
对应的值( list
是一些内部巫术,但它基本上是一个数组)。就非常非常松散的代码而言:
Which basically means: argArray.length
. The spec then proceeds to do a simple for
loop over length
items, making a list
of corresponding values (list
is some internal voodoo, but it's basically an array). In terms of very, very loose code:
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
在这种情况下,是一个长度
属性的对象。现在我们可以看到为什么值未定义,但键不是,在参数
:我们创建 key =>值
mappings。
So all we need to mimic an argArray
in this case is an object with a length
property. And now we can see why the values are undefined, but the keys aren't, on arguments
: We create the key=>value
mappings.
Phew,所以这可能不会比前一部分短。但是当我们完成时会有蛋糕,所以请耐心等待!但是,在接下来的部分(这将是简短的,我保证)之后,我们可以开始剖析表达式。万一你忘了,问题是如何工作:
Phew, so this might not have been shorter than the previous part. But there'll be cake when we finish, so be patient! However, after the following section (which'll be short, I promise) we can begin dissecting the expression. In case you forgot, the question was how does the following work:
Array.apply(null, { length: 5 }).map(Number.call, Number);
3。如何数组
处理多个参数
所以!我们看到当你将 length
参数传递给 Array
时会发生什么,但是在表达式中,我们传递了几个作为参数的东西(确切地说,数组为5 undefined
)。 第15.4.2.1节告诉我们该怎么做。最后一段对我们来说都很重要,并且它的措辞真的奇怪,但它归结为:
3. How Array
handles multiple arguments
So! We saw what happens when you pass a length
argument to Array
, but in the expression, we pass several things as arguments (an array of 5 undefined
, to be exact). Section 15.4.2.1 tells us what to do. The last paragraph is all that matters to us, and it's worded really oddly, but it kind of boils down to:
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]
Tada!我们得到一个包含几个未定义值的数组,并返回这些未定义值的数组。
Tada! We get an array of several undefined values, and we return an array of these undefined values.
最后,我们可以解读以下内容:
Finally, we can decipher the following:
Array.apply(null, { length: 5 })
我们看到它返回一个包含5个未定义值的数组,所有键都存在。
We saw that it returns an array containing 5 undefined values, with keys all in existence.
现在,到表达式的第二部分:
Now, to the second part of the expression:
[undefined, undefined, undefined, undefined, undefined].map(Number.call, Number)
这将更容易,非复杂的部分,因为它不太依赖于模糊的黑客。
This will be the easier, non-convoluted part, as it doesn't so much rely on obscure hacks.
执行数字(某事)
(第15.7.1节)转换某事
到一个数字,就是这样。它是如何做的有点复杂,特别是在字符串的情况下,但操作在第9.3节中定义如果您有兴趣。
Doing Number(something)
(section 15.7.1) converts something
to a number, and that is all. How it does that is a bit convoluted, especially in the cases of strings, but the operation is defined in section 9.3 in case you're interested.
call
is apply
的兄弟,在第15.3节中定义.4.4 。它不是取一个参数数组,而是接受它收到的参数,并将它们传递给它们。
call
is apply
's brother, defined in section 15.3.4.4. Instead of taking an array of arguments, it just takes the arguments it received, and passes them forward.
当你链接多个一起调用
,将奇怪的变为11:
Things get interesting when you chain more than one call
together, crank the weird up to 11:
function log () {
console.log(this, arguments);
}
log.call.call(log, {a:4}, {a:5});
//{a:4}, [{a:5}]
//^---^ ^-----^
// this arguments
在你掌握正在发生的事情之前,这是非常有用的。 log.call
只是一个函数,相当于任何其他函数的调用
方法,因此具有也可以调用
方法:
This is quite wtf worthy until you grasp what's going on. log.call
is just a function, equivalent to any other function's call
method, and as such, has a call
method on itself as well:
log.call === log.call.call; //true
log.call === Function.call; //true
调用什么
做什么?它接受 thisArg
和一堆参数,并调用其父函数。我们可以通过 apply
来定义它(再次,非常宽松的代码,不起作用):
And what does call
do? It accepts a thisArg
and a bunch of arguments, and calls its parent function. We can define it via apply
(again, very loose code, won't work):
Function.prototype.call = function (thisArg) {
var args = arguments.slice(1); //I wish that'd work
return this.apply(thisArg, args);
};
让我们跟踪这种情况如何下降:
Let's track how this goes down:
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
全部
尚未结束。让我们看看当你为大多数数组方法提供函数时会发生什么:
The later part, or the .map
of it all
It's not over yet. Let's see what happens when you supply a function to most array methods:
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
如果我们自己不提供此
参数,则默认为 window
。记下为我们的回调提供参数的顺序,让我们再一次把它变为11:
If we don't provide a this
argument ourselves, it defaults to window
. Take note of the order in which the arguments are provided to our callback, and let's weird it up all the way to 11 again:
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
已定义,以下几乎发生:
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);
}
所以,我们得到这个:
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)
有效:
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);
返回 i
的转换,当前指数,到一个数字。
Which returns the transformation of i
, the current index, to a number.
表达式
Array.apply(null, { length: 5 }).map(Number.call, Number);
分为两部分:
var arr = Array.apply(null, { length: 5 }); //1
arr.map(Number.call, Number); //2
第一部分创建一个包含5个未定义项目的数组。第二个遍历该数组并获取其索引,从而产生一个元素索引数组:
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]
这篇关于用JavaScript创建范围 - 奇怪的语法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!