【问题标题】:Are Arrow Functions Untraceable?箭头函数是不可追踪的吗?
【发布时间】:2018-07-23 15:53:31
【问题描述】:

我刚刚了解到,写这个:

const setName = name => {
  // set name here
}

与此相反

function setName(name) {
  //set name here
}

原因是第一种方法很难调试与该函数相关的问题,因为它不会出现在堆栈跟踪中。

问题:为什么这不会出现在堆栈跟踪中?

【问题讨论】:

  • 该函数将出现在堆栈跟踪中,它只会被命名为(anonymous),这无济于事。
  • “与此相反”你说的 bad 是什么意思?显示箭头函数未出现在堆栈跟踪中的完整示例。
  • 它没有出现在错误堆栈中,因为您将匿名函数分配给了一个变量。这和const f = function() {//something impressive};是一回事
  • 你在哪里看到'它不会出现在堆栈跟踪中。 '?
  • @oniondomes:它确实出现在 stace 跟踪中,并且是问题 is not anonymous 中的第一个函数。 (您评论中的内容也不是,但您评论中的内容与问题中的内容不同。)

标签: javascript ecmascript-6


【解决方案1】:

箭头函数是不可追踪的吗?

不,如果您指的是能够调试它们、堆栈跟踪等,它们与任何其他函数一样可追溯。

我刚刚了解到,写这个...比这个更糟糕...

不,不是。这不一样。这在客观上并不不好。¹

原因是第一种方法很难调试与该函数相关的问题,因为它不会出现在堆栈跟踪中。

是的,它会的。如果有人告诉你它不会,那他们就错了?——这是可以理解的,因为箭头函数没有声明语法,而且它们的表达式语法没有任何地方可以放置名称;所以假设他们没有名字似乎是合理的。虽然有些人不这样做,但很多人这样做(有关方式和时间的详细信息,请参阅my answer here),包括您的。让我们在堆栈跟踪中查看名称:

const setName = name => {
    throw new Error("Ack!");
};

const wrapper = name => setName(name);

wrapper("foo");
Look in the real console.

如果你运行它,你会在控制台中看到这个:

注意setNamewrapper 是如何列出的。

因此,即使是使用匿名语法创建的函数也可以(有点自相矛盾地)获得名称,而且经常这样做。这不仅仅是特定的 JavaScript 引擎提供的帮助(尽管它曾经是),它是由 JavaScript 规范规定的。

那么什么是匿名函数语法与命名函数语法?让我们快速回顾一下创建函数的五种常用方法(这不是完整的列表——I provide one here——只有五种常用方法):

  • 函数声明(^ 标记表示声明本身, 标记表示函数的内容):

        function example1() { return 42; }
    // ^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−^
    
  • 命名函数表达式(相对很少见):

    const example = function example2() { return 42; };
    //              ^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−^
    
  • 匿名function 表达式:

    const example3 = function() { return 42; };
    //               ^^^^^^^^^^^^−−−−−−−−−−−−^
    
    
  • 一个“详细”的箭头函数表达式(有一个函数体块,没有隐含的return):

    const example4 = () => { return 42; };
    //               ^^^^^^^−−−−−−−−−−−−^
    
  • 一个“简洁”的箭头函数表达式(没有函数体块,隐含return):

    const example5 = () => 42;
    //               ^^^^^−−−
    

function 声明总是包含一个名称(有一个模糊的例外²)。 function 表达式可以命名或匿名。所有箭头函数表达式都是匿名的。但是这些示例中的所有函数都有名称(example1example2example3example4example5)。他们的名字是根据他们所属的表达式分配的。 (详情请参阅the answer linked earlier。)

明确一点:一些箭头函数没有名称,就像一些传统函数没有名称一样。例如,这些函数都没有名称:

result = theArray.map(s => s.toUpperCase());
result = theArray.map(function(s) { return s.toUpperCase(); });

传统函数相对于箭头函数的名称的唯一优势在于,它们的表达式语法允许您显式指定名称而不是依赖于上下文,如下所示:

result = theArray.map(function upperCaseCallback(s) { return s.toUpperCase(); });

没有办法在带有箭头函数的单个表达式中做到这一点,你需要这样的东西:

const upperCaseCallback = s => s.toUpperCase();
result = theArray.map(upperCaseCallback);

但即使某些箭头函数是匿名的,您的示例却不是。


¹ 事实上,有一些相反的说法:箭头函数没有prototype 属性和与之关联的对象,调用它不需要设置新的this 绑定或arguments 伪对象和绑定(因为箭头没有自己的 thisarguments 或 [如果相关] super)。所以理论上,它的重量更轻。反驳这是一些可读性问题——“我没有意识到这是一个函数”(尽管我怀疑人们会习惯箭头函数)——function 声明的提升行为有时是有用且没有箭头类似物。

² function 声明总是包含一个名称(有一个模糊的例外²)。” 模糊的例外是这样的:

export default function() { }

这是一个导出函数声明,不是函数表达式,有提升等。这是函数声明唯一不能指定名称的地方。 (不过,该函数确实有一个名称:default。)

【讨论】:

  • 我觉得这真的很有帮助。非常感谢@T.J,你教会了我一些东西。我真的很感激。
  • @T.J,似乎有箭头函数没有名称的例外情况。我试图在下面的答案中记录此案。有没有机会你可以权衡并确保我没有遗漏什么?感谢您的回答以及您可以在下面进行的任何评论! :)
【解决方案2】:

告诉你这个的人可能认为你的箭头函数没有名字。他们也错了。

似乎并非总是如此。 “匿名”函数只有在分配给变量时才会被命名。

const f = () => () => { debugger; };
const h = f();
const g = () => { debugger; };


console.log(f.name) // "f"
console.log(h.name) // ""
console.log(g.name) // "g"


h();
g();

调用 h() 有这个堆栈跟踪:

而调用 g() 有这个堆栈跟踪:

注意“g”是如何命名的,而“h”只是表示“匿名”。

因此,如果您按照上面示例中 f 的编写方式定义部分应用函数,您可能会遇到一些人正在谈论的问题。

我想如果您依赖其他人向您报告错误并向您发送他们的堆栈跟踪,这将是一个主要问题,因为如果您在开发过程中点击 if,您将能够点击它并查看在它。

在某些示例中,您可能可以使用行号到达那里,但如果有人为您提供每个捆绑代码的堆栈跟踪,这可能会成为更大的问题。

您可以通过将函数命名为其他函数的“内部”来避免这种情况...

const f1 = () => {
  const named = () => { debugger; };
  return named;
}

// or

const f2 = () => { 
  return function named() { debugger; }
};

【讨论】:

  • 我没有说所有箭头函数都有名字。其中很多都没有,例如您作为内联回调提供的任何箭头函数。我说OP的箭头函数有一个名字,它确实有。 :-) 问题具体是关于创建函数的两种风格,分配给常量与使用函数声明。
  • @T.J.Crowder 够公平的!我把你的回答读成“不用担心!箭头函数没有陷阱!” (如果您甚至称其为“陷阱”),这可能是在推断您所说的太过分了!感谢回复
猜你喜欢
  • 2017-11-30
  • 2019-08-12
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 2020-06-30
相关资源
最近更新 更多