閉包應用之延遲函數setTimeout

根據HTML 5標準,setTimeout推遲執行的時間,最少是5毫秒。如果小于這個值,會被自動增加到5ms。

每一個setTimeout在執行時,會返回一個唯一ID,把該ID保存在一個變量中,并傳入clearTimeout,可以清除定時器。

在setTimeout內部,this綁定采用默認綁定規則,也就是說,在非嚴格模式下,this會指向window;而在嚴格模式下,this指向undefined。

setTimeout不止有2個參數,第一個參數是回調函數,第二個參數是時間,第三個參數以后都是第一個回調函數的參數

一、用setTimeout代替setInterval

由于setInterval間歇調用定時器會因為在定時器代碼未執行完畢時又向任務隊列中添加定時器代碼,導致某些間隔被跳過等問題,所以應使用setTimeout代替setInterval。

setTimeout(function myTimer() {/*** 需要執行的代碼* setTimeout會等到定時器代碼執行完畢后才會重新調用自身(遞歸),記得給匿名函數添加一個函數名,以便調用自身。*/setTimeout(myTimer, 1000);
}, 1000);

這樣做的好處是,在前一個定時器執行完畢之前,不會向任務隊列中插入新的定時器代碼,可以避免任何缺失的間隔,還可以保證在下一次定時器代碼執行前,至少要等待指定的間隔,避免了連續執行。這個模式主要用于重復定時器。

// 代碼段1,間歇性輸出1到10
let num = 0;
let max = 10;
setTimeout(function myTimer() {num++;console.log(num);if (num === max) {return;}setTimeout(myTimer, 500);
}, 500);
// 代碼段2,間歇性輸出1到10
setTimeout(function myTimer() {num++;console.log(num);if (num < max) {setTimeout(myTimer, 500);}
}, 500);

二、在for循環中創建setTimeout定時器

1、根據事件循環和任務隊列的原理,定時器通常在循環結束后才會加入到任務隊列執行。

2、定時器是循環創建的。

3、定時器幾乎是同時開始計時的。

4、定時器中的回調函數屬于閉包,包含著對循環后全局變量i的引用。在塊作用域和定時器外創建一個函數作用域時,此時不會查找全局作用域。

5、定時器的第二個參數不屬于閉包的一部分,其值與循環i的值相同。

程序運行遵循同步優先異步靠邊回調墊底

// 代碼段1,輸出6個5
for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, 1000 * i);
}
console.log(i);

第1個5直接輸出,1 秒之后,輸出 5 個 5,并且每隔1s輸出一個,一共用時4s。

for循環和循環體外部的console是同步的,所以先執行for循環,再執行外部的console.log。等for循環執行完,就會給setTimeout傳參,最后執行。

JavaScript單線程如何處理回調呢?JavaScript同步的代碼是在堆棧中順序執行的,而setTimeout回調會先放到消息隊列,for循環每執行一次,就會放一個setTimeout到消息隊列排隊等候,當同步的代碼執行完了,再去調用消息隊列的回調方法。這個消息隊列執行的時間,需要等待到函數調用棧清空之后才開始執行。即所有可執行代碼執行完畢之后,才會開始執行由setTimeout定義的操作。而這些操作進入隊列的順序,則由設定的延遲時間來決定,消息隊列遵循先進先出(FIFO)原則。因此,即使我們將延遲時間設置為0,它定義的操作仍然需要等待所有代碼執行完畢后才開始執行。這里的延遲時間,并非相對于setTimeout執行這一刻,而是相對于其他代碼執行完畢這一刻。

先執行for循環,按順序放了5個setTimeout回調到消息隊列,然后for循環結束,下面還有一個同步的console,執行完console之后,堆棧中已經沒有同步的代碼了,就去消息隊列找,發現找到了5個setTimeout,注意setTimeout是有順序的。

JavaScript在把setTimeout放到消息隊列的過程中,循環的i是不會及時保存進去的,相當于你寫了一個異步的方法,但是ajax的結果還沒返回,只能等到返回之后才能傳參到異步函數中。

