摘要:需要注意的是,及更早的浏览器不支持第一种语法中向延迟函数传递额外参数的功能。如果在不改变递归模式的前提下修善这段代码解决方案加入定时器题目四考察和系列解释立即的对象,是在本轮事件循环的结束时,而不是在下一轮事件循环的开始时。
前言:setTimeout是JavaScript中常见的一个window对象方法,本文将介绍关于它的一些基础知识和易出错的地方。
1、基础知识作用:setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。
基本语法:
</>复制代码
let timeoutId = window.setTimeout(func[, delay, param1, params2, ...]);
let timeoutId = scope.setTimeout(code[, delay]);
let timeoutId = window.setTimeout(function, milliseconds);
timeoutID 是该延时操作的数字ID, 此ID随后可以用来作为window.clearTimeout方法的参数。
func是你想要在delay毫秒之后执行的函数。
code 在第二种语法,是指你想要在delay毫秒之后执行的代码字符串,(使用该语法是不推荐的,
不推荐的原因和eval()一样,即:1、安全性差(可被植入恶意代码)2、执行效率低(需要将字符串解析为代码再执行))
delay 是延迟的毫秒数(1秒=1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0。
需要注意的是,IE9 及更早的 IE 浏览器不支持第一种语法中向延迟函数传递额外参数的功能。
</>复制代码
//以下是一个简单实例,3秒后弹窗提示
function myFunction(){
setTimeout(function(){alert("Hello")},3000);
}
2、单线程与事件队列机制
先来看一些程序
</>复制代码
//请判断以下代码输出结果
setTimeout(function(){
alert("Hello World");
},1000);
while(true){};
//该函数会陷入死循环,1秒后并不会弹出提醒
</>复制代码
//请写出以下代码输出结果
setTimeout(function (){
console.log("a")
},0)
console.log("b")
//输出结果为b,a
有同学可能会认为:第一段代码在1秒后会弹窗提示Hello World。而第二段代码,把延迟毫秒数设为0,就会立即执行,先输出a,再输出b。显然,实际的结果不是这样。
为什么呢?因为:
JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。
我们先来介绍下浏览器渲染时的线程机制:
浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。
JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。
GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。
回归开始的问题:
第一段代码始终会执行同步代码,陷入死循环,根本不会执行setTimeout内的函数,也就不会弹窗。
第二段代码,经过查找资料,可以得知,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说——即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动,它仍然会放在事件队列的最后。当同步代码执行完,输出b,然后再执行延迟函数,输出a。
3、thisthis是JavaScript中一个重要的考察点,可以简单记为:this是指向函数执行时的当前对象,倘若没有明确的当前对象,它就是指向window的。
请看如下代码
</>复制代码
//求函数执行结果
var a=1;
var obj={
a:2,
b:function(){
setTimeout(function(){
console.log(this.a);
},2000);
}
};
obj.b();
//函数输出为1
由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字会指向 window (或全局)对象,这和所期望的this的值是不一样的。也就是当执行时,this.a并不能读取obj对象中的a,而是会找到全局对象的a,故输出结果为1。
为了改变这种情况,有以下两种方法
</>复制代码
//方法一
var a=1;
var obj={
var me = this;
a:2,
b:function(){
setTimeout(function(){
console.log(me.a);
},2000);
}
};
obj.b();
//方法二
var a=1;
var obj={
a:2,
b:function(){
setTimeout(function(){
console.log(this.a);
}.bind(this),2000);
}
};
obj.b();
4、混合知识点考察
题目一:求输出结果
</>复制代码
(function() {
console.log(1);
setTimeout(function(){console.log(2)}, 1000);
setTimeout(function(){console.log(3)}, 0);
console.log(4);
})();
//输出结果是1,4,3,2
题目二:对setTimeout和IIFE的考察</>复制代码
解释:参照第二部分的事件机制
将 JavaScript 代码包含在一个函数块中有什么用途?为什么要这么做?
换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。
IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。
</>复制代码
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//输出为5,5,5,5,5
使用IIFE
</>复制代码
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
}
//输出为0,1,2,3,4
题目三:setTimeout的分片应用
如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?
</>复制代码
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
nextListItem();
}
};
解决方案:加入定时器
</>复制代码
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout(nextListItem(), 0);
}
};
题目四:考察setTimeout和Promise系列
</>复制代码
setTimeout(function () {
console.log("three");
}, 0);
Promise.resolve().then(function () {
console.log("two");
});
console.log("one");
// one
// two
// three
</>复制代码
解释:立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log("one")则是立即执行,因此最先输出。
题目五:setTimeout与箭头函数的this指向
</>复制代码
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log("s1: ", timer.s1), 3100);
setTimeout(() => console.log("s2: ", timer.s2), 3100);
</>复制代码
上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/83442.html
摘要:我对知乎前端相关问题的十问十答张鑫旭张鑫旭大神对知乎上经典的个前端问题的回答。作者对如何避免常见的错误,难以发现的问题,以及性能问题和不好的实践给出了相应的建议。但并不是本身有问题,被标准定义的是极好的。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老...
摘要:异步那些事一基础知识异步那些事二分布式事件异步那些事三异步那些事四异步那些事五异步脚本加载事件概念异步回调首先了讲讲中两个方法和定义和用法方法用于在指定的毫秒数后调用函数或计算表达式。功能在事件循环的下一次循环中调用回调函数。 JS异步那些事 一 (基础知识)JS异步那些事 二 (分布式事件)JS异步那些事 三 (Promise)JS异步那些事 四(HTML 5 Web Workers...
摘要:声明的变量不得改变值,这意味着,一旦声明变量,就必须立即初始化,不能留到以后赋值。 虽然今年没有换工作的打算 但为了跟上时代的脚步 还是忍不住整理了一份最新前端知识点 知识点汇总 1.HTML HTML5新特性,语义化浏览器的标准模式和怪异模式xhtml和html的区别使用data-的好处meta标签canvasHTML废弃的标签IE6 bug,和一些定位写法css js放置位置和原因...
摘要:声明的变量不得改变值,这意味着,一旦声明变量,就必须立即初始化,不能留到以后赋值。 虽然今年没有换工作的打算 但为了跟上时代的脚步 还是忍不住整理了一份最新前端知识点 知识点汇总 1.HTML HTML5新特性,语义化浏览器的标准模式和怪异模式xhtml和html的区别使用data-的好处meta标签canvasHTML废弃的标签IE6 bug,和一些定位写法css js放置位置和原因...
阅读 3435·2021-10-14 09:42
阅读 3644·2019-08-26 13:56
阅读 3730·2019-08-26 11:59
阅读 1014·2019-08-23 18:00
阅读 2310·2019-08-23 17:51
阅读 3634·2019-08-23 17:17
阅读 1556·2019-08-23 15:11
阅读 5511·2019-08-23 15:05