你不知道的this—JS非同步編程中的this

来源:http://www.cnblogs.com/star91/archive/2016/07/10/5657269.html
-Advertisement-
Play Games

Javascript小學生都知道了javascript中的函數調用時會 隱性的接收兩個附加的參數:this和arguments。參數this在javascript編程中占據中非常重要的地位,它的值取決於調用的模式。總的來說Javascript中函數一共有4中調用模式:方法調用模式、普通函數調用模式、 ...


  Javascript小學生都知道了javascript中的函數調用時會 隱性的接收兩個附加的參數:this和arguments。參數this在javascript編程中占據中非常重要的地位,它的值取決於調用的模式。總的來說Javascript中函數一共有4中調用模式:方法調用模式、普通函數調用模式、構造器調用模式、apply/call調用模式。這些模式在如何初始化關鍵參數this上存在差異。“可能還有小伙伴不知道它們之間的區別,那我就勉為其難擼一擼吧!”

  • 方法調用模式:函數是在某個明確的上下文對象中調用的,this綁定的是那個上下文對象。

  • 普通函數調用模式:預設情況下,如果函數是被直接調用的,如果在嚴格模式下,就綁定到undefined,否則綁定到全局對象。

  • 構造器調用模式:函數通過new操作符調用,this綁定的是新創建的對象。

  • apply/call調用模式:函數通過apply或者call調用,this綁定的是指定的對象,如果把null或者undefined作為this的綁定對象傳入call/apply,在調用時會被忽略,實際應用的是預設綁定規則。

下麵舉一個簡單的綜合例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var a=2; function foo(b) {     this.b=b;     console.log(this.a); }   var obj={     a:4,     foo:foo };   foo();//普通函數調用,輸出2 obj.foo();//作為對象方法調用,輸出4 foo.call(obj);//call顯示綁定,輸出4 foo.call(null);//輸出2   var bar=new foo(8);//構造函數調用,輸出了undefined(由console.log(a)列印) console.log(bar.b)//輸出8

 

  上面的例子在瀏覽器環境中已經測試通過了,在Node環境中在函數外面定義的變數不會成為全局對象的屬性,理解這個例子的輸出結果對於上面提到的四種調用方式大概就理解了。在大多數情況下,每次遇到函數調用(註意是每次,不管調用時這個函數位於哪裡,只要遇到調用這個函數就要停下來確定裡面的this),只要仔細區分上面的四種調用模式,就能很快確定函數中的this綁定的是哪個對象。但是有一類情況很特殊,你不能一眼或者兩眼就能看出函數調用的模式,那就是JavaScript中的非同步函數調用。下麵介紹幾種實際開發過程中常用的非同步函數調用中this綁定的例子。

1.超時調用和間歇調用

超時調用需要使用 window 對象的 setTimeout() 方法,它接受兩個參數:要執行的代碼和以毫秒表示的時間(即在執行代碼前需要等待多少毫秒)。其中,第一個參數可以是一個包含JavaScript代碼的字元串(就和在eval() 函數中使用的字元串一樣),也可以是一個函數。setTimeout() 的第二個參數告訴 JavaScript 再過多長時間把當前任務添加到隊列中。如果隊列是空的,那麼添加的代碼會立即執行;如果隊列不是空的,那麼它就要等前面的代碼執行完了以後再執行。
下麵對setTimeout()的兩次調用都會在一秒鐘後顯示一個警告框。

 

1 2 3 setTimeout(function() { alert("Hello world!"); }, 1000);
 下麵看一下setTimeout的回調函數中存在this的情況

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 var a=5;   function foo() {   this.a++;     setTimeout(function(){         console.log(this.++a);     },1000); }   var obj={     a:2 }; foo.call(obj); console.log(obj.a);

  在瀏覽器環境測試,上述代碼的輸出結果是3 6,為什麼會是3 和6呢,首先我們知道超時函數的回調函數是非同步的,所以先輸出的是最後一條語句執行的結果。foo.call(obj)語句通過call綁定obj,所以foo函數執行時內部的this綁定的是obj,所以this.a++使得obj的a屬性增加了1.接下來通過超時函數設置回調的匿名函數一秒後加入到任務隊列。所以在執行最後一條語句時,超時函數里的回調函數還沒有執行,所以最後一條語句輸出為3,接下來當任務隊列里的回調函數被調用執行時,輸出的是6,也就是全局變數a加1,因此超時調用的回調代碼都是在全局作用域中執行的,函數中的this的值指向全局對象,這裡補充說明一下在嚴格模式下this綁定的是undefined。

