1. setTimeout についてのシングルスレッド
長い間、ブラウザーで JavaScript プログラムを実行するスレッドが 1 つだけであっても、JavaScript はシングルスレッドであると誰もが言ってきました。
しかし、疑問があるかどうかはわかりません。プログラミング プロセスで使用する setTimeout (setInterval、Ajax に似ています) は非同期で実行されるのではありませんか? ! !
例:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> console.log("a"); //利用setTimeout延迟执行匿名函数 setTimeout(function(){ console.log("b"); },100); console.log("c"); </script> </body> </html>
コードを実行してChromeデバッガーを開くと、次の結果が得られます
setTimeoutの内容が後で実行されるため、この結果は理解しやすいですもちろん 100ms です。最初に a を出力し、次に c を出力し、100ms 後に setTimeout で b を出力します。
ねえ、JavaScript はシングルスレッドじゃないの? マルチスレッドに対応できるの? ! !
実際には、違います。 setTimeout は JavaScript のシングルスレッド メカニズムを壊すものではなく、実際には依然としてシングルスレッドです。
なぜこんなことを言うのですか? それでは、setTimeout が何であるかを理解する必要があります。
以下のコードを見て、結果を推測してください:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> var date = new Date(); //打印才进入时的时间 console.log('first time: ' + date.getTime()); //一秒后打印setTimeout里匿名函数的时间 setTimeout(function(){ var date1 = new Date(); console.log('second time: ' + date1.getTime() ); console.log( date1.getTime() - date.getTime() ); },1000); //重复操作 for(var i=0; i < 10000 ; i++){ console.log(1); } </script> </body> </html>
上記のコードを読んだ後、出力結果が何になるか推測してください。 1000ミリ秒?
Chrome デバッガーを開きます。下の図を参照してください
ナニ、なぜ 1000 ミリ秒ではないのですか? ! ! !
次のコードをもう一度見てみましょう:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> //一秒后执行setTimeout里的匿名函数,alert下 setTimeout(function(){ alert("monkey"); },1000); while(true){}; </script> </body> </html>
コードを実行した後!
ブラウザがスタックし、警告も表示されず、更新し続けるのはなぜですか。 !
論理的には、while を無限ループしたとしても、1 秒後にはアラートが発生する必要があります。
あらゆる種類の問題には 1 つの理由があります。それは、JavaScript がシングルスレッドであるということです。
JavaScript はシングルスレッドであり、setTimeout はマルチスレッドを実装していないことに注意してください:
JavaScript エンジンは単一スレッドで実行され、ブラウザーは常に 1 つのスレッドのみを実行します。 .JavaScriptプログラム。
ブラウザのカーネルはマルチスレッドであり、カーネルの制御下で相互に連携して、ブラウザには少なくとも 3 つの常駐スレッド (JavaScript エンジン スレッド、GUI レンダリング スレッド、ブラウザ イベント トリガー スレッド) が実装されています。 。
*JavaScript エンジンはイベント駆動型のシングルスレッド実行に基づいており、常にタスク キュー内のタスクの到着を待機してから、ブラウザーで JavaScript プログラムを実行する JavaScript スレッドは 1 つだけです。時間。
*GUI レンダリング スレッドは、ブラウザ インターフェイスのレンダリングを担当します。インターフェイスの再描画 (Repaint) が必要な場合、または何らかの操作によってリフロー (Reflow) が発生した場合、このスレッドが実行されます。ただし、GUI レンダリング スレッドと JavaScript エンジンは相互に排他的であることに注意してください。JavaScript エンジンが実行されると、GUI スレッドは一時停止され、GUI の更新はキューに保存され、JavaScript が実行されるとすぐに実行されます。エンジンはアイドル状態です。
*イベントトリガースレッド。イベントがトリガーされると、スレッドは保留キューの最後にイベントを追加し、JavaScript エンジンによる処理を待ちます。これらのイベントは、setTimeout などの JavaScript エンジンによって現在実行されているコード ブロックから、またはマウス クリックや Ajax 非同期リクエストなどのブラウザ カーネル内の他のスレッドから発生する可能性があります。ただし、JavaScript のシングルスレッド関係により、すべてのイベントが発生します。これらのイベントは、JavaScript エンジンによる処理のためにキューに入れられる必要があります (スレッド内で同期コードが実行されていない場合にのみ、非同期コードが実行されます)。
したがって、上記の説明を通じて、上記の問題はすべて簡単に解決できます。
2. setTimeoutの遅延時間は0です
setTimeoutの遅延時間が0の場合、どのように実行されるかを考えてみてください。
たとえば、次のコード:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> console.log('a'); setTimeout(function(){ console.log('b'); },0); console.log('c'); console.log('d'); </script> </body> </html>
コードを実行した結果は次のようになります:
Javascript シングルスレッドの動作原理をすでに知っていると仮定します。次に、「setTimeout 時間が 0 で、処理キューの最後に追加されていないのに、どうして実行が遅くなるのですか?」という疑問が生じるかもしれません。すぐにやるべきではないでしょうか?
私の理解では、setTimeout時間が0であってもsetTimeoutであり、原則は変わりません。したがって、キューの最後に追加され、0 秒後に実行されます。
さらに、情報を検索したところ、setTimeoutには最小実行時間があり、指定された時間がその時間未満の場合、ブラウザはsetTimeoutの時間間隔として最小許容時間を使用することがわかりました。 setTimeout のミリ秒を変更した場合でも、0 に設定すると、呼び出されたプログラムはすぐには開始されません。
最小時間間隔はどれくらいですか?
这和浏览器及操作系统有关。在John Resig的《Javascript忍者的秘密》一书中提到–Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。
说了这么多,setTimeout的延迟时间为0,看来没什么意义嘛,都是放在队列后执行嘛。
非也,天生我材必有用,就看你怎么用咯。抛砖迎玉下。
1、可以用setTimeout的延迟时间为0,模拟动画效果哦。
详情请见下代码:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <div id="container" style="width:100px;height:100px;border:1px solid black;"></div> <div id="btn" style="width:40px;height:40px;line-height:40px;margin-top:20px;background:pink;">click</div> <script> window.onload = function(){ var con = document.getElementById('container'); var btn = document.getElementById('btn'); //Params: i 为起始高度,num为预期高度 function render(i, num) { i++; con.style.height = i + 'px'; //亮点在此 if(i < num){ setTimeout(function() { render(i, num); },0); } else { con = null; btn = null; } }; btn.onclick = function(){ render(100, 200); }; }; </script> </body> </html>
由于是动画,所以想看其效果,还请各位看官运行下代码哦。
代码第19行中,利用setTimeout,在每一次render执行完成(给高度递增1)后,由于Javascript是单线程,且setTimeout里的匿名函数会在render执行完成后,再执行render。所以可以实现动画效果。
2、可以用setTimeout的延迟时间为0,实现捕获事件哦。
当我们点击子元素时,我们可以利用setTimeout的特性,来模拟捕获事件。
请见如下代码:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <style> #parent { width:100px; height:100px; border:1px solid black; } #child { width:50px; height:50px; background:pink; } </style> </head> <body> <div id="parent"> <div id="child"></div> </div> <script> //点击子元素,实现子元素的事件在父元素触发后触发 window.onload = function(){ var parent = document.getElementById('parent'); var child = document.getElementById('child'); parent.onclick = function(){ console.log('parent'); } child.onclick = function(){ //利用setTimeout,冒泡结束后,最后输出child setTimeout(function(){ console.log('child'); },0); } parent = null; child = null; } </script> </body> </html>
执行代码,点击粉红色方块,输出结果:
三、setTimeout那些事儿之this
说到this,对于它的理解就是:this是指向函数执行时的当前对象,倘若没有明确的当前对象,它就是指向window的。
好了,那么我们来看看下面这段代码:
<!DOCTYPE html> <head> <title>setTimeout</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> var name = '!!'; var obj = { name:'monkey', print:function(){ console.log(this.name); }, test:function(){ //this.print setTimeout(this.print,1000); } } obj.test(); </script> </body> </html>
通过chrome调试器,查看输出结果:
咦,它怎么输出的是”!!”呢?不应该是obj里的”monkey”吗?!!
这是因为setTimeout中所执行函数中的this,永远指向window。
不对吧,那上面代码中的setTimeout(this.print,1000)里的this.print怎么指向的是obj呢?!!
注意哦,我这里说的是“延迟执行函数中的this”,而不是setTimeout调用环境下的this。
什么意思?
setTimeout(this.print,1000),这里的this.print中的this就是调用环境下的;
而this.print=function(){console.log(this.name);},这个匿名函数就是setTimeout延迟执行函数,其中的this.name也就是延迟执行函数中的this啦。
嘿嘿,这下明白了吧。
var age = 24; function Fn(){ this.age = 18; setTimeout(function(){ //this代表window console.log(this); //输出24,而不是Fn的18 console.log(this.age); },1000); } new Fn();
咦,那有个疑问,比如我想在setTimeout延迟执行函数中的this指向调用的函数呢,而不是window?!!我们该怎么办呢。
常用的方法就是利用that。
that?
对,that。利用闭包的知识,让that保证你传进去的this,是你想要的。
详情见下:
var age = 24; function Fn(){ //that在此 var that = this; this.age = 18; setTimeout(function(){ console.log(that); console.log(that.age); },1000); } new Fn();
还有一种方法就是,利用bind。
如下:
var age = 24; function Fn(){ this.age = 18; //bind传入this setTimeout(function(){ console.log(this); console.log(this.age); }.bind(this),1000); } new Fn();