前端设计——JavaScript中关于setTimeout的那些事

媒介: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、this

this是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一次都没更新。

    原文作者:Alarak
    原文地址: https://segmentfault.com/a/1190000009776999
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