那麼間歇調用setInterval方法是什麼情況呢。稍微小改一下上面的代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 var a=5;   function foo() {   this.a++;     setInterval(function(){         console.log(++this.a);     },1000); }   var obj={     a:2 }; foo.call(obj); console.log(obj.a);

上面的代碼輸出為3 6 7 8 9·····

也就是說間歇調用和超時調用的情況一樣,回調函數也是在全局環境中執行的。

2.事件處理程式

  • ​(1)HTML事件處理程式

 

 在事件處理函數內部, this 值等於事件的目標元素,例如:
1 2 <!-- 輸出  "Click Me" --> <input type="button" value="Click Me" onclick="alert(this.value)">
  • (2)DOM0 級事件處理程式

使用DOM0級方法指定的事件處理程式被認為是元素的方法。因此,這時候的事件處理程式是在元素的作用域中運行;換句話說,程式中的 this 引用當前元素。來看一個例子。

 

1 2 3 4 var btn = document.getElementById("myBtn"); btn.onclick = function(){ alert(this.id); //"myBtn" };
  • (3)DOM2 級事件處理程式

要在按鈕上為 click 事件添加事件處理程式,可以使用下列代碼:
1 2 3 4 5 varbtn = document.getElementById("myBtn"); btn.addEventListener("click", function(){ alert(this.id);//"myBtn" }, false);

DOM0級方法一樣,這裡添加的事件處理程式也是在其依附的元素的作用域中運行。
在舊版本的IE瀏覽器中有一種特殊情況,舊版本的IE可以通過attachEvent() 添加事件處理程式,IE中使用attachEvent() 與使用DOM0級方法的主要區別在於事件處理程式的作用域。在使DOM0級方法的情況下,事件處理程式會在其所屬元素的作用域內運行;在使用 attachEvent() 法的情況下,事件處理程式會在全局作用域中運行,因此 this 等於 window。來看下麵的例子。

1 2 3 4 var btn = document.getElementById("myBtn"); btn.attachEvent("onclick", function(){     alert(this === window); //true });
 為了加深理解,我們看下麵一個比較難懂的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 functionJSClass(){ this.m_Text ='division element'; this.m_Element = document.createElement('div'); this.m_Element.innerHTML =this.m_Text; this.m_Element.addEventListener('click',this.func); // this.m_Element.onclick = this.func; } JSClass.prototype.Render=function(){     document.body.appendChild(this.m_Element); } JSClass.prototype.func =function(){   alert(this.m_Text); }; var jc =newJSClass(); jc.Render();// add div jc.func();// 輸出 division element

click添加的div元素division element會輸出underfined,為什麼? 
答案:division element undefined

解析:第一次輸出很好理解,func()作為對象的方法調用,所以輸出division element,點擊添加的元素時,this其實已經指向this.m_Element,也就是事件的目標元素(事件對象的currentTarget屬性值-或者說是註冊事件處理程式的元素),因為是this.m_Element調用的addEventListener函數,所以內部的this全指向它了,而這個元素並沒有m_Text屬性,所以輸出undefined。

 關於事件處理程式理解上面這些還不夠,我們還要關註一下事件委托對“事件處理程式過多”問題的解決方案就是事件委托。事件委托利用了事件冒泡,只指定一個事件處理程式,就可以管理某一類型的所有事件。例如, click 事件會一直冒泡到 document 層次。也就是說,我們可以為整個頁面指定一個 onclick 事件處理程式,而不必給每個可單擊的元素分別添加事件處理程式。以下麵的 HTML 代碼為例。
1 2 3 4 5 <ul id="myLinks">  <li id="goSomewhere">Go somewhere</li>  <li id="doSomething">Do something</li>  <li id="sayHi">Say hi</li> </ul>
其中包含3個被單擊後會執行操作的列表項。按照傳統的做法,需要像下麵這樣為它們添加3個事件處理程式。
1 2 3 4 5 6 7 8 9 10 11 12 var item1 = document.getElementById("goSomewhere"); var item2 = document.getElementById("doSomething"); var item3 = document.getElementById("sayHi"); item1.addEventListener("click", function(event){     alert(this.id);//"goSomewhere" }); item2.addEventListener("click", function(event){     alert(this.id);//"oSomething" }); item3.addEventListener("click", function(event){     alert(this.id);//"sayHi" });
如果在一個複雜的 Web 應用程式中,對所有可單擊的元素都採用這種方式,那麼結果就會有數不清的代碼用於添加事件處理程式。此時,可以利用事件委托技術解決這個問題。使用事件委托,只需在DOM樹中儘量最高的層次上添加一個事件處理程式,如下麵的例子所示。
1 2 3 4 var list=document.getElementById('"myLinks'); list.addEventListener('click',function(event){     alert(this.id); })

那上面的例子能否實現事件委托前的功能呢,我們用下麵的代碼在瀏覽器中測試一下:

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html> <html> <head>     <meta charset="utf-8" />     <title></title>       </head> <body>   <ul id="myLinks">    <li id="goSomewhere">Go somewhere</li>    <li id="doSomething">Do something</li>    <li id="sayHi">Say hi</li>  </ul>   </body> <script type="text/javascript">     var list=document.getElementById('myLinks');    list.addEventListener('click',function(event){     alert(this.id);   }) </script> </html>

測試結果



也就是說不論點擊哪一個列表,彈出的是父元素的ID,那麼該怎麼改寫才能實現預期的功能呢?我們知道事件對象event有很多屬性,其中包括兩個屬性currentTarget和target,在事件處理程式內部,對象this 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ■ 基本操作 啟動: oncreate onstart onresume back: onPause onStop onDestroy home: onPause onStop home後的啟動(未銷毀): onRestart onStart onResume 已銷毀後的啟動:onCreate on ...
  • 在android的應用層中,涉及到很多應用框架,例如:Service框架,Activity管理機制,Broadcast機制,對話框框架,標題欄框架,狀態欄框架,通知機制,ActionBar框架等等。 經常會使用到通知機制中的通知欄框架(Notificaiton),它適用於交互事件的通知。它是位於頂層 ...
  • 結合自身的實踐開發經驗總結出了22個iOS開發的小技巧,以非常歡樂的語調輕鬆解決開發過程中所遇到的各種苦逼難題,光讀著便已忍俊不禁。 1. TableView不顯示沒內容的Cell怎麼辦? 類似於圖1,我不想讓下麵的那些空顯示。很簡單,添加“self.tableView.tableFooterVie ...
  • 0.簽名 java -Xmx2048m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.x509.pem build/target/product/security/test ...
  • 效果:(換膚出來一個div,選擇你想要的圖片,作為網頁背景,保存) 要點:cookie保存狀態 html代碼: css代碼: js代碼: 效果圖: ...
  • 前面的話 前端工程師最基本的工作是切圖。photoshop用的6不6,對於工作效率有很大的影響。小火柴將前端工程師需要掌握的photoshop的知識和技能進行了梳理和歸納,總結成以下目錄 目錄 前端工程師技能之photoshop巧用系列第一篇——準備篇 前端工程師技能之photoshop巧用系列第二 ...
  • × 目錄 [1]初始設置 [2]自動切圖 前面的話 隨著photoshop版本的不斷升級,軟體本身增加了很多新的功能,也為切圖工作增加了很多的便利。photoshop最新的版本新增了自動切圖功能,本文將詳細介紹photoshop的這個新功能 初始設置 當然首先還是要進行一些首選項設置 【1】在編輯 ...
  • 源碼: <input type="checkbox" id="cleckAll" />全選 <div class="list"> <input type="checkbox" />覆選一 <input type="checkbox" />覆選二 <input type="checkbox" />覆選 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...