为什么我必须使用匿名函数而不是向“setTimeout”传递附加参数?

发布于 2025-01-10 06:37:25 字数 789 浏览 0 评论 0原文

我想在 1 秒后停止这个 requestAnimationFrame

rAF = requestAnimationFrame(draw);

所以我使用 setTimeout

当我将 cancelAnimationFrame(rAF) 包装在箭头函数中时,它工作正常:

setTimeout(() => cancelAnimationFrame(rAF), 1000);

但是,当我使用 cancelAnimationFrame 作为函数本身并传递 rAF > 作为 setTimeout 的第三个参数,它不起作用:

setTimeout(cancelAnimationFrame, 1000, rAF);

我以为我一开始并不知道 setTimeout 的确切语法。但是,我认为语法没有错误,因为这段代码工作正常:

setTimeout(alert, 1000, "Hello");

为什么它不起作用?

I want to stop this requestAnimationFrame after 1 second:

rAF = requestAnimationFrame(draw);

So I use setTimeout.

When I wrap the cancelAnimationFrame(rAF) in an arrow function, it works fine:

setTimeout(() => cancelAnimationFrame(rAF), 1000);

But, when I use cancelAnimationFrame as the function itself and pass rAF as the third argument to setTimeout, it doesn’t work:

setTimeout(cancelAnimationFrame, 1000, rAF);

I thought that I didn’t know the exact syntax for setTimeout at first. But, I think the syntax isn’t wrong as this code works fine:

setTimeout(alert, 1000, "Hello");

Why doesn’t it work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

零度℉ 2025-01-17 06:37:25

区别在于评估rAF 的时间点。

说明

大概,您的代码如下所示:

let rAF;
const draw = () => {
    // Do some work.
    
    rAF = requestAnimationFrame(draw);
  };

rAF = requestAnimationFrame(draw);

当您执行此操作时:

setTimeout(cancelAnimationFrame, 1000, rAF);

您将三个值传递给 setTimeout:一个函数、一个数字和另一个数字。
执行此语句时,将评估调用,这意味着首先,setTimeout 标识符被解析为函数引用。
其次,评估三个参数:cancelAnimationFrame 标识符解析为函数引用,然后数字文字是数字基元,然后 rAF 标识符解析为另一个数字原始。
然后,执行调用

这就是 setTimeout 看到的全部内容。
在 JavaScript 中,您无法像在 C 中那样传递对数字的引用。

我们假设rAF 最初为1
在一秒钟的时间内,rAF 不断递增,最终达到 61 左右的值。

由于您在开始时注册了 setTimeout,因此该语句

setTimeout(cancelAnimationFrame, 1000, rAF);

等效于

setTimeout(cancelAnimationFrame, 1000, 1);

但是,该语句

setTimeout(() => cancelAnimationFrame(rAF), 1000);

等效于

setTimeout(() => cancelAnimationFrame(1), 1000);

函数体仅在调用时才进行计算。
这意味着,JS 不会“查看函数内部”并尝试计算变量。
该语句本质上意味着“用其他函数和数字 1000 作为参数调用某个函数”。

当一秒结束并且需要取消动画帧时,setTimeout 执行其回调。
如果回调是 () =>; cancelAnimationFrame(rAF),然后执行它,因此函数体被求值:cancelAnimationFrame(rAF)相当于cancelAnimationFrame(61)

然而,在非工作情况下,cancelAnimationFrame保持不变,参数1(相当于setTimeout<时的rAF) /code> 最初被称为)保持不变。
当您已经处于第 61 帧时,您无法取消第 1 帧。

并且 setTimeout(alert, 1000, "Hello"); 当然可以,因为 "Hello" 是静态的,仅评估一次,永不改变。

相关

这是可以检查此行为的更一般情况:

let greeting = "Hello";
const greet = (theGreeting) => console.log(`${theGreeting}, world!`);
const boundGreeting = greet.bind(null, greeting);

greeting = "Goodbye";

boundGreeting(); // Logs "Hello, world!".
greet(greeting); // Logs "Goodbye, world!".

绑定 将第二个参数 (greeting) 作为第一个参数传递给 greet(忽略 null)。
这很像使用 setTimeout(greet, 0,greeting);,只不过这与超时无关,并且我们自己调用(绑定)greet

如果您有一个也接受对象的函数,您可以传递类似引用的东西,即对象,以使其工作:

const timing = {
    rAF: null
  },
  cancelTiming = ({ rAF }) => cancelAnimationFrame(rAF),
  draw = () => {
    // Do some work.
    
    timing.rAF = requestAnimationFrame(draw);
  };

timing.rAF = requestAnimationFrame(draw);
setTimeout(cancelTiming, 1000, timing);