for循環結束之后,因為i是用var定義的,所以var是全局變量(這里沒有函數,如果有就是函數內部的變量),這個時候的i是5,從外部的console輸出結果就可以知道。那么當執行setTimeout的時候,由于全局變量的i已經是5了,所以傳入setTimeout中的每個參數都是5。很多人都會以為setTimeout里面的i是for循環過程中的i,這種理解是不對的。

for (var i = 0; i < 5; i++) {console.log(i);setTimeout(function myTimer() {console.log(i);}, i * 1000);
}

立刻輸出0 1 2 3 4

間歇性輸出5個5

溫馨提示:如果在開發者工具console面板運行這段程序,你會看到不一樣的結果。
立刻輸出0 1 2 3 4
立即輸出定時器ID
間歇性輸出5個5
for (var i = 0; i < 5; i++) {setTimeout((function() {console.log(i);})(), 1000 * i);
}

立即輸出0 1 2 3 4。因為setTimeout的第一個參數是函數或者字符串,而此時函數又立即執行了。因此,定時器失效,直接輸出0 1 2 3 4。

for (var i = 0; i < 5; i++) {(function() {console.log(i); })();
}

該程序也是立即輸出0 1 2 3 4。

三、如何讓程序間歇性輸出0 1 2 3 4呢?

這里有兩種思路,不過原理都相同。

思路1:ES6 let關鍵字,給setTimeout定時器外層創建一個塊作用域。

for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);}, 1000 * i);
}

思路1的另一種表達

for (var i = 0; i < 5; i++) {let j = i;  //閉包的塊作用域setTimeout(function() {console.log(j);}, 1000 * j);
}

思路2:IIFE,創建函數作用域以形成閉包。

Immediately Invoked Function Expression:聲明即執行的函數表達式。

for (var i = 0; i < 5; i++) {(function iife(j) {     //閉包的函數作用域setTimeout(function() {console.log(j);}, 1000 * i);   //這里將i換為j, 可以證明以上的想法。
  })(i);
}

給定時器外層創建了一個IIFE,并且傳入變量i。此時,setTimeout會形成一個閉包,記住并且可以訪問所在的詞法作用域。因此,會間歇輸出0 1 2 3 4。

實際上,函數參數,就相當于函數內部定義的局部變量,因此下面的寫法也是可以的,思路2的另一種表達。

for (var i = 0; i < 5; i++) {(function iife() {var j = i;setTimeout(function() {console.log(j);}, 1000 * i);   //如果這里將i換為j, 可以證明以上的想法。
  })();
}

思路3

for (var i = 0; i < 5; i++) {setTimeout(function(j) {return function(){console.log('index is ',j);} }(i), 1000 * i);   //如果這里將i換為j, 可以證明以上的想法。
}

思路4

var myTimer = function (i) {setTimeout(function() {console.log(i);}, 1000);
};
for (var i = 0; i < 5; i++) {myTimer(i);  //這里傳過去的i值被復制了
}
console.log(i);//5

代碼執行時,立即輸出5,之后每隔1秒依次立刻輸出0 1 2 3 4。

四、如何讓程序間歇性輸出0 1 2 3 4 5呢?

思路1

for (var i = 0; i < 5; i++) {(function(j) {setTimeout(function() {console.log( j);}, 1000 * j);  //這里修改0~4的定時器時間
  })(i);
}
setTimeout(function() { //這里增加定時器,超時設置為5秒
  console.log(i);
}, 1000 * i);

我們都知道使用Promise處理異步代碼比回調機制讓代碼可讀性更高,但是使用Promise的問題也很明顯,即如果沒有處理Promise的reject,會導致錯誤被丟進黑洞,好在新版的Chrome和Node 7.x 能對未處理的異常給出Unhandled Rejection Warning,而排查這些錯誤還需要一些特別的技巧(瀏覽器、Node.js)

思路2

