1.1 分块的程序
可以把 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论