用JavaScript创建范围 - 奇怪的语法 [英] Creating range in JavaScript - strange syntax

查看:102
本文介绍了用JavaScript创建范围 - 奇怪的语法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在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:


  1. 为什么我们不做 Array(5).map(...)

  2. 如何 Function.prototype.apply 处理参数

  3. 如何数组处理多个参数

  4. Number 函数如何处理参数

  5. 什么 Function.prototype.call 确实

  1. Why we don't just do Array(5).map(...)
  2. How Function.prototype.apply handles arguments
  3. How Array handles multiple arguments
  4. How the Number function handles arguments
  5. 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:



  1. 让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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