const myArr = [];
for (var i = 0; i < 5; i++) {   // 這里i的聲明不能改成let,如果要改該怎么做?((j) => {myArr.push(new Promise((resolve) => {setTimeout(() => {console.log(new Date, j);resolve();  //這里一定要resolve,否則代碼不會按預期執行}, 1000 * j); //定時器的超時時間逐步增加
    }));})(i);
}Promise.all(myArr).then(() => {setTimeout(() => {console.log(new Date, i);}, 1000);   // 注意這里只需要把超時設置為1秒
});

思路3

const myArr = []; //這里存放異步操作的Promise
const myTimer = (i) => new Promise((resolve) => {setTimeout(() => {console.log(new Date, i);resolve();}, 1000 * i);
});
// 生成全部的異步操作
for (var i = 0; i < 5; i++) {myArr.push(myTimer(i));
}
// 異步操作完成之后,輸出最后的 i
Promise.all(myArr).then(() => {setTimeout(() => {console.log(new Date, i);}, 1000);
});

思路4:使用ES7中的async await特性

// 模擬其他語言中的sleep,實際上可以是任何異步操作。
const sleep = (timeountMS) => new Promise((resolve) => {setTimeout(resolve, timeountMS);
});
(async () => {  //聲明即執行的async函數表達式for (var i = 0; i < 5; i++) {await sleep(1000);console.log(new Date, i);}await sleep(1000);console.log(new Date, i);
})();

五、清除定時器

function fn1(){for(var i = 0;i < 5; i++){var tc = setTimeout(function(i){console.log(i);clearTimeout(tc);},10,i);}
}
fn1();//0 1 2 3

解讀fn1,這個tc是定義在閉包外面的,也就是說tc并沒有被閉包保存,所以這里的tc指的是最后一個循環留下來的tc,所以最后一個4被清除了,沒有輸出。

function fn2(){for(var i = 0;i < 5; i++){var tc = setInterval(function(i,tc){console.log(i);clearInterval(tc);},10,i,tc);}
}        
fn2();//0 1 2 3 4 4 4 4

解讀fn2,可以發現最后一個定時器沒被刪除。在瀏覽器中單步調試,在第一次循環的時候tc并沒有被賦值,所以是undefined,在第二次循環的時候,定時器其實清理的是上一個循環的定時器。所以導致每次循環都是清理上一次的定時器,而最后一次循環的定時器沒被清理,導致一直輸出4。

六、閱讀下列程序,說出運行結果順序。

let a = new Promise(function(resolve, reject) {console.log(1);setTimeout(() => console.log(2), 0);console.log(3);console.log(4);resolve(true);}
);
a.then(v => {console.log(8);
});
let b = new Promise(function() {console.log(5);setTimeout(() => console.log(6), 0);}
)
console.log(7);

輸出結果:1?3?4?5?7?8?2?6。

程序結果分析如下:

1、a變量是一個Promise,Promise本身是同步的,Promise的then()和catch()方法是異步的,所以這里先執行a變量內部的Promise同步代碼,輸出1?3?4。(同步優先)至于setTimeout回調,先去消息隊列排隊等著吧。(回調墊底)執行resolve(true),進入then(),then是異步,下面還有同步沒執行呢,所以then也去消息隊列排隊等候吧。(異步靠邊)

2、b變量也是一個Promise,和a一樣,執行內部的同步代碼,輸出5,setTimeout滾去消息隊列排隊等候。

3、最下面同步輸出7。

4、同步的代碼執行完了,JavaScript就跑去消息隊列呼叫異步的代碼。這里只有一個異步then,所以輸出8。

5、異步執行結束,終于輪到回調啦。這里有2個回調在排隊,他們的時間都設置為0,所以不受時間影響,只跟排隊先后順序有關。這時,先輸出a里面的回調2,最后輸出b里面的回調6。

?

我們還可以稍微做一點修改,把a里面Promise的 setTimeout(() => console.log(2), 0)改成 setTimeout(() => console.log(2), 2),對,時間改成了2ms,為什么不改成1試試呢?1ms的話,瀏覽器都還沒有反應過來呢。你改成大于或等于2的數字就能看到2個setTimeout的輸出順序發生了變化。所以回調函數正常情況下是在消息隊列順序執行的,但是使用setTimeout的時候,還需要注意時間的大小也會改變它的順序。

