摘要:需要注意的是,及更早的瀏覽器不支持第一種語法中向延遲函數傳遞額外參數的功能。如果在不改變遞歸模式的前提下修善這段代碼解決方案加入定時器題目四考察和系列解釋立即的對象,是在本輪事件循環的結束時,而不是在下一輪事件循環的開始時。
前言: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
題目五:setTimeout與箭頭函數的this指向解釋:立即resolve的Promise對象,是在本輪“事件循環”(event loop)的結束時,而不是在下一輪“事件循環”的開始時。上面代碼中,setTimeout(fn, 0)在下一輪“事件循環”開始時執行,Promise.resolve()在本輪“事件循環”結束時執行,console.log("one")則是立即執行,因此最先輸出。
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一次都沒更新。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/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放置位置和原因...
閱讀 3175·2021-10-14 09:42
閱讀 3569·2019-08-26 13:56
閱讀 3464·2019-08-26 11:59
閱讀 943·2019-08-23 18:00
閱讀 2208·2019-08-23 17:51
閱讀 3530·2019-08-23 17:17
閱讀 1481·2019-08-23 15:11
閱讀 5176·2019-08-23 15:05