可以把 程序写在单个 .js 文件中,但是这个程序几乎一定是由多个块构成的。这些块中只有一个是现在执行,其余的则会在将来执行。最常见的块单位是函数。
大部分新手会遇到的一个问题:程序中将来执行的部分并不一定在现在运行的部分执行完之后就立即执行。换句话说,现在无法完成的任务将会异步完成,因此,并不会出现人们本能地认为会出现的或希望出现的阻塞行为。
// ajax(..)是某个库中提供的某个Ajax函数
var data = ajax( "http://some.url.1" );
console.log( data );
// data通常不会包含Ajax结果
标准 Ajax
请求不是同步完成的,这意味着 ajax(..)
函数还没有返回任何值可以赋给变量data
。如果ajax(..)
能够阻塞到响应返回,那么data = ..
赋值就会正确工作。
从现在到将来的“等待”,最简单的方法(但绝对不是唯一的,甚至也不是最好的!)是使用回调函数来解决:
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", function myCallbackFunction(data){
console.log( data ); // 这里得到了数据!
});
可以同步发送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 响应等)时执行,就是在代码中创建了一个将来执行的块,也由此在这个程序中引入了异步机制。
异步控制台
var a = {
index: 1
};
// 然后
console.log( a ); // ??
// 再然后
a.index++;
上述代码运行时,浏览器可能会认为需要把控制台 I/O 延迟到后台,在这种情况下, 等到浏览器控制台输出对象内容时,a.index++
可能已经执行,因此会显示 { index: 2 }
。
如果在调试的过程中遇到对象在 console.log(..)
语句之后被修改,可你却看到了意料之外的结果, 要意识到这可能是这种 I/O 的异步化造成的。避免这种情况可以选择在 js 调试器中使用断点,还有一个方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过 JSON.stringify(..)
。