轉載于:https://www.cnblogs.com/camille666/p/js_setTimeout.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259507.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259507.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259507.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

并行編程2——多核體系架構

1.1 多核處理器定義 多內核處理器架構是指&#xff1a;芯片設計工程師在單個處理器中集成兩個或多個 “執行內核&#xff08;即計算引擎&#xff09;”。多內核處理器可直接插入到單一處理器基座中。但是&#xff0c;操作系統會把它的每個執行內核作為獨立的邏輯處理器&#x…

21:蘋果和蟲子2

團隊QQ&#xff1a;466373640個人博客&#xff1a;www.doubleq.winc/noi/信息學奧數博客&#xff1a;http://www.cnblogs.com/zwfymqz 1:蘋果和蟲子2 查看提交統計提問總時間限制:1000ms內存限制:65536kB描述你買了一箱n個蘋果&#xff0c;很不幸的是買完時箱子里混進了一條蟲子…

php運行代碼運行退出為0,php – Selenium測試用例返回進程以退出代碼0結束

你使用“phpunit yourTestCase.php”而不是“php yourTestCase.php”嗎&#xff1f;我使用phpunit(3.5.14)和“selenium-server-standalone-2.0rc2.jar”運行你的testfile,沒有問題(除了測試本身失敗)&#xff1a;PHPUnit 3.5.14 by Sebastian Bergmann.ETime: 10 seconds, Mem…

Xcode6中使用initWithTitle:title image:image selectedImage:自定義圖片

使用xcode6來運行項目&#xff0c;發現使用原生的tabbar上的圖片不顯示了。這個問題是因為xcode6中的一些api方法被廢棄了,同時tabbar上圖片的渲染方式發生了改變。先看xcode6中的tabbar api方法的變更&#xff1a;- (void)setFinishedSelectedImage:(UIImage *)selectedImage …

[Node.js]get/post請求

摘要 在很多情況下&#xff0c;我們的web服務器都需要接受客戶端瀏覽器傳遞的參數或者數據。最常見的是get和post請求。 獲取get請求的內容 get請求傳遞的參數在url中&#xff0c;參數部分在?后面。因此可以手動解析后面的內容作為get請求的參數。node.js中url模塊中的parse函…

MyEclipse10 Tomcat7 JDK1.7 配置

第一步.MyEclipse10 Tomcat7 JDK1.7下載 MyEclipse10http://downloads.myeclipseide.com/downloads/products/eworkbench/indigo/installers/myeclipse-10.0-offline-installer-windows.exe Tomcat http://tomcat.apache.org/ Java SE Development Kit 7 WINDOWS版 http://www…

類的靜態成量變量必須初始化

