当前位置: 首页 > 面试题库 >

用JavaScript创建范围-奇怪的语法

赵炯
2023-03-14
问题内容

我在es-discuss邮件列表中遇到了以下html" target="_blank">代码:

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

这产生

[0, 1, 2, 3, 4]

为什么这是代码的结果?这里发生了什么事?


问题答案:

了解此“ hack”需要了解几件事:

  1. 为什么我们不只是做 Array(5).map(...)
  2. 如何Function.prototype.apply处理论点
  3. 如何Array处理多个参数
  4. Number函数如何处理参数
  5. 是什么Function.prototype.call

它们是javascript中相当高级的主题,因此它的时间要长得多。我们将从顶部开始。系好安全带!

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.lengthkey=>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映射。

ew,所以这可能不比上一部分短。但是,当我们完成时会有蛋糕,所以请耐心等待!但是,在接下来的部分(我保证会很短)之后,我们可以开始剖析表达式。万一您忘记了,问题是以下工作原理:

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)

这将是更简单,更轻松的部分,因为它并不太依赖晦涩的hack。

4.如何Number对待输入

Doing
Number(something)(第15.7.1节)将转换something为数字,仅此而已。它的操作方式有些复杂,尤其是在字符串的情况下,但是如果您感兴趣的话,该操作在第9.3节中定义。

5.游戏 Function.prototype.call

call是15.3.4.4节中apply定义的的兄弟。它不采用参数数组,而只是采用接收到的参数并将它们向前传递。

当您将多个链接call在一起时,事情变得很有趣,将怪异的事物提高到11:

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]


 类似资料:
  • 你好,我正在为我的openGL项目制作一个图像和纹理类,当我添加stb_image一些纹理时,就开始在图像边缘添加噪声。 图像类别: 纹理类: 图像为128x128像素 以下是它在 paint.net 中的样子: 以下是渲染时的外观: 我相信图像加载很好,因为我用图像类设置了窗口的图标,看起来很好。我认为问题源于纹理类。 解决了Jérôme Richard为我解决了这个问题。这不起作用的原因是因为

  • 发现了一个非常有趣的问题,在调试后找到了重现它的场景。 因此,如果我有一个具有包范围B的类,它有一些公共方法和扩展它的公共类A: 然后在测试中: 你猜怎么着,我得到了我刚刚抛出的NullPointerException,所以Mockito以某种方式创建了一个“真实”的对象,并调用了一个真正的方法,而不是一个被嘲笑的方法。为什么会这样? 在上面的示例中,我在类<code>B<code>中更改了<co

  • 问题内容: 知道为什么我会收到这个例外吗? 问题答案: 我怀疑如果是接口(是吗?),您将不会遇到此错误。我相信您可能有一个正在使用cglib代理,执行魔术等操作的类,最后,它不能安全地转换为setter或构造函数中的参数。尝试对接口进行编程,看看错误是否消失。 更新 :不是接口。这是一个扩展的类。 鉴于此,我建议您尝试以下操作: 重命名为。 从已命名的接口中提取一个接口(例如“ ”) 浏览所有使用

  • 问题内容: 我正在尝试创建一个select元素,该元素的页面编号为1,其中pages是一个变量,即我所拥有的页面数。我不知道该怎么做,是为了构造ng- options表达式,以便它将给我所需的数字。这是我到目前为止的 我需要在ng-options表达式中放置什么才能使其创建我的select 我是否需要创建一个返回数字数组的函数并以某种方式在其中使用它,还是有一种更简单的方法呢? 任何帮助将不胜感激

  • 问题内容: 在弄乱Eclipse中的自定义格式设置选项的同时,在示例代码之一中,我看到了如下代码: 我从未见过像这样使用过,并且我从事Java编程已有9年了!有谁知道你为什么要这么做?这样做的可能用例/好处是什么? 我看到的另一段代码,我认为这是一个非常有用的速记,因此我也在这里共享了它,它的作用很明显: 问题答案: 它是在Java 7中添加的。它称为try-with- resources 语句。

  • 问题内容: 我有一个表,其中的行带有范围的开始和结束编号,例如 等等.. 我想创建一个临时表(或表变量/ cte等),其中每个数字都有一行,并且它们之间的范围也包括在内-即给出上述示例,我希望看到一个具有以下行的表: 谁能指出我朝着实现这一目标的快捷方式的方向?我考虑过以某种方式使用数字表,但是我正在查看的表具有> 200m行,而我没有那么大的数字表! 任何帮助深表感谢。提前致谢。 问题答案: