Javascript 函数
Zhongjun Qiu 元婴开发者

JavaScript 函数机制详解,涵盖定时调度方法、嵌套定时器的自适应调度技巧、函数装饰器模式与缓存优化实现、this 丢失问题的成因与 bind 绑定解决方案等实用编程模式与技巧。

函数调度

setTimeout

setTimeout(func,[delay], [arg1], [arg2], …)

func必须是函数,不能写成调用的形式。

1
2
// 错的!
setTimeout(sayHi(), 1000);

setInterval

setInterval(func,[delay], [arg1], [arg2], …)

1
2
3
4
5
// 每 2 秒重复一次
let timerId = setInterval(() => alert('tick'), 2000);

// 5 秒之后停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);

嵌套的 setTimeout

周期性调度有两种方式。

一种是使用 setInterval,另外一种就是嵌套的 setTimeout,就像这样:

1
2
3
4
5
6
7
8
/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/

let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);

例如,我们要实现一个服务(server),每间隔 5 秒向服务器发送一个数据请求,但如果服务器过载了,那么就要降低请求频率,比如将间隔增加到 10、20、40 秒等。

1
2
3
4
5
6
7
8
9
10
11
12
13
let delay = 5000;

let timerId = setTimeout(function request() {
...发送请求...

if (request failed due to server overload) {
// 下一次执行的间隔是当前的 2 倍
delay *= 2;
}

timerId = setTimeout(request, delay);

}, delay);

使用 setInterval 时,func 函数的实际调用间隔要比代码中设定的时间间隔要短!

这也是正常的,因为 func 的执行所花费的时间“消耗”了一部分间隔时间。

装饰器模式和转发

透明缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function slow(x) {
// 这里可能会有重负载的 CPU 密集型工作
alert(`Called with ${x}`);
return x;
}

function cachingDecorator(func) {
let cache = new Map();

return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x);
cache.set(x, result);
return result;
};
}

slow = cachingDecorator(slow);
alert( slow(1) );

函数绑定

丢失this

1
2
3
4
5
6
7
8
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

浏览器中的 setTimeout 方法有些特殊:它为函数调用设定了 this=window。所以对于 this.firstName,它其实试图获取的是 window.firstName,这个变量并不存在。

  • 解决方案 1:包装器
1
2
3
4
5
6
7
8
9
10
11
12
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};

setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
// or
setTimeout(() => user.sayHi(), 1000);
  • 解决方案 2:bind
1
2
3
4
5
6
7
8
9
10
11
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};

let say2 = user.say.bind(user);
user = null;
say2("Hello"); // Hello, John!
say2("Bye"); // Bye, John!

say中会存储user对象对应的引用。

即改变user.say函数后,say2方法内容也会改变

 REWARD AUTHOR
 Comments
Comment plugin failed to load
Loading comment plugin