因為類的靜態成員變量是所有實例共用的.所以得在類外初始化. 調用的時候可以通過對象調用,也可以通過類直接調用 classA { public: inti; //有默認值}; classB { public: staticintn;staticA Aobj;}; intB::n 1; //靜態成員變量的初始化A B::Aobj; //靜態…

2階節IIR算法C語言源碼

純C語言軟件算法&#xff0c;沒有做過多優化&#xff0c;只是實現了基本IIR算法 /****************************************************************************** * 二階IIR濾波器單元,采用直接II型 * 由多個2階節&#xff0c;可以組成更多高階的濾波器 * 根據參數的不同&a…

HDU 3709 Balanced Number (數位DP)

題意 求出[x, y] 范圍內的平衡數&#xff0c;平衡數定義為&#xff1a;以數中某個位為軸心&#xff0c;兩邊的數的偏移量為矩&#xff0c;數位權重&#xff0c;使得整個數平衡。 思路 外層枚舉平衡點&#xff0c;然后數位DP即可。設計狀態&#xff1a; dp[pos][o][left_right] …

跳過 Windows RT的UI

RT啟動進入常規桌面 微軟Surface RT發布的時間已經不短了&#xff0c;相信很多朋友都已經熟悉了這個全新的平板&#xff0c;并且已經上手。Surface RT開機默認進入的界面為Windows UI&#xff0c;這對于經常使用App的朋友來說并沒有什么&#xff0c;但是對于那些經常使用Office…

matlab調用mstg,實驗五 雙線性變換法設計IIR數字濾波器

實驗五 IIR 數字濾波器設計一、實驗目的(1)熟悉用雙線性變換法設計IIR 數字濾波器的原理與方法&#xff1b;(2)學會調用MATLAB 信號處理工具箱中濾波器設計函數設計各種IIR 數字濾波器&#xff0c;學會根據濾波需求確定濾波器指標參數。(3)掌握IIR 數字濾波器的MATLAB 實現方法…

Android知識點剖析系列:深入了解layout_weight屬性

前言 Android中layout_weight這個屬性對于經常搗鼓UI的我們來說&#xff0c;肯定不會陌生。但是我們在真正使用這個屬性時&#xff0c;經常會出現一些莫名奇妙的布局效果&#xff1b;如果僅僅知其然而不知其所以然&#xff0c;一些意外的布局效果一定讓我們頗為頭疼。在本文中&…

C++ 中explicit的使用

C提供了關鍵字explicit&#xff0c;可以阻止不應該允許的經過轉換構造函數進行的隱式轉換的發生。聲明為explicit的構造函數不能在隱式轉換中使用。 C中&#xff0c; 一個參數的構造函數(或者除了第一個參數外其余參數都有默認值的多參構造函數)&#xff0c; 承擔了兩個角色。1…

BZOJ 1026 windy數 (數位DP)

題意 區間[A,B]上&#xff0c;總共有多少個不含前導零且相鄰兩個數字之差至少為2的正整數&#xff1f; 思路 狀態設計非常簡單&#xff0c;只需要pos、limit和一個前驅數pre就可以了&#xff0c;每次枚舉當前位時判斷是否與上一位相差2即可。一個需要注意的地方是第一位不用比較…

oracle診斷,Oracle?診斷事件列表

Oracle 診斷事件列表(2013-03-26 18:05:26)標簽&#xff1a;oracle診斷事件itORA-10000: controlfile debug event, name control_fileORA-10001: controlfile crash event1ORA-10002: controlfile crash event2ORA-10003: controlfile crash event3ORA-10004: controlfile cra…

考研數學:【以錯補錯】 降低做題出錯率

考研數學&#xff1a;以錯補錯 降低做題出錯率  眾所周知&#xff0c;數學需要做題&#xff0c;需要通過做題來鞏固掌握&#xff0c;但很多同學卻陷入了題海戰術&#xff0c;把所有的精力都放在數學練習上&#xff0c;一門心思做題&#xff0c;可幾個月下來卻沒有進展&#x…

treeview右鍵添加新節點

private void advTree1_MouseDown(object sender, MouseEventArgs e){if (e.Button MouseButtons.Right)//判斷你點的是不是右鍵{Point ClickPoint new Point(e.X, e.Y);Node CurrentNode advTree1.GetNodeAt(ClickPoint);if (CurrentNode ! null)//判斷你點的是不是一個節點…

RPM方式安裝MySQL5.6

RPM方式安裝MySQL5.6 rpm -ivh MySQL-server-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-client-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-devel-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-embedded-5.6.25-1.linux_glibc2.5.x86_64.rpm rpm -iv…

centos7靜默搭建oracle11g,Linux靜默安裝Oracle方法(centos7+oracle11g)

1、 增加虛擬內存ddif/dev/zero of/swapadd bs1024 count2006424mkswap /swapaddswapon /swapadd2、 檢查依賴包rpm -q binutils compat-libstdc-33 elfutils-libelf elfutils-libelf-devel gcc gcc-c glibc-2.5 glibc-common glibc-devel glibc-headers ksh libaio libaio-dev…

Ms SQL Server 約束和規則

一、SQL約束 約束定義關于列中允許值的規則&#xff0c;是強制完整性的標準機制。 使用約束優先于使用觸發器、規則和默認值。查詢優化器也使用約束定義生成高性能的查詢執行計劃。 1&#xff1a;類型 約束的類型一共分三種 域約束&#xff1a; 涉及一個或多個列&#xf…