整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript錯誤處理指南

          JavaScript錯誤處理指南

          文將分三部分分析 JavaScript 中的錯誤,首先我們將了解錯誤的一般情況,之后,我們將關注后端(Node.js + Express.js),最后,我們將重點看下如何處理 React.js 中的錯誤。選擇這些框架,是因為它們是目前最流行的,但是,你應該也能夠將這些新發現應用到其他框架中吧!

          我們害怕錯誤,因為錯誤往往會涉及到在公共場合受到傷害或羞辱。通過犯錯誤,我們實際上學會了如何不去做某事,以及下次如何做得更好。

          顯然,這是關于從現實生活的錯誤中學習。編程中的錯誤有點不同。它們為我們提供了很好的特征來改進我們的代碼,并告訴用戶什么地方出了問題(也可能是教他們如何修復它)。

          GitHub(https://github.com/gisderdube/graceful-error-handling) 上提供了一個完整的樣例項目。

          JavaScript 錯誤和一般處理

          throw new Error(’something went wrong’)?會在 JavaScript 中創建一個錯誤實例,并停止腳本的執行,除非你對錯誤做了一些處理。當你作為 JavaScript 開發者開啟自己的職業生涯時,你自己很可能不會這樣做,但是,你已經從其他庫(或運行時)那里看到了,例如,類似“ReferenceError: fs 未定義”這樣的錯誤。

          Error 對象

          Error 對象有兩個內置屬性供我們使用。第一個是消息,作為參數傳遞給 Error 構造函數,例如 new Error(“這是錯誤消息”)。你可以通過 message 屬性訪問消息:

          const myError=new Error(‘請改進代碼’)
          console.log(myError.message) // 請改進代碼
          

          第二個是錯誤堆棧跟蹤,這個屬性非常重要。你可以通過 stack 屬性訪問它。錯誤堆棧將為你提供歷史記錄(調用堆棧),從中可以查看哪個文件導致了錯誤。堆棧的上部也包括消息,然后是實際的堆棧,從距離錯誤發生最近的點開始,一直到最外層“需要為錯誤負責”的文件:

          Error: 請改進代碼
           at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
           at Module._compile (internal/modules/cjs/loader.js:689:30)
           at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
           at Module.load (internal/modules/cjs/loader.js:599:32)
           at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
           at Function.Module._load (internal/modules/cjs/loader.js:530:3)
           at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
           at startup (internal/bootstrap/node.js:266:19)
           at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
          

          拋出和處理錯誤

          現在,Error 實例本身不會導致任何結果,例如,new Error(’…’) 不會做任何事情。當錯誤被拋出時,就會變得更有趣。然后,如前所述,腳本將停止執行,除非你在流程中以某種方式對它進行了處理。記住,是手動拋出錯誤,還是由庫拋出錯誤,甚至由運行時本身(Node 或瀏覽器),都沒有關系。讓我們看看如何在不同的場景中處理這些錯誤。

          try …. catch

          這是最簡單但經常被遺忘的錯誤處理方法——多虧 async / await,它的使用現在又多了起來。它可以用來捕獲任何類型的同步錯誤,例如,如果我們不把 console.log(b) 放在一個 try … catch 塊中,腳本會停止執行。

          … finally

          有時候,不管是否有錯誤,代碼都需要執行。你可以使用第三個可選塊 finally。通常,這與在 try…catch 語句后面加一行代碼是一樣的,但它有時很有用。

          異步性——回調

          異步性,這是在使用 JavaScript 時必須考慮的一個主題。當你有一個異步函數,并且該函數內部發生錯誤時,你的腳本將繼續執行,因此,不會立即出現任何錯誤。當使用回調函數處理異步函數時(不推薦),你通常會在回調函數中收到兩個參數,如下所示:

          如果有錯誤,err 參數就等同于那個錯誤。如果沒有,參數將是 undefined 或 null。要么在 if(err) 塊中返回某項內容,要么將其他指令封裝在 else 塊中,這一點很重要,否則你可能會得到另一個錯誤,例如,result 可能未定義,而你試圖訪問 result.data,類似這樣的情況。

          異步性——Promises

          處理異步性的更好方法是使用 Promises。在這一點上,除了代碼可讀性更強之外,我們還改進了錯誤處理。只要有一個 catch 塊,我們就不再需要太關注具體的錯誤捕獲。在鏈接 Promises 時,catch 塊捕獲會自 Promises 執行或上一個 catch 塊以來的所有錯誤。注意,沒有 catch 塊的 Promises 不會終止腳本,但會給你一條可讀性較差的消息,比如:

          (node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
          (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
          

          因此,務必要在 Promises 中加入 catch 塊。

          回到 try … catch

          隨著 JavaScript 引入 async / await,我們回到了最初的錯誤處理方法,借助 try … catch … finally,錯誤處理變得非常簡單。

          因為這和我們處理“普通”同步錯誤的方法是一樣的,所以如果需要的話,更容易使用作用域更大的 catch 語句。

          服務器端錯誤的產生和處理

          現在,我們已經有了處理錯誤的工具,讓我們看下,我們在實際情況下能用它們做什么。后端錯誤的產生和處理是應用程序至關重要的組成部分。對于錯誤處理,有不同的方法。我將向你展示一個自定義 Error 構造函數和錯誤代碼的方法,我們可以輕松地傳遞到前端或任何 API 消費者。構建后端的細節并不重要,基本思路不變。

          我們將使用 Express.js 作為路由框架。讓我們考慮下最有效的錯誤處理結構。我們希望:

          1. 一般錯誤處理,如某種回退,基本上只是說:“有錯誤,請再試一次或聯系我們”。這并不是特別聰明,但至少通知用戶,有地方錯了——而不是無限加載或進行類似地處理。
          2. 特殊錯誤處理為用戶提供詳細信息,讓用戶了解有什么問題以及如何解決它,例如,有信息丟失,數據庫中的條目已經存在等等。

          構建一個自定義 Error 構造函數

          我們將使用已有的 Error 構造函數并擴展它。繼承在 JavaScript 中是一件危險的事情,但根據我的經驗,在這種情況下,它非常有用。我們為什么需要它?我們仍然希望堆棧跟蹤給我們一個很好的調試體驗。擴展 JavaScript 原生 Error 構造函數可以讓我們方便地獲得堆棧跟蹤。我們唯一要做的是添加代碼(我們稍后可以通過錯誤代碼訪問)和要傳遞給前端的狀態(http 狀態代碼)。

          如何處理路由

          在完成 Error 的自定義之后,我們需要設置路由結構。正如我指出的那樣,我們想要一個單點真錯誤處理,就是說,對于每一個路由,我們要有相同的錯誤處理行為。在默認情況下,由于路由都是封裝的,所以 Express 并不真正支持那種方式。

          為了解決這個問題,我們可以實現一個路由處理程序,并把實際的路由邏輯定義為普通的函數。這樣,如果路由功能(或任何內部函數)拋出一個錯誤,它將返回到路由處理程序,然后可以傳給前端。當后端發生錯誤時,我們可以用以下格式傳遞一個響應給前端——比如一個 JSON API:

          {
           error: 'SOME_ERROR_CODE',
           description: 'Something bad happened. Please try again or contact support.'
          }
          

          準備好不知所措。當我說下面的話時,我的學生總是生我的氣:

          如果你咋看之下并不是什么都懂,那沒問題。只要使用一段時間,你就會發現為什么要那樣。

          順便說一下,這可以稱為自上而下的學習,我非常喜歡。

          路由處理程序就是這個樣子:

          我希望你能讀下代碼中的注釋,我認為那比我在這里解釋更有意義。現在,讓我們看下實際的路由文件是什么樣子:

          在這些例子中,我沒有做任何有實際要求的事情,我只是假設不同的錯誤場景。例如,GET /city 在第 3 行結束,POST /city 在第 8 號結束等等。這也適用于查詢參數,例如,GET /city?startsWith=R。本質上,你會有一個未處理的錯誤,前端會收到:

          {
           error: 'GENERIC',
           description: 'Something went wrong. Please try again or contact support.'
          }
          

          或者你將手動拋出 CustomError,例如:

          throw new CustomError('MY_CODE', 400, 'Error description')
          

          上述代碼會轉換成:

          {
           error: 'MY_CODE',
           description: 'Error description'
          }
          

          既然我們有了這個漂亮的后端設置,我們就不會再把錯誤日志泄漏到前端,而總是返回有用的信息,說明出現了什么問題。

          確保你已經在 GitHub(https://github.com/gisderdube/graceful-error-handling) 上看過完整的庫。你可以把它用在任何項目中,并根據自己的需要來修改它!

          向用戶顯示錯誤

          下一個也是最后一個步驟是管理前端的錯誤。這里,你要使用第一部分描述的工具處理由前端邏輯產生的錯誤。不過,后端的錯誤也要顯示。首先,讓我們看看如何顯示錯誤。如前所述,我們將使用 React 進行演練。

          把錯誤保存在 React 狀態中

          和其他數據一樣,錯誤和錯誤消息會變化,因此,你想把它們放在組件狀態中。在默認情況下,你想要在加載時重置錯誤,以便用戶第一次看到頁面時,不會看到錯誤。

          接下來我們必須澄清的是不同錯誤類型及與其匹配的可視化表示。就像在后端一樣,有 3 種類型:

          1. 全局錯誤,例如,其中一個常見的錯誤是來自后端,或者用戶沒有登錄等。
          2. 來自后端的具體錯誤,例如,用戶向后端發送登錄憑證。后端答復密碼不匹配。前端無法進行此項驗證,所以這樣的信息只能來自后端。
          3. 由前端導致的具體錯誤,例如,電子郵件輸入驗證失敗。

          2 和 3 非常類似,雖然源頭不一樣,但如果你愿意,就可以在同樣的 state 中處理。我們將從代碼中看下如何實現。

          我將使用 React 的原生 state 實現,但是,你還可以使用類似 MobX 或 Redux 這樣的狀態管理系統。

          全局錯誤

          通常,我將把這些錯誤保存在最外層的有狀態組件中,并渲染一個靜態 UI 元素,這可能是屏幕頂部的一個紅色橫幅、模態或其他什么東西,設計實現由你決定。

          讓我們看下代碼:

          正如你看到的那樣,Application.js 中的狀態存在錯誤。我們也有方法可以重置并改變錯誤的值。我們把值和重置方法傳遞給 GlobalError 組件,在點擊’x’時,該組件會顯示錯誤并重置它。讓我們看看 GlobalError 組件:

          你可以看到,在第 5 行,如果沒有錯誤,我們就不做任何渲染。這可以防止我們的頁面上出現一個空的紅框。當然,你可以改變這個組件的外觀和行為。例如,你可以將“x”替換為 Timeout,幾秒鐘后重置錯誤狀態。

          現在,你已經準備好在任何地方使用全局錯誤狀態了,只是從 Application.js 把 _setError 向下傳遞,而且,你可以設置全局錯誤,例如,當一個請求從后端返回了字段 error: ‘GENERIC’。例如:

          如果你比較懶,到這里就可以結束了。即使你有具體的錯誤,你總是可以改變全局錯誤狀態,并把錯誤提示框顯示在頁面頂部。不過,我將向你展示如何處理和顯示具體的錯誤。為什么?首先,這是關于錯誤處理的權威指南,所以我不能停在這里。其次,如果你只是把所有的錯誤都作為全局錯誤來顯示,那么體驗人員會瘋掉。

          處理具體的請求錯誤

          和全局錯誤類似,我們也有位于其他組件內部的局部錯誤狀態,過程相同:

          有件事要記住,清除錯誤通常有一個不同的觸發器。用’ x ’刪除錯誤是沒有意義的。關于這一點,在發出新請求時清除錯誤會更有意義。你還可以在用戶進行更改時清除錯誤,例如當修改輸入值時。

          源于前端的錯誤

          如前所述,這些錯誤可以使用與處理后端具體錯誤相同的方式(狀態)進行處理。這次,我們將使用一個有輸入字段的示例,只允許用戶在實際提供以下輸入時刪除一個城市:

          使用錯誤代碼實現錯誤國際化

          也許你一直想知道為什么我們有這些錯誤代碼,例如 GENERIC ,我們只是顯示從后端傳遞過來的錯誤描述。現在,隨著你的應用越來越大,你就會希望征服新的市場,并在某個時候面臨多種語言支持的問題。如果你到了這個時候,你就可以使用前面提到的錯誤代碼使用用戶的語言來顯示恰當的描述。

          我希望你對如何處理錯誤有了一些了解。忘掉 console.error(err),它現在已經是過去時了。可以使用它進行調試,但它不應該出現在最終的產品構建中。為了防止這種情況,我建議你使用日志庫,我過去一直使用 loglevel,我對它非常滿意。

          avaScript編程語言:

          1.專門在瀏覽器編譯與執行的編程語言

          2.幫助瀏覽器解決用戶提出簡單需求

          3.基于面向對象采用弱類型語法風格實現

          JavaScript學習教程

          老杜講解的JavaScript教程,內容涵蓋了JavaScript核心語法、JavaScript內置支持類、JavaScript調試、JavaScript DOM編程、JavaScript BOM編程、大量前端小案例、JavaScript事件處理、JavaScript對象、繼承、JSON等知識點。

          接下來說的JavaScript學習內容均與下邊的javaweb學習教程相結合

          http://www.bjpowernode.com/?chaijavaweb

          基礎語法

          一、JavaScript命令書寫方式

          1.在HTML文件中<script>內部進行命令書寫

          2. 在js文件中書寫命令格式【推薦】

          二、JavaScript中變量

          1.變量聲明方式: JavaScript弱類型編程語言,因此禁止使用具體數據類型修飾變量

          • var 變量名;
          • var 變量名1,變量名2;

          2.變量賦值方式

          • var 變量名=值
          • var 變量名;
          • 變量名=值;

          3.變量名命名規則

          1)變量名只能存在字母,數字,下劃線, 美元符號($)

          2) 變量名不能以數字為開始

          3) 不能使用JavaScript關鍵字作為變量名 var var=10; error

          三、JavaScript中數據類型

          1.分類:

          1)基本數據類型

          2) 高級引用數據類型

          2. 基本數據類型

          1) number類型:數字類型,整數,單精度,雙精度都是number類型

          2) string類型: 字符串類型,字符串既可以包裹在一對雙引號中也可以包裹在一對單引號 "abc" 'abc'

          3) boolean類型: 布爾類型 值true/false

          3.高級引用數據類型

          1) function類型:函數類型.相當于Java中Method類型。一個function類型對象用于管理一個函數

          2) object類型: 在JavaScript中所有通過構造函數生成的對象都是object

          4. 變量與數據類型之間關系:

          JavaScript中根據變量賦值內容判斷變量的數據類型

          四、JavaScript中特殊值

          1. undefined:javascript中變量沒有賦值時,其默認值都是undefined。

          此時變量數據類型也是undefined

          2.NaN: 表示非法數字。此時變量數據類型是number

          3.Infinity:表示一個無窮大數字.此時變量數據類型是number

          4.null:表示當前對象指向一個空內存,但是空內存不能存儲數據

          此時對象數據類型是object

          五、JavaScript中控制語句和循環語句

          1.與Java控制語句和循環語句語法完全一致

          2.控制語句 if ,switch

          3.循環語句 for while,do..while

          六、JavaScript中數組

          1.JavaScript中數組相當于Java中List集合

          2.JavaScript中數組一次可以存放任意類型數據

          3.JavaScript中數組控制內存個數可以隨意改變

          七、JavaScript中函數

          1.函數聲明格式

          function 函數名(形參名,形參名){


          命令行;


          命令行;


          eturn 返回值


          }

          1) 函數聲明時,必須使用function修飾進行修飾

          2) 函數聲明時,禁止指定返回數據類型

          3)函數聲明時, 形參只能指定名稱,但是不能指定數據類型,也不能使用var進行修飾

          4)函數聲明時,可以通過return指定返回數據。

          2.函數調用:

          對象.函數(實參)

          ***window對象中屬性和函數在調用時,可以不寫window

          JavaScript應用篇(面試)

          一、JavaScript中dom對象與document對象

          1.dom對象

          1) dom對象:document Object model;文檔模型對象

          2) 一個dom對象用于管理一個HTML標簽

          3)瀏覽器每加載一個HTML標簽時,自動為這個HTML標簽生成一個DOM對象

          2.document

          1)document對象 文檔對象

          2) 瀏覽器根據html標簽聲明順序在內存中以樹形結構方式存儲DOM對象.

          3) document對象由瀏覽器生成 。一個瀏覽器只有一個document對象

          4)document對象負責根據定位條件定位dom對象

          二、dom對象定位方式:

          1.根據標簽Id屬性定位關聯的DOM對象

          var domObj=document.getElementById("id屬性")

          2.根據標簽name屬性定位關聯的DOM對象

          var domArray=document.getElementsByName("name屬性")

          3.根據標簽類型定位關聯的DOM對象

          var domArray=document.getElementsByTagName("標簽類型")

          三、dom對象對標簽管理:

          1.dom對象作用:用于對管理的標簽中屬性進行賦值與取值操作

          2.dom對象管理標簽中value屬性:

          var num=dom.value; //讀取

          dom.value=num; //賦值

          3.dom對象管理標簽狀態屬性

          checked是boolean checked=true 表示被選中,checked=false 表示未被選中

          4.dom對象管理雙目標簽提示信息

          dom.innerText=值;

          var num=dom.innerText;

          5.dom對象管理標簽的樣式屬性

          dom.style.樣式屬性名=值

          var num=dom.style.樣式屬性名

          四、監聽事件

          1. 介紹:

          1)HTML標簽中一組屬性

          2)監聽用戶在何時以何種方式來操作當前標簽。當監聽行為發生時。

          監聽事件通知瀏覽器調用javascript函數進行處理

          2.分類:

          1)監聽用戶使用鼠標操作標簽---鼠標監聽事件

          2) 監聽用戶使用鍵盤操作標簽---鍵盤監聽事件

          3.鍵盤監聽事件

          1)onkeydown: 監聽用戶在何時在當前標簽上按下鍵盤

          2)onkeyup:監聽用戶在何時在當前標簽上彈起鍵盤

          3)onkeypress:監聽用戶在何時在當前標簽按下一次鍵盤

          4.鍵盤監聽事件

          1)onclick:監聽用戶何時使用鼠標單擊當前的HTML標簽

          2) onblur:監聽用戶何時使用鼠標讓當前標簽丟失光標

          3)onfocus:監聽用戶何時使用鼠標讓當前標簽獲得光標

          4) onmouseover:監聽用戶何時使用鼠標懸停在標簽上方

          5) onmouseout:監聽用戶何時使用鼠標從標簽上方移開

          6)onchange:監聽用戶何時使用鼠標改變下拉列表中選中項


          his是JavaScript中的一個關鍵字,但是又一個相對比較特別的關鍵字,不像function、var、for、if這些關鍵字一樣,可以很清楚的搞清楚它到底是如何使用的。

          this會在執行上下文中綁定一個對象,但是是根據什么條件綁定的呢?在不同的執行條件下會綁定不同的對象,這也是讓人捉摸不定的地方。

          這一次,我們一起來徹底搞定this到底是如何綁定的吧!

          一. 理解this

          1.1. 為什么使用this

          在常見的編程語言中,幾乎都有this這個關鍵字(Objective-C中使用的是self),但是JavaScript中的this和常見的面向對象語言中的this不太一樣:

          • 常見面向對象的編程語言中,比如Java、C++、Swift、Dart等等一系列語言中,this通常只會出現在類的方法中。
          • 也就是你需要有一個類,類中的方法(特別是實例方法)中,this代表的是當前調用對象。
          • 但是JavaScript中的this更加靈活,無論是它出現的位置還是它代表的含義。

          使用this有什么意義呢?下面的代碼中,我們通過對象字面量創建出來一個對象,當我們調用對象的方法時,希望將對象的名稱一起進行打印。

          如果沒有this,那么我們的代碼會是下面的寫法:

          • 在方法中,為了能夠獲取到name名稱,必須通過obj的引用(變量名稱)來獲取。
          • 但是這樣做有一個很大的弊端:如果我將obj的名稱換成了info,那么所有的方法中的obj都需要換成info。
          var obj={
            name: "why",
            running: function() {
              console.log(obj.name + " running");
            },
            eating: function() {
              console.log(obj.name + " eating");
            },
            studying: function() {
              console.log(obj.name + " studying");
            }
          }
          

          事實上,上面的代碼,在實際開發中,我們都會使用this來進行優化:

          • 當我們通過obj去調用running、eating、studying這些方法時,this就是指向的obj對象
          var obj={
            name: "why",
            running: function() {
              console.log(this.name + " running");
            },
            eating: function() {
              console.log(this.name + " eating");
            },
            studying: function() {
              console.log(this.name + " studying");
            }
          }
          

          所以我們會發現,在某些函數或者方法的編寫中,this可以讓我們更加便捷的方式來引用對象,在進行一些API設計時,代碼更加的簡潔和易于復用。

          當然,上面只是應用this的一個場景而已,開發中使用到this的場景到處都是,這也是為什么它不容易理解的原因。

          1.2. this指向什么

          我們先說一個最簡單的,this在全局作用域下指向什么?

          • 這個問題非常容易回答,在瀏覽器中測試就是指向window
          • 所以,在全局作用域下,我們可以認為this就是指向的window
          console.log(this); // window
          
          var name="why";
          console.log(this.name); // why
          console.log(window.name); // why
          

          但是,開發中很少直接在全局作用域下去使用this,通常都是在函數中使用

          所有的函數在被調用時,都會創建一個執行上下文:

          • 這個上下文中記錄著函數的調用棧、函數的調用方式、傳入的參數信息等;
          • this也是其中的一個屬性;

          我們先來看一個讓人困惑的問題:

          • 定義一個函數,我們采用三種不同的方式對它進行調用,它產生了三種不同的結果
          // 定義一個函數
          function foo() {
            console.log(this);
          }
          
          // 1.調用方式一: 直接調用
          foo(); // window
          
          // 2.調用方式二: 將foo放到一個對象中,再調用
          var obj={
            name: "why",
            foo: foo
          }
          obj.foo() // obj對象
          
          // 3.調用方式三: 通過call/apply調用
          foo.call("abc"); // String {"abc"}對象
          

          上面的案例可以給我們什么樣的啟示呢?

          • 1.函數在調用時,JavaScript會默認給this綁定一個值;
          • 2.this的綁定和定義的位置(編寫的位置)沒有關系;
          • 3.this的綁定和調用方式以及調用的位置有關系;
          • 4.this是在運行時被綁定的;

          那么this到底是怎么樣的綁定規則呢?一起來學習一下吧

          二. this綁定規則

          我們現在已經知道this無非就是在函數調用時被綁定的一個對象,我們就需要知道它在不同的場景下的綁定規則即可。

          2.1. 默認綁定

          什么情況下使用默認綁定呢?獨立函數調用。

          • 獨立的函數調用我們可以理解成函數沒有被綁定到某個對象上進行調用;

          案例一:普通函數調用

          • 該函數直接被調用,并沒有進行任何的對象關聯;
          • 這種獨立的函數調用會使用默認綁定,通常默認綁定時,函數中的this指向全局對象(window);
          function foo() {
            console.log(this); // window
          }
          
          foo();
          

          案例二:函數調用鏈(一個函數調用另外一個函數)

          • 所有的函數調用都沒有被綁定到某個對象上;
          // 2.案例二:
          function test1() {
            console.log(this); // window
            test2();
          }
          
          function test2() {
            console.log(this); // window
            test3()
          }
          
          function test3() {
            console.log(this); // window
          }
          test1();
          

          案例三:將函數作為參數,傳入到另一個函數中

          function foo(func) {
            func()
          }
          
          function bar() {
            console.log(this); // window
          }
          
          foo(bar);
          

          我們對案例進行一些修改,考慮一下打印結果是否會發生變化:

          • 這里的結果依然是window,為什么呢?
          • 原因非常簡單,在真正函數調用的位置,并沒有進行任何的對象綁定,只是一個獨立函數的調用;
          function foo(func) {
            func()
          }
          
          var obj={
            name: "why",
            bar: function() {
              console.log(this); // window
            }
          }
          
          foo(obj.bar);
          

          2.2. 隱式綁定

          另外一種比較常見的調用方式是通過某個對象進行調用的:

          • 也就是說它的調用位置中,是通過某個對象發起的函數調用。

          案例一:通過對象調用函數

          • foo的調用位置是obj.foo()方式進行調用的
          • 那么foo調用時this會隱式地被綁定到obj對象上
          function foo() {
            console.log(this); // obj對象
          }
          
          var obj={
            name: "why",
            foo: foo
          }
          
          obj.foo();
          

          案例二:案例一的變化

          • 我們通過obj2又引用了obj1對象,再通過obj1對象調用foo函數;
          • 那么foo調用的位置上其實還是obj1被綁定了this;
          function foo() {
            console.log(this); // obj對象
          }
          
          var obj1={
            name: "obj1",
            foo: foo
          }
          
          var obj2={
            name: "obj2",
            obj1: obj1
          }
          
          obj2.obj1.foo();
          

          案例三:隱式丟失

          • 結果最終是window,為什么是window呢?
          • 因為foo最終被調用的位置是bar,而bar在進行調用時沒有綁定任何的對象,也就沒有形成隱式綁定;
          • 相當于是一種默認綁定;
          function foo() {
            console.log(this);
          }
          
          var obj1={
            name: "obj1",
            foo: foo
          }
          
          // 講obj1的foo賦值給bar
          var bar=obj1.foo;
          bar();
          

          2.3. 顯示綁定

          隱式綁定有一個前提條件:

          • 必須在調用的對象內部有一個對函數的引用(比如一個屬性);
          • 如果沒有這樣的引用,在進行調用時,會報找不到該函數的錯誤;
          • 正是通過這個引用,間接地將this綁定到了這個對象上;

          如果我們不希望在 對象內部 包含這個函數的引用,同時又希望在這個對象上進行強制調用,該怎么做呢?

          • JavaScript所有的函數都可以使用call和apply方法(這個和Prototype有關)。
            • 它們兩個的區別這里不再展開;
            • 其實非常簡單,第一個參數是相同的,后面的參數,apply為數組,call為參數列表;
          • 這兩個函數的第一個參數都要求是一個對象,這個對象的作用是什么呢?就是給this準備的。
          • 在調用這個函數時,會將this綁定到這個傳入的對象上。

          因為上面的過程,我們明確地綁定了this指向的對象,所以稱之為 顯示綁定

          2.3.1. call、apply

          通過call或者apply綁定this對象

          • 顯示綁定后,this就會明確地指向綁定的對象
          function foo() {
            console.log(this);
          }
          
          foo.call(window); // window
          foo.call({name: "why"}); // {name: "why"}
          foo.call(123); // Number對象,存放時123
          

          2.3.2. bind函數

          如果我們希望一個函數總是顯示的綁定到一個對象上,可以怎么做呢?

          方案一:自己手寫一個輔助函數(了解)

          • 我們手動寫了一個bind的輔助函數
          • 這個輔助函數的目的是在執行foo時,總是讓它的this綁定到obj對象上
          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "why"
          }
          
          function bind(func, obj) {
            return function() {
              return func.apply(obj, arguments);
            }
          }
          
          var bar=bind(foo, obj);
          
          bar(); // obj對象
          bar(); // obj對象
          bar(); // obj對象
          

          方案二:使用Function.prototype.bind

          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "why"
          }
          
          var bar=foo.bind(obj);
          
          bar(); // obj對象
          bar(); // obj對象
          bar(); // obj對象
          

          2.3.3. 內置函數

          有些時候,我們會調用一些JavaScript的內置函數,或者一些第三方庫中的內置函數。

          • 這些內置函數會要求我們傳入另外一個函數;
          • 我們自己并不會顯示的調用這些函數,而且JavaScript內部或者第三方庫內部會幫助我們執行;
          • 這些函數中的this又是如何綁定的呢?

          案例一:setTimeout

          • setTimeout中會傳入一個函數,這個函數中的this通常是window
          setTimeout(function() {
            console.log(this); // window
          }, 1000);
          

          為什么這里是window呢?

          • 這個和setTimeout源碼的內部調用有關;
          • setTimeout內部是通過apply進行綁定的this對象,并且綁定的是全局對象;

          案例二:數組的forEach

          數組有一個高階函數forEach,用于函數的遍歷:

          • 在forEach中傳入的函數打印的也是Window對象;
          • 這是因為默認情況下傳入的函數是自動調用函數(默認綁定);
          var names=["abc", "cba", "nba"];
          names.forEach(function(item) {
            console.log(this); // 三次window
          });
          

          我們是否可以改變該函數的this指向呢?

          forEach參數

          var names=["abc", "cba", "nba"];
          var obj={name: "why"};
          names.forEach(function(item) {
            console.log(this); // 三次obj對象
          }, obj);
          

          案例三:div的點擊

          如果我們有一個div元素:

          • 注意:省略了部分代碼
            <style>
              .box {
                width: 200px;
                height: 200px;
                background-color: red;
              }
            </style>
          
            <div class="box"></div>
          

          獲取元素節點,并且監聽點擊:

          • 在點擊事件的回調中,this指向誰呢?box對象;
          • 這是因為在發生點擊時,執行傳入的回調函數被調用時,會將box對象綁定到該函數中;
          var box=document.querySelector(".box");
          box.onclick=function() {
            console.log(this); // box對象
          }
          

          所以傳入到內置函數的回調函數this如何確定呢?

          • 某些內置的函數,我們很難確定它內部是如何調用傳入的回調函數;
          • 一方面可以通過分析源碼來確定,另一方面我們可以通過經驗(見多識廣)來確定;
          • 但是無論如何,通常都是我們之前講過的規則來確定的;

          2.4. new綁定

          JavaScript中的函數可以當做一個類的構造函數來使用,也就是使用new關鍵字。

          使用new關鍵字來調用函數時,會執行如下的操作:

          • 1.創建一個全新的對象;
          • 2.這個新對象會被執行Prototype連接;
          • 3.這個新對象會綁定到函數調用的this上(this的綁定在這個步驟完成);
          • 4.如果函數沒有返回其他對象,表達式會返回這個新對象;
          // 創建Person
          function Person(name) {
            console.log(this); // Person {}
            this.name=name; // Person {name: "why"}
          }
          
          var p=new Person("why");
          console.log(p);
          

          2.5. 規則優先級

          學習了四條規則,接下來開發中我們只需要去查找函數的調用應用了哪條規則即可,但是如果一個函數調用位置應用了多條規則,優先級誰更高呢?

          1.默認規則的優先級最低

          毫無疑問,默認規則的優先級是最低的,因為存在其他規則時,就會通過其他規則的方式來綁定this

          2.顯示綁定優先級高于隱式綁定

          顯示綁定和隱式綁定哪一個優先級更高呢?這個我們可以測試一下:

          • 結果是obj2,說明是顯示綁定生效了
          function foo() {
            console.log(this);
          }
          
          var obj1={
            name: "obj1",
            foo: foo
          }
          
          var obj2={
            name: "obj2",
            foo: foo
          }
          
          // 隱式綁定
          obj1.foo(); // obj1
          obj2.foo(); // obj2
          
          // 隱式綁定和顯示綁定同時存在
          obj1.foo.call(obj2); // obj2, 說明顯式綁定優先級更高
          

          3.new綁定優先級高于隱式綁定

          • 結果是foo,說明是new綁定生效了
          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "why",
            foo: foo
          }
          
          new obj.foo(); // foo對象, 說明new綁定優先級更高
          

          4.new綁定優先級高于bind

          new綁定和call、apply是不允許同時使用的,所以不存在誰的優先級更高

          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "obj"
          }
          
          var foo=new foo.call(obj);
          

          new和call同時使用

          但是new綁定是否可以和bind后的函數同時使用呢?可以

          • 結果顯示為foo,那么說明是new綁定生效了
          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "obj"
          }
          
          // var foo=new foo.call(obj);
          var bar=foo.bind(obj);
          var foo=new bar(); // 打印foo, 說明使用的是new綁定
          

          優先級總結:

          • new綁定 > 顯示綁定(bind)> 隱式綁定 > 默認綁定

          三. this規則之外

          我們講到的規則已經足以應付平時的開發,但是總有一些語法,超出了我們的規則之外。(神話故事和動漫中總是有類似這樣的人物)

          3.1. 忽略顯示綁定

          如果在顯示綁定中,我們傳入一個null或者undefined,那么這個顯示綁定會被忽略,使用默認規則:

          function foo() {
            console.log(this);
          }
          
          var obj={
            name: "why"
          }
          
          foo.call(obj); // obj對象
          foo.call(null); // window
          foo.call(undefined); // window
          
          var bar=foo.bind(null);
          bar(); // window
          

          3.2. 間接函數引用

          另外一種情況,創建一個函數的 間接引用,這種情況使用默認綁定規則。

          我們先來看下面的案例結果是什么?

          • (num2=num1)的結果是num1的值;
          var num1=100;
          var num2=0;
          var result=(num2=num1);
          console.log(result); // 100
          

          我們來下面的函數賦值結果:

          • 賦值(obj2.foo=obj1.foo)的結果是foo函數;
          • foo函數被直接調用,那么是默認綁定;
          function foo() {
            console.log(this);
          }
          
          var obj1={
            name: "obj1",
            foo: foo
          }; 
          
          var obj2={
            name: "obj2"
          }
          
          obj1.foo(); // obj1對象
          (obj2.foo=obj1.foo)();  // window
          

          3.3. ES6箭頭函數

          在ES6中新增一個非常好用的函數類型:箭頭函數

          • 這里不再具體介紹箭頭函數的用法,可以自行學習。

          箭頭函數不使用this的四種標準規則(也就是不綁定this),而是根據外層作用域來決定this。

          我們來看一個模擬網絡請求的案例:

          • 這里我使用setTimeout來模擬網絡請求,請求到數據后如何可以存放到data中呢?
          • 我們需要拿到obj對象,設置data;
          • 但是直接拿到的this是window,我們需要在外層定義:var _this=this
          • 在setTimeout的回調函數中使用_this就代表了obj對象
          var obj={
            data: [],
            getData: function() {
              var _this=this;
              setTimeout(function() {
                // 模擬獲取到的數據
                var res=["abc", "cba", "nba"];
                _this.data.push(...res);
              }, 1000);
            }
          }
          
          obj.getData();
          

          上面的代碼在ES6之前是我們最常用的方式,從ES6開始,我們會使用箭頭函數:

          • 為什么在setTimeout的回調函數中可以直接使用this呢?
          • 因為箭頭函數并不綁定this對象,那么this引用就會從上層作用域中找到對應的this
          var obj={
            data: [],
            getData: function() {
              setTimeout(()=> {
                // 模擬獲取到的數據
                var res=["abc", "cba", "nba"];
                this.data.push(...res);
              }, 1000);
            }
          }
          
          obj.getData();
          

          思考:如果getData也是一個箭頭函數,那么setTimeout中的回調函數中的this指向誰呢?

          • 答案是window;
          • 依然是不斷的從上層作用域找,那么找到了全局作用域;
          • 在全局作用域內,this代表的就是window
          var obj={
            data: [],
            getData: ()=> {
              setTimeout(()=> {
                console.log(this); // window
              }, 1000);
            }
          }
          
          obj.getData();
          

          四. this面試題

          4.1. 面試題一:

          var name="window";
          var person={
            name: "person",
            sayName: function () {
              console.log(this.name);
            }
          };
          function sayName() {
            var sss=person.sayName;
            sss(); 
            person.sayName(); 
            (person.sayName)(); 
            (b=person.sayName)(); 
          }
          sayName();
          

          這道面試題非常簡單,無非就是繞一下,希望把面試者繞暈:

          function sayName() {
            var sss=person.sayName;
            // 獨立函數調用,沒有和任何對象關聯
            sss(); // window
            // 關聯
            person.sayName(); // person
            (person.sayName)(); // person
            (b=person.sayName)(); // window
          }
          

          4.2. 面試題二:

          var name='window'
          var person1={
            name: 'person1',
            foo1: function () {
              console.log(this.name)
            },
            foo2: ()=> console.log(this.name),
            foo3: function () {
              return function () {
                console.log(this.name)
              }
            },
            foo4: function () {
              return ()=> {
                console.log(this.name)
              }
            }
          }
          
          var person2={ name: 'person2' }
          
          person1.foo1(); 
          person1.foo1.call(person2); 
          
          person1.foo2();
          person1.foo2.call(person2);
          
          person1.foo3()();
          person1.foo3.call(person2)();
          person1.foo3().call(person2);
          
          person1.foo4()();
          person1.foo4.call(person2)();
          person1.foo4().call(person2);
          

          下面是代碼解析:


          主站蜘蛛池模板: 少妇无码一区二区三区免费| 波多野结衣AV无码久久一区| 国产一区二区好的精华液| 久久精品免费一区二区| 亚洲精品一区二区三区四区乱码| 国产电影一区二区| 中文字幕一区二区三区精华液| 国产乱码精品一区二区三区香蕉 | 亚洲国产精品成人一区| 91视频一区二区三区| 久久精品视频一区| 亚洲一区二区三区影院| 91国在线啪精品一区| 一区二区三区www| 人妻无码一区二区三区四区| 国产在线精品一区在线观看| 少妇人妻精品一区二区| 无码精品一区二区三区| 亚洲乱色熟女一区二区三区蜜臀 | 亚洲国产AV一区二区三区四区| 文中字幕一区二区三区视频播放 | 亚洲乱码一区二区三区国产精品| 一区二区三区观看免费中文视频在线播放| 国产成人高清精品一区二区三区| 内射少妇一区27P| 精品一区二区三区色花堂| 好爽毛片一区二区三区四 | 99久久无码一区人妻a黑| 精品无码国产一区二区三区AV| 色久综合网精品一区二区| 91久久精品一区二区| 无码午夜人妻一区二区不卡视频| 在线观看精品一区| 一区二区三区美女视频| 国模无码人体一区二区| 97久久精品一区二区三区| 亚洲av综合av一区二区三区| 国产伦精品一区二区三区视频小说| 国产一区二区三区免费在线观看| 一区二区三区在线视频播放| 成人精品视频一区二区三区不卡 |