那些年,踩过的 setTimeout 坑
1. 前言
众所周知,在JavaScript中,我们可以使用setTimeout()
函数设置定时器,时间到了之后,执行一个函数或者一段代码
2. 基本用法
setTimeout(func|code, delay)
第一个参数接受一个函数或者一段代码
第二个参数是延迟执行的时间
举例:
funtion say() {
console.log('早上好')
}
setTimeout(say, 1000) // 1s后执行say函数
setTimeout('console.log('早上好')', 1000) // 1s后执行这段代码(不推荐使用, 有安全风险)
很简单吧?看似很简单,其实不然,setTimeout()
这个函数埋了不少坑
3. 坑点一,this指向
看这一段代码
var name = 'lx'
var obj = {
name: 'zmj',
sayName: function() {
console.log(this.name)
}
}
setTimeout(obj.sayName, 1000); // 'lx'
this并没有指向obj,而是指向window对象
为什么呢?
因为setTimeout()
调用的代码运行在与所在函数完全分离的执行环境上。
这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象
说白来,obj.sayName
这个函数并没有交给obj
对象去执行,真正去调用它的是setTimeout()
对于this指向问题,主要是要观察是谁去调用它
3.1 解决方法
解决方法主要就是我们要把sayName
这个函数交给obj去执行
1. 使用包装函数
var name = 'lx'
var obj = {
name: 'zmj',
sayName: function() {
console.log(this.name)
}
}
setTimeout(function() {
obj.sayName()
}, 1000);
在setTimeout
中执行的是外面的包装函数,真正调用sayName()
的还是obj,所以这种方法解决了 this 的指向问题
2. 使用显式绑定
var name = 'lx'
var obj = {
name: 'zmj',
sayName: function() {
console.log(this.name)
}
}
setTimeout(obj.sayName.bind(obj), 1000);
^_^
,我们强行把 obj 绑给sayName
,this 不得不从,指向了 obj
4. 坑点二,异步
setTimeout(function() {
console.log('吴亦凡:没吃');
}, 10);
for (var i = 0; i < 100000; i++) {
console.log('李雪琴:中午好呀,吃饭了没?')
}
我们和 setTimeout
说好了,让它10ms
之后就执行
结果,setTimeout
非要等 for 循环执行结束后(时间已然过去好几秒,如果你电脑比较好,把循环次数设大一些),它才执行
如果你把for循环 改成 while(true)
,那么李雪琴小姐永远都听不到吴亦凡先生的回复了
原因当然是因为 setTimeout
是一个异步任务
在 js 中,一段代码从上到下,遇到异步任务的时候,并不影响下面的代码块,js 先把所有的同步任务执行完毕后,再来执行异步任务
于是乎,思考一道题,下面的代码会打印出什么?
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}
答案打印出了5个6
虽然这道题主要考察的是作用域,但也考察了异步相关的知识
具体原因和解决办法可以参考 for 循环中的setTimeout(function(){})异步问题