8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

为什么我在函数内部修改变量后它保持不变? - 异步代码参考

MP-SATARA 1月前

158 0

给出以下示例,为什么在所有情况下 outerScopeVar 都未定义?var outerScopeVar;var img = document.createElement('img');img.onload = function() { outerScopeVar = this.width;};...

给出以下例子,为什么 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);
// with observables
var outerScopeVar;
myObservable.subscribe(function (value) {
    outerScopeVar = value;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

在所有这些示例中 undefined 都会输出 为什么 会发生这种情况。


注意: JavaScript 异步性 的典型问题 。欢迎改进此问题并添加更多社区可以识别的简化示例。

帖子版权声明 1、本帖标题:为什么我在函数内部修改变量后它保持不变? - 异步代码参考
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由MP-SATARA在本站《object》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 简短的回答是: 异步。

    为什么需要异步?

    JavaScript 是单线程的,这意味着脚本的两个部分不能同时运行;它们必须一个接一个地运行。在浏览器中,JavaScript 与大量其他内容共享一个线程,这些内容因浏览器而异。但通常 JavaScript 与绘制、更新样式和处理用户操作(例如突出显示文本和与表单控件交互)位于同一队列中。其中一项活动会延迟其他活动。

    您可能已经使用事件和回调来解决这个问题。以下是事件:

    var img1 = document.querySelector('.img-1');
    
    img1.addEventListener('load', function() {
      // image loaded
      console.log("Loaded");
    });
    
    img1.addEventListener('error', function() {
      // error caught
      console.log("Error printed");
    });
    <img class="img-1" src="#" alt="img">

    这根本不是打喷嚏。我们获取图像,添加几个侦听器,然后 JavaScript 可以停止执行,直到其中一个侦听器被调用。

    不幸的是,在上面的例子中,事件可能在我们开始监听它们之前就发生了,所以我们需要使用图像的“complete”属性来解决这个问题:

    var img1 = document.querySelector('.img-1');
    
    function loaded() {
      // image loaded
      console.log("Loaded");
    }
    
    if (img1.complete) {
      loaded();
    } else {
      img1.addEventListener('load', loaded);
    }
    
    img1.addEventListener('error', function() {
      // error caught
      console.log("Error printed");
    });
    <img class="img-1" src="#" alt="img">

    这不会在我们有机会监听之前捕获出错的图像;不幸的是,DOM 没有提供这样做的方法。此外,这只是加载一张图片。如果我们想知道一组图像何时加载,事情会变得更加复杂。

    活动并不总是最好的方式

    事件非常适合在同一个对象上发生多次的事情 - keyup , touchstart 等等。对于这些事件,您并不真正关心在附加侦听器之前发生了什么。

    正确执行此操作的两种主要方法是回调和承诺。

    回调

    回调函数是传递到其他函数的参数中的函数,此过程在 JavaScript 中有效,因为函数是对象,而对象可以作为参数传递给函数。回调函数的基本结构如下所示:

    function getMessage(callback) {
      callback();
    }
    
    function showMessage() {
      console.log("Hello world! I am a callback");
    }
    getMessage(showMessage);

    承诺

    尽管有办法用原生 JS 避免回调地狱,但 Promise 越来越受欢迎,目前已在 ES6 中标准化 (请参阅 Promise) .

    Promise代表异步操作的最终结果(值)

    • 承诺占位符将被结果值(如果成功)或失败原因(如果不成功)替换

    如果您不需要知道某件事何时发生,而只需要知道它是否发生,那么承诺就是您所寻找的。

    承诺有点像事件监听器,不同之处在于:

    • Promise 只能成功或失败一次
    • 承诺不能从失败转变为成功,反之亦然
    • 一旦你有了结果,承诺就是不可改变的
    • 如果 Promise 成功或失败,并且你稍后添加了成功/失败回调,则会调用正确的回调
    • 事件是否发生在你添加回调之前并不重要

    注意:始终从 Promise 内的函数返回结果,否则后续函数就无法执行。

    Promise 术语

    承诺可以是:

    • 已实现:与承诺相关的操作已成功完成
      • the asynchronous operation has completed
      • the promise has a value
      • the promise will not change again
    • 拒绝:与承诺相关的操作失败
      • the asynchronous operation failed
      • the promise will never be fulfilled
      • the promise has a reason indicating why the operation failed
      • the promise will not change again
    • 待处理:尚未完成或拒绝
      • the asynchronous operation hasn't been completed yet
      • can transition to fulfilled or rejected
    • 已解决:已完成或已拒绝,因此不可改变

    如何创建承诺

    function getMessage() {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve('Hello world! I am a promise');
        }, 0);
      });
    }
    
    getMessage().then(function(message) {
      console.log(message);
    });
  • 在所有这些场景中, outerScopeVar 异步 修改或分配一个值 ,或者 在稍后的时间发生(等待或监听某个事件的发生),当前执行不会等待 。所以在所有这些情况下,当前执行流都会导致 outerScopeVar = undefined

    让我们讨论每个例子(我标记了异步调用或延迟发生某些事件的部分):

    1.

    enter image description here

    这里我们注册了一个事件列表,该列表将在特定事件发生时执行。这里加载图像。然后当前执行继续下一行 img.src = 'lolcat.png'; alert(outerScopeVar); 同时事件可能不会发生。即,函数 img.onload 等待引用的图像异步加载。这将发生在以下所有示例中 - 事件可能有所不同。

    2.

    2

    此处超时事件起着作用,它将在指定时间后调用处理程序。它在这里 0 ,但它仍然注册了一个异步事件,它将被添加到 Event Queue 执行的最后位置,从而保证延迟。

    3.

    这次是ajax回调。

    4.

    enter image description here

    Node可以看作是异步编码之王。这里标记的函数被注册为回调处理程序,它将在读取指定的文件后执行。

    5.

    enter image description here

    明显的承诺(某事将会在未来完成)是异步的。请参阅 JavaScript 中的 Deferred、Promise 和 Future 之间有什么区别?

    https://www.quora.com/Whats-the-difference- Between-a-promise-and-a-callback-in-Javascript

  • 其他答案都很棒,我只想提供一个直接的答案。仅限于 jQuery 异步调用

    所有 ajax 调用(包括 $.get $.post $.ajax )都是异步的。

    考虑到你的例子

    var outerScopeVar;  //line 1
    $.post('loldog', function(response) {  //line 2
        outerScopeVar = response;
    });
    alert(outerScopeVar);  //line 3
    

    代码从第 1 行开始执行,在第 2 行声明变量并触发异步调用(即 post 请求),并从第 3 行继续执行,而无需等待 post 请求执行完成。

    假设 post 请求需要 10 秒才能完成, outerScopeVar 那么 的值只会在 10 秒后设置。

    尝试一下,

    var outerScopeVar; //line 1
    $.post('loldog', function(response) {  //line 2, takes 10 seconds to complete
        outerScopeVar = response;
    });
    alert("Lets wait for some time here! Waiting is fun");  //line 3
    alert(outerScopeVar);  //line 4
    

    现在,当您执行此操作时,您会在第 3 行收到警报。现在等待一段时间,直到您确定发布请求已返回某个值。然后,当您在警报框上单击“确定”时,下一个警报将打印预期值,因为您等待了它。

    在现实生活中,代码变成,

    var outerScopeVar;
    $.post('loldog', function(response) {
        outerScopeVar = response;
        alert(outerScopeVar);
    });
    

    所有依赖于异步调用的代码都被移到异步块内,或者等待异步调用。

  • @broccoli2000 我这样说并不是说这个问题很明显,而是说杯子在图画中代表什么很明显:)

  • 而尝试使异步函数同步执行就好比尝试在 1 秒时喝下咖啡,然后在 1 分钟时将其倒到你的腿上。

  • 显而易见的是,杯子代表着 outerScopeVar .

    异步函数就像......

    asynchronous call for coffee

  • 由于我正在运行旧的软件包,因此您提供的 $.Deferred() 实现正是我在 jQuery 中寻找的,谢谢。

  • @Chiwda 您只需将回调参数放在最后:function getMessage(param1,param2,callback){...}。

  • 这一切都很好,但如果您需要使用参数调用 getMessage() 怎么办?在这种情况下,您将如何编写上述代码?

  • 您的 Promises 示例基本上就是我过去几个小时一直在寻找的。您的示例非常漂亮,同时还解释了 Promises。为什么其他地方没有这个示例,真是令人困惑。

  • 对于那些寻求快速参考以及使用承诺和异步/等待的一些示例的人来说,这是一个更简洁的答案。

    从简单的方法(不起作用)开始,该方法调用异步方法(在本例中 setTimeout )并返回一条消息:

    function getMessage() {
      var outerScopeVar;
      setTimeout(function() {
        outerScopeVar = 'Hello asynchronous world!';
      }, 0);
      return outerScopeVar;
    }
    console.log(getMessage());
    

    undefined 在这种情况下被记录,因为 getMessage 之前返回 setTimeout 并更新 outerScopeVar .

    解决这个问题的两种主要方法是使用 回调 承诺

    回调

    这里的变化是 getMessage 接受一个 callback 参数,一旦结果可用,该参数将被调用以将结果传回调用代码。

    function getMessage(callback) {
      setTimeout(function() {
        callback('Hello asynchronous world!');
      }, 0);
    }
    getMessage(function(message) {
      console.log(message);
    });
    

    承诺

    Promises 提供了一种比回调更灵活的替代方案,因为它们可以自然地组合起来以协调多个异步操作。Node.js Promises/A+ Bluebird 等库中实现 Q .

    function getMessage() {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve('Hello asynchronous world!');
        }, 0);
      });
    }
    
    getMessage().then(function(message) {
      console.log(message);  
    });
    

    jQuery 延迟

    jQuery 通过其 Deferreds 提供了与承诺类似的功能。

    function getMessage() {
      var deferred = $.Deferred();
      setTimeout(function() {
        deferred.resolve('Hello asynchronous world!');
      }, 0);
      return deferred.promise();
    }
    
    getMessage().done(function(message) {
      console.log(message);  
    });
    

    异步/等待

    如果你的 JavaScript 环境包含对 async await 函数 async 内同步使用 promise

    function getMessage () {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('Hello asynchronous world!');
            }, 0);
        });
    }
    
    async function main() {
        let message = await getMessage();
        console.log(message);
    }
    
    main();
    
  • 我怎么知道 img.onload=function(){.. 是异步的,我也许可以将其重写为 function img.onload(){.. 虽然这是不可能的,但它似乎是同步的。这是否在于人们在阅读了大量这些废话之后应该得到的 JS 感受?

  • 最后的代码示例展示了另一个参数的用法 - 此处为 src - 它与回调函数无关,甚至在回调后插入代码中。正如 Mastermind 所写,简洁明了!

  • 但是,如果您想在不同的计算中使用结果,或者将其存储在对象变量中,那么警报或发送到控制台有什么用呢?

  • Fabrício 的回答非常正确;但我想用一些不太技术性的东西来补充他的回答,重点是通过类比来帮助解释异步性的概念


    打个比方……

    昨天,我工作中需要一位同事提供一些信息。我给他打了电话;以下是通话内容:

    :嗨,鲍勃,我想知道我们酒吧。吉姆想要一份报告,而你是唯一一个知道细节的人。

    鲍勃 :当然可以,但是要花大约 30 分钟的时间吧?

    :太好了,鲍勃。等你了解情况后再给我打电话吧!

    这时,我挂断了电话。由于我需要 Bob 提供的信息来完成我的报告,所以我放下报告去喝了杯咖啡,然后查看了一些电子邮件。40 分钟后(Bob 很慢),Bob 回电并给了我所需的信息。此时,我又继续写报告,因为我已经掌握了所需的所有信息。


    想象一下如果对话是这样的;

    :嗨,鲍勃,我想知道我们酒吧。吉姆想要一份报告,而你是唯一一个知道细节的人。

    鲍勃 :当然可以,但是要花大约 30 分钟的时间吧?

    :太好了,鲍勃。我会等的。

    我坐在那里等着。等了又等。等了 40 分钟。什么也没做,只是等待。最后,鲍勃给了我信息,我们挂了电话,我完成了报告。但我损失了 40 分钟的生产力。


    这是异步与同步行为

    这正是我们问题中所有示例中的情况。加载图像、从磁盘加载文件以及通过 AJAX 请求页面都是缓慢的操作(在现代计算环境中)。

    JavaScript 允许您注册一个回调函数,该函数将在慢速操作完成后执行, 而不是 等待 其他代码 使行为变得 异步 。如果 JavaScript 等待操作完成后再执行任何其他代码,这将是 同步 行为。

    var outerScopeVar;    
    var img = document.createElement('img');
    
    // Here we register the callback function.
    img.onload = function() {
        // Code within this function will be executed once the image has loaded.
        outerScopeVar = this.width;
    };
    
    // But, while the image is loading, JavaScript continues executing, and
    // processes the following lines of JavaScript.
    img.src = 'lolcat.png';
    alert(outerScopeVar);
    

    在上面的代码中,我们要求 JavaScript 加载 lolcat.png ,这是一个 很慢的 操作。回调函数将在这个缓慢的操作完成后执行,但与此同时,JavaScript 将继续处理下一行代码;即 alert(outerScopeVar) .

    这就是我们看到显示警报的原因 undefined ;因为是 alert() 立即处理的,而不是在图像加载之后处理的。

    为了修复我们的代码,我们要做的就是将<代码> alert(outerScopeVar) code。因此,我们不再需要将 outerScopeVar 变量声明为全局变量。

    var img = document.createElement('img');
    
    img.onload = function() {
        var localScopeVar = this.width;
        alert(localScopeVar);
    };
    
    img.src = 'lolcat.png';
    

    总是 看到回调被指定为一个函数,因为这是 JavaScript 中定义某些代码但稍后才执行的唯一方法。

    因此,在我们所有的示例中,都是 function() { /* Do something */ } 回调;要修复 所有 示例,我们所要做的就是将需要操作响应的代码移到那里!

    * Technically you can use eval() as well, but eval()对于此目的来说是邪恶的 for this purpose


    如何让呼叫者等待?

    您目前可能有一些类似于此的代码;

    function getWidthOfImage(src) {
        var outerScopeVar;
    
        var img = document.createElement('img');
        img.onload = function() {
            outerScopeVar = this.width;
        };
        img.src = src;
        return outerScopeVar;
    }
    
    var width = getWidthOfImage('lolcat.png');
    alert(width);
    

    但是,我们现在知道 会 return outerScopeVar 立即发生;在 onload 回调函数更新变量之前。这会导致 getWidthOfImage() 返回 undefined ,并 undefined 发出警报。

    为了解决这个问题,我们需要允许函数调用 getWidthOfImage() 注册一个回调,然后将宽度的警报移动到该回调内;

    function getWidthOfImage(src, cb) {     
        var img = document.createElement('img');
        img.onload = function() {
            cb(this.width);
        };
        img.src = src;
    }
    
    getWidthOfImage('lolcat.png', function (width) {
        alert(width);
    });
    

    ...和以前一样,请注意我们已经能够删除全局变量(在本例中 width )。

  • 引用 17

    您需要了解,红色圆圈代码之所以是异步的,只是因为它是由 NATIVE 异步 javascript 函数执行的。这是您的 javascript 引擎的一个功能——无论是 Node.js 还是浏览器。它是异步的,因为它作为 \'回调\' 传递给本质上是黑盒的函数(用 C 等实现)。对于倒霉的开发人员来说,它们是异步的……只是因为。如果您想编写自己的异步函数,您必须通过将其发送到 SetTimeout(myfunc,0) 来破解它。你应该这样做吗?另一个争论……可能不是。

  • 这是死锁吗?felix kling 指着你的答案,而你指着 felix 的答案

  • 代码示例有点奇怪,因为您在调用函数后声明它。当然,这是因为提升而起作用,但这是故意的吗?

  • 在您的最后一个例子中,使用匿名函数有什么特殊原因吗?或者使用命名函数是否可以达到相同的效果?

返回
作者最近主题: