返回介绍

第一部分 类型和语法

第二部分 异步和性能

1.1 分块的程序

发布于 2023-05-24 16:38:21 字数 3039 浏览 0 评论 0 收藏 0

可以把 JavaScript 程序写在单个 .js 文件中,但是这个程序几乎一定是由多个块构成的。这些块中只有一个是现在 执行,其余的则会在将来 执行。最常见的 单位是函数。

大多数 JavaScript 新手程序员都会遇到的问题是:程序中将来 执行的部分并不一定在现在 运行的部分执行完之后就立即执行。换句话说,现在 无法完成的任务将会异步完成,因此并不会出现人们本能地认为会出现的或希望出现的阻塞行为。

考虑:

// ajax(..)是某个库中提供的某个Ajax函数
var data = ajax( "http://some.url.1" );

console.log( data );
// 啊哦!data通常不会包含Ajax结果

你可能已经了解,标准 Ajax 请求不是同步完成的,这意味着 ajax(..) 函数还没有返回任何值可以赋给变量 data 。如果 ajax(..) 能够阻塞到响应返回,那么 data = .. 赋值就会正确工作。

但我们并不是这么使用 Ajax 的。现在 我们发出一个异步 Ajax 请求,然后在将来 才能得到返回的结果。

现在将来 的“等待”,最简单的方法(但绝对不是唯一的,甚至也不是最好的!)是使用一个通常称为回调函数 的函数:

// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", function myCallbackFunction(data){

  console.log( data ); // 耶!这里得到了一些数据!

} );

可能你已经听说过,可以发送同步 Ajax 请求。尽管技术上说是这样,但是,在任何情况下都不应该使用这种方式,因为它会锁定浏览器 UI(按钮、菜单、滚动条等),并阻塞所有的用户交互。这是一个可怕的想法,一定要避免。

你有不同的意见?我知道,但为了避免回调函数引起的混乱并不足以成为使用阻塞式同步 Ajax 的理由。

举例来说,考虑一下下面这段代码:

function now() {
  return 21;
}

function later() {
  answer = answer * 2;
  console.log( "Meaning of life:", answer );
}

var answer = now();

setTimeout( later, 1000 ); // Meaning of life: 42

这个程序有两个块:现在 执行的部分,以及将来 执行的部分。这两块的内容很明显,但这里我们还是要明确指出来。

现在:

function now() {
  return 21;
}

function later() { .. }

var answer = now();

setTimeout( later, 1000 );

将来:

answer = answer * 2;
console.log( "Meaning of life:", answer );

现在 这一块在程序运行之后就会立即执行。但是,setTimeout(..) 还设置了一个事件(定时)在将来 执行,所以函数 later() 的内容会在之后的某个时间(从现在起 1000 毫秒之后)执行。

任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点击、Ajax 响应等)时执行,你就是在代码中创建了一个将来 执行的块,也由此在这个程序中引入了异步机制。

异步控制台

并没有什么规范或一组需求指定 console.* 方法族如何工作——它们并不是 JavaScript 正式的一部分,而是由宿主环境 (请参考本书的“类型和语法”部分)添加到 JavaScript 中的。

因此,不同的浏览器和 JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。

尤其要提出的是,在某些条件下,某些浏览器的 console.log(..) 并不会把传入的内容立即输出。出现这种情况的主要原因是,在许多程序(不只是 JavaScript)中,I/O 是非常低速的阻塞部分。所以,(从页面 /UI 的角度来说)浏览器在后台异步处理控制台 I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。

下面这种情景不是很常见,但也可能发生,从中(不是从代码本身而是从外部)可以观察到这种情况:

var a = {
  index: 1
};

// 然后
console.log( a ); // ??

// 再然后
a.index++;

我们通常认为恰好在执行到 console.log(..) 语句的时候会看到 a 对象的快照,打印出类似于 { index: 1 } 这样的内容,然后在下一条语句 a.index++ 执行时将其修改,这句的执行会严格在 a 的输出之后。

多数情况下,前述代码在开发者工具的控制台中输出的对象表示与期望是一致的。但是,这段代码运行的时候,浏览器可能会认为需要把控制台 I/O 延迟到后台,在这种情况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示 { index: 2 } 。

到底什么时候控制台 I/O 会延迟,甚至是否能够被观察到,这都是游移不定的。如果在调试的过程中遇到对象在 console.log(..) 语句之后被修改,可你却看到了意料之外的结果,要意识到这可能是这种 I/O 的异步化造成的。

如果遇到这种少见的情况,最好的选择是在 JavaScript 调试器中使用断点,而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过 JSON.stringify(..) 。

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

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

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文