gpt4 book ai didi

javascript - 在函数内部修改变量后,为什么变量未更改? -异步代码引用

转载 作者:行者123 更新时间:2023-12-04 01:58:01 25 4
gpt4 key购买 nike

给定以下示例,为什么在所有情况下outerScopeVar都未定义?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);
var outerScopeVar;
setTimeout(function() {
outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);
// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
});
alert(outerScopeVar);
// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
outerScopeVar = data;
});
console.log(outerScopeVar);
// with promises
var outerScopeVar;
myPromise.then(function (response) {
outerScopeVar = response;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);

为什么在所有这些示例中都输出 undefined?我不需要解决方法,我想知道 为什么会发生

Note: This is a canonical question for JavaScript asynchronicity. Feel free to improve this question and add more simplified examples which the community can identify with.

最佳答案

一个单词的答案:异​​步

前言

在本主题中的Stack Overflow中,已经至少重复了数千次此主题。因此,首先,我想指出一些非常有用的资源:

  • @Felix Kling's answer to "How do I return the response from an asynchronous call?"。请参阅他出色的解释同步和异步流的答案,以及“重组代码”部分。
    @Benjamin Gruenbaum还付出了很多努力来解释同一线程中的异步性。
  • @Matt Esch's answer to "Get data from fs.readFile"还以一种简单的方式很好地解释了异步性。


  • 眼前问题的答案

    让我们首先跟踪常见行为。在所有示例中, outerScopeVar在函数内部进行了修改。该函数显然不会立即执行,而是被分配或作为参数传递。这就是我们所说的 回调

    现在的问题是,何时调用该回调?

    这要视情况而定。让我们尝试再次跟踪一些常见行为:

    当(以及是否)成功加载图像时,将来可能会调用
  • img.onload
  • setTimeout可能在以后的某个时间被调用,该延迟已到期并且clearTimeout尚未取消超时。注意:即使使用0作为延迟,所有浏览器也具有最小超时延迟上限(在HTML5规范中指定为4ms)。
  • jQuery $.post的回调可能在以后(以及是否已经成功完成)Ajax请求的某个时候调用。
  • 当文件已被成功读取或引发错误时,将来可能会调用Node.js的fs.readFile

  • 在所有情况下,我们都有一个回调,它可能在将来的某个时间运行。这个“将来的某个时候”就是我们所说的 异​​步流

    异步执行从同步流中推出。也就是说,异步代码在执行同步代码堆栈时将 从不执行。这就是JavaScript是单线程的意思。

    更具体地说,当JS引擎处于空闲状态时-不执行(a)同步代码的堆栈-它将轮询可能触发异步回调的事件(例如过期的超时,收到的网络响应)并逐个执行它们。这被视为Event Loop

    也就是说,以手绘红色形状突出显示的异步代码只有在其各自代码块中的所有其余同步代码都已执行后才能执行:

    简而言之,回调函数是同步创建的,但异步执行。您只是不能知道异步函数的执行,就不能依靠它的执行,以及如何执行?

    真的很简单。应从该异步函数内部启动/调用依赖于异步函数执行的逻辑。例如,将alertconsole.log移到回调函数中也将输出预期的结果,因为此时该结果可用。

    实现自己的回调逻辑

    通常,您需要根据异步函数的结果执行更多操作,或者根据调用异步函数的位置对结果执行不同的操作。让我们处理一个更复杂的示例:
    var outerScopeVar;
    helloCatAsync();
    alert(outerScopeVar);

    function helloCatAsync() {
    setTimeout(function() {
    outerScopeVar = 'Nya';
    }, Math.random() * 2000);
    }

    注意:我使用具有随机延迟的setTimeout作为通用异步函数,同一示例适用于Ajax,readFileonload和任何其他异步流。

    显然,该示例与其他示例存在相同的问题,它不等待异步函数执行。

    让我们解决实现它自己的回调系统的问题。首先,我们摆脱了这种丑陋的outerScopeVar,它在这种情况下完全没有用。然后,我们添加一个接受函数参数的参数,即回调。当异步操作完成时,我们调用此回调传递结果。实现(请按顺序阅读注释):
    // 1. Call helloCatAsync passing a callback function,
    // which will be called receiving the result from the async operation
    helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    // now do whatever you want with it:
    alert(result);
    });

    // 2. The "callback" parameter is a reference to the function which
    // was passed as argument from the helloCatAsync call
    function helloCatAsync(callback) {
    // 3. Start async operation:
    setTimeout(function() {
    // 4. Finished async operation,
    // call the callback passing the result as argument
    callback('Nya');
    }, Math.random() * 2000);
    }

    上面示例的代码片段:

    // 1. Call helloCatAsync passing a callback function,
    // which will be called receiving the result from the async operation
    console.log("1. function called...")
    helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    // now do whatever you want with it:
    console.log("5. result is: ", result);
    });

    // 2. The "callback" parameter is a reference to the function which
    // was passed as argument from the helloCatAsync call
    function helloCatAsync(callback) {
    console.log("2. callback here is the function passed as argument above...")
    // 3. Start async operation:
    setTimeout(function() {
    console.log("3. start async operation...")
    console.log("4. finished async operation, calling the callback, passing the result...")
    // 4. Finished async operation,
    // call the callback passing the result as argument
    callback('Nya');
    }, Math.random() * 2000);
    }


    在实际使用案例中,大多数情况下,DOM API和大多数库已经提供了回调功能(此演示示例中的helloCatAsync实现)。您只需要传递回调函数,并了解它将在同步流之外执行,并重新组织代码以适应该情况。

    您还将注意到,由于异步性质,不可能将一个异步流中的值return返回到定义了回调的同步流中,因为异步回调是在同步代码已经完成执行很长时间之后执行的。

    而不是return从异步回调中获取值,您将不得不使用回调模式,或者... promise 。

    promise

    尽管可以通过 Vanilla JS来阻止callback hell的产生,但 promise 越来越流行,并且目前已在ES6中进行了标准化(请参阅Promise - MDN)。

    promise (又称 future )提供了一种更加线性,因此令人愉悦的异步代码读取方法,但是解释其整个功能不在此问题的范围之内。相反,我会将这些出色的资源留给感兴趣的人:
  • JavaScript Promises - HTML5 Rocks
  • You're Missing the Point of Promises - domenic.me


  • 有关JavaScript异步性的更多阅读 Material
  • The Art of Node - Callbacks通过 Vanilla JS示例和Node.js代码很好地解释了异步代码和回调。


  • Note: I've marked this answer as Community Wiki, hence anyone with at least 100 reputations can edit and improve it! Please feel free to improve this answer, or submit a completely new answer if you'd like as well.

    I want to turn this question into a canonical topic to answer asynchronicity issues which are unrelated to Ajax (there is How to return the response from an AJAX call? for that), hence this topic needs your help to be as good and helpful as possible!

    关于javascript - 在函数内部修改变量后,为什么变量未更改? -异步代码引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27590541/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com