这是有效的,因为当传递timing时,传递的值是一个引用。
改变它,例如通过更新rAF属性,在该引用可见的任何地方都是可见的。
但这使得编程变得相当麻烦。

这与 JavaScript 是按引用传递还是按值传递语言? 无关。

替代方案

还有一个使用 setTimeout 的替代方案。
requestAnimationFrame 调用时它的回调函数,它传递一个 DOMHighResTimeStamp,类似于 performance.now 返回。
因此,您可以在 draw 函数中进行检查:

const timeoutTimestamp = performance.now() + 1000,
  draw = (now) => {
    // Do some work.
    
    if(now < timeoutTimestamp){
      requestAnimationFrame(draw);
    }
  };

requestAnimationFrame(draw);

相关:几秒钟后停止 requestAnimationFrame

The difference is the moment in time at which rAF is evaluated.

Explanation

Presumably, your code looks something like this:

let rAF;
const draw = () => {
    // Do some work.
    
    rAF = requestAnimationFrame(draw);
  };

rAF = requestAnimationFrame(draw);

When you do this:

setTimeout(cancelAnimationFrame, 1000, rAF);

you pass three values to setTimeout: a function, a number, and another number.
When this statement is executed the call is evaluated, meaning that first, the setTimeout identifier is resolved to a function reference.
Second, the three arguments are evaluated: the cancelAnimationFrame identifier is resolved to a function reference, then the number literal is a number primitive, then the rAF identifier is resolved to another number primitive.
Then, the call is performed.

That’s all that setTimeout sees.
In JavaScript, you cannot pass a reference to a number, like you can in C, for example.

Let’s assume rAF is initially 1.
Over the course of one second, rAF has been repeatedly incremented and eventually reaches the value 61 or so.

Since you register the setTimeout at the start, the statement

setTimeout(cancelAnimationFrame, 1000, rAF);

is equivalent to

setTimeout(cancelAnimationFrame, 1000, 1);

However, the statement

setTimeout(() => cancelAnimationFrame(rAF), 1000);

is not equivalent to

setTimeout(() => cancelAnimationFrame(1), 1000);

The function bodies are only evaluated when they are called.
This means, JS doesn’t “peek inside the functions” and attempt to evaluate variables.
That statement essentially means “call some function with some other function and the number 1000 as arguments”.

When the one second is over and it’s time to cancel the animation frame, setTimeout executes its callback.
If the callback is () => cancelAnimationFrame(rAF), then it’s executed, so the function body is evaluated: cancelAnimationFrame(rAF) is equivalent to cancelAnimationFrame(61).

However, in the non-working case, cancelAnimationFrame stays the same, the argument 1 (equivalent to rAF at the time setTimeout was originally called) stays the same.
You can’t cancel frame 1 when you’re already at frame 61.

And setTimeout(alert, 1000, "Hello"); works, of course, because "Hello" is static, is only evaluated once, never changes.

Related

Here’s a more general situation where this behavior can be examined:

let greeting = "Hello";
const greet = (theGreeting) => console.log(`${theGreeting}, world!`);
const boundGreeting = greet.bind(null, greeting);

greeting = "Goodbye";

boundGreeting(); // Logs "Hello, world!".
greet(greeting); // Logs "Goodbye, world!".

bind passes the 2nd parameter (greeting) as the 1st argument to greet (ignore the null).
This is much like using setTimeout(greet, 0, greeting);, except this is unrelated to timeouts and we call (the bound) greet ourselves.

You could pass something like a reference, i.e. an object, to make it work, if you had a function that also accepts an object:

const timing = {
    rAF: null
  },
  cancelTiming = ({ rAF }) => cancelAnimationFrame(rAF),
  draw = () => {
    // Do some work.
    
    timing.rAF = requestAnimationFrame(draw);
  };

timing.rAF = requestAnimationFrame(draw);
setTimeout(cancelTiming, 1000, timing);

This works because when passing timing, the value being passed is a reference.
Mutating it, e.g. by updating the rAF property, is visible everywhere where this reference is visible.
But this makes programming quite cumbersome.

This is tangentially related to Is JavaScript a pass-by-reference or pass-by-value language?.

Alternative

There’s an alternative to using setTimeout.
When requestAnimationFrame calls its callback function, it passes a DOMHighResTimeStamp, similar to what performance.now returns.
So you could make the check in your draw function:

const timeoutTimestamp = performance.now() + 1000,
  draw = (now) => {
    // Do some work.
    
    if(now < timeoutTimestamp){
      requestAnimationFrame(draw);
    }
  };

requestAnimationFrame(draw);

Related: Stop requestAnimationFrame after a couple of seconds.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文