对于下面的例子,为什么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);

为什么在所有这些例子中输出都是未定义的?我不想要变通办法,我想知道为什么会这样。


注意:这是JavaScript异步性的典型问题。请随意改进这个问题,并添加更多社区可以认同的简化示例。


当前回答

显而易见,杯子代表outerScopeVar。

异步函数就像……

其他回答

其他的答案都很好,我只是想提供一个直接的答案。仅限于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行得到一个警告。现在等待一段时间,直到您确定post请求已经返回了一些值。然后,当您单击警报框上的OK时,下一个警报将打印预期值,因为您一直在等待它。

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

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

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

显而易见,杯子代表outerScopeVar。

异步函数就像……

在所有这些场景中,outerScopeVar都是异步修改或分配值的,或者在稍后发生(等待或监听某些事件的发生),当前执行将不会等待这些事件。所有这些情况下,当前执行流的结果都是outerScopeVar = undefined

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

1.

这里我们注册了一个eventlistner,它将在特定事件时执行。这里加载图像。然后,当前执行继续与下一行img。SRC = 'lolcat.png';和警报(outerScopeVar);与此同时,事件可能不会发生。也就是函数img。Onload等待被引用的图像异步加载。这将发生在所有下面的例子-事件可能不同。

2.

在这里,超时事件起作用,它将在指定的时间之后调用处理程序。这里它是0,但它仍然注册了一个异步事件,它将被添加到事件队列的最后一个位置执行,这使得有保证的延迟。

3.

这次是ajax回调。

4.

节点可以被认为是异步编码的王者。这里标记的函数被注册为回调处理程序,该处理程序将在读取指定文件后执行。

5.

显而易见的承诺(将来会做某事)是异步的。JavaScript中Deferred, Promise和Future的区别是什么?

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

这里有一个更简洁的答案,给那些正在寻找快速参考的人,以及一些使用promises和async/await的例子。

从一个调用异步方法(在本例中是setTimeout)并返回一条消息的函数的朴素方法(这不起作用)开始:

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

在这种情况下,将记录undefined,因为getMessage在调用setTimeout回调并更新outerScopeVar之前返回。

解决它的两种主要方法是使用回调和承诺:

回调

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

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

承诺

承诺提供了一种比回调更灵活的替代方法,因为它们可以自然地组合在一起来协调多个异步操作。promise /A+标准实现在node.js(0.12+)和许多当前浏览器中原生提供,但也在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);  
});

异步- await

如果你的JavaScript环境包含对async和await的支持(比如Node.js 7.6+),那么你可以在async函数中同步使用promises:

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();

简单的答案是:异步性。

为什么需要异步?

JavaScript是单线程的,这意味着脚本的两个部分不能同时运行;他们不得不一个接一个地跑。在浏览器中,JavaScript与不同浏览器的其他内容共享一个线程。但通常JavaScript与绘制、更新样式和处理用户操作(比如突出显示文本和与表单控件交互)处于同一队列中。其中一种物质的活性会延迟其他物质的活性。

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

var img1 = document.querySelector('.img-1'); img1。addEventListener('load', function() { //图像加载 console.log(“加载”); }); img1。addEventListener('错误',函数(){ //错误 console.log(“错误打印”); }); <img class="img-1" src="#" alt="img">

这一点也不打喷嚏。我们获得图像,添加两个侦听器,然后JavaScript可以停止执行,直到其中一个侦听器被调用。

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

var img1 = document.querySelector('.img-1'); 函数loaded() { //图像加载 console.log(“加载”); } If (img1.complete) { 加载(); }其他{ img1。addEventListener(加载,加载); } img1。addEventListener('错误',函数(){ //错误 console.log(“错误打印”); }); <img class="img-1" src="#" alt="img">

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

事情并不总是最好的

事件是伟大的事情,可以发生在同一对象上多次- keyup, touchstart等。对于这些事件,您并不真正关心在连接侦听器之前发生了什么。

正确的方法有两种:回调和承诺。

回调

回调函数是在其他函数的参数中传递的函数,这个过程在JavaScript中是有效的,因为函数是对象,对象可以作为参数传递给函数。回调函数的基本结构是这样的:

函数getMessage(回调){ 回调(); } 函数showMessage() { console.log(“Hello world !我是一个回调”); } getMessage (showMessage);

承诺

尽管在普通JS中有一些方法可以避免回调的麻烦,但是承诺越来越受欢迎,目前正在ES6中标准化(参见承诺)。

promise是一个占位符,表示异步操作的最终结果(值)

承诺占位符将被结果值(如果成功)或失败原因(如果不成功)所取代。

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

promise有点像一个事件监听器,除了:

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

注意:总是返回Promise内部函数的结果,否则后续函数就没有作用了。

承诺的术语

承诺可以是:

fulfilled: The action relating to the promise succeeded the asynchronous operation has completed the promise has a value the promise will not change again rejected: The action relating to the promise failed 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 pending: Hasn't fulfilled or rejected yet the asynchronous operation hasn't been completed yet can transition to fulfilled or rejected settled: Has been fulfilled or rejected and is thus immutable

如何创造承诺

函数getMessage() { 返回新的Promise(函数(解析,拒绝){ setTimeout(函数(){ 解决(“Hello world !”我是一个承诺’); }, 0); }); } getMessage(),然后(函数(消息){ console.log(消息); });