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

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

MP-SATARA 1月前

146 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)
  • 答案只有一个词: 异步性 .

    前言

    这个话题在 Stack Overflow 上至少被重复了几千次。因此,首先我想指出一些非常有用的资源:

    • p2

    • p3


    当前问题的答案

    让我们首先追溯一下常见的行为。在所有示例中,都 outerScopeVar 函数 内部进行了修改 。该函数显然不会立即执行;它被赋值或作为参数传递。这就是我们所说的 回调 .

    现在的问题是,什么时候调用该回调?

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

    • img.onload 当图像成功加载时(如果成功加载), 在将来的某个时间 被调用
    • setTimeout 可能会 在延迟到期后某个时间 ,并且超时尚未被取消 clearTimeout 。注意:即使使用 0 延迟,所有浏览器都有最小超时延迟上限(在 HTML5 规范中指定为 4 毫秒)。
    • 当 Ajax 请求成功完成时(并且如果成功完成), $.post jQuery 的回调可能会 在将来的某个时间
    • 当文件读取成功或者抛出错误时, fs.readFile Node.js 可能会 在将来的某个时间

    在未来某个时间 运行的回调 。这个“未来某个时间”就是我们所说的 异步流 .

    异步执行被推出同步流程。也就是说,在同步代码堆栈执行时,异步代码永远不会 执行 。这就是 JavaScript 单线程的含义。

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

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

    async code highlighted

    简而言之,回调函数是同步创建的,但异步执行的。您不能依赖异步函数的执行,除非您知道它已经执行,那么如何做到这一点?

    其实很简单。依赖于异步函数执行的逻辑应该从这个异步函数内部启动/调用。例如,在回调函数内部移动 s alert console.log s 将输出预期结果,因为此时结果可用。

    实现自己的回调逻辑

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

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

    注意: 我使用 setTimeout 随机延迟作为通用异步函数;相同示例适用于 Ajax、 readFile , onload 和任何其他异步流。

    这个例子显然与其他例子存在同样的问题;它没有等待异步函数执行。

    让我们通过实现我们自己的回调系统来解决这个问题。首先,我们要摆脱那个丑陋的东西,因为 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 an 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 an 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 an 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 从异步回调中获取值,而必须使用回调模式,或者......承诺。

    承诺

    尽管有一些方法可以通过 vanilla JS 避免 回调地狱 ,但 Promise 越来越受欢迎,并且目前正在 ES6 中进行标准化(请参阅 Promise - MDN )。

    Promises(又名 Futures)提供了更线性、更令人愉悦的异步代码阅读体验,但解释其全部功能超出了本文的范围。相反,我将为感兴趣的人留下这些优秀的资源:

    • JavaScript 承诺 - HTML5 Rocks
    • 你错过了承诺的意义 - domenic.me

    有关 JavaScript 异步性的更多阅读材料


    注意: 我已将此答案标记为社区 Wiki。因此,任何拥有至少 100 个声誉的人都可以编辑和改进它!如果您愿意,请随意改进此答案或提交一个全新的答案。

    我想将这个问题变成一个规范主题来回答与 Ajax 无关的异步问题(有关如何 从 AJAX 调用返回响应? ),因此这个主题需要您的帮助才能尽可能好和有用!

返回
作者最近主题: