文將分三部分分析 JavaScript 中的錯誤,首先我們將了解錯誤的一般情況,之后,我們將關注后端(Node.js + Express.js),最后,我們將重點看下如何處理 React.js 中的錯誤。選擇這些框架,是因為它們是目前最流行的,但是,你應該也能夠將這些新發現應用到其他框架中吧!
我們害怕錯誤,因為錯誤往往會涉及到在公共場合受到傷害或羞辱。通過犯錯誤,我們實際上學會了如何不去做某事,以及下次如何做得更好。
顯然,這是關于從現實生活的錯誤中學習。編程中的錯誤有點不同。它們為我們提供了很好的特征來改進我們的代碼,并告訴用戶什么地方出了問題(也可能是教他們如何修復它)。
GitHub(https://github.com/gisderdube/graceful-error-handling) 上提供了一個完整的樣例項目。
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 作為路由框架。讓我們考慮下最有效的錯誤處理結構。我們希望:
構建一個自定義 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 種類型:
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,我對它非常滿意。
1.專門在瀏覽器編譯與執行的編程語言
2.幫助瀏覽器解決用戶提出簡單需求
3.基于面向對象采用弱類型語法風格實現
老杜講解的JavaScript教程,內容涵蓋了JavaScript核心語法、JavaScript內置支持類、JavaScript調試、JavaScript DOM編程、JavaScript BOM編程、大量前端小案例、JavaScript事件處理、JavaScript對象、繼承、JSON等知識點。
接下來說的JavaScript學習內容均與下邊的javaweb學習教程相結合
http://www.bjpowernode.com/?chaijavaweb
1.在HTML文件中<script>內部進行命令書寫
2. 在js文件中書寫命令格式【推薦】
1.變量聲明方式: JavaScript弱類型編程語言,因此禁止使用具體數據類型修飾變量
2.變量賦值方式
3.變量名命名規則
1)變量名只能存在字母,數字,下劃線, 美元符號($)
2) 變量名不能以數字為開始
3) 不能使用JavaScript關鍵字作為變量名 var var=10; error
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中根據變量賦值內容判斷變量的數據類型
1. undefined:javascript中變量沒有賦值時,其默認值都是undefined。
此時變量數據類型也是undefined
2.NaN: 表示非法數字。此時變量數據類型是number
3.Infinity:表示一個無窮大數字.此時變量數據類型是number
4.null:表示當前對象指向一個空內存,但是空內存不能存儲數據
此時對象數據類型是object
1.與Java控制語句和循環語句語法完全一致
2.控制語句 if ,switch
3.循環語句 for while,do..while
1.JavaScript中數組相當于Java中List集合
2.JavaScript中數組一次可以存放任意類型數據
3.JavaScript中數組控制內存個數可以隨意改變
1.函數聲明格式
function 函數名(形參名,形參名){
命令行;
命令行;
eturn 返回值
}
1) 函數聲明時,必須使用function修飾進行修飾
2) 函數聲明時,禁止指定返回數據類型
3)函數聲明時, 形參只能指定名稱,但是不能指定數據類型,也不能使用var進行修飾
4)函數聲明時,可以通過return指定返回數據。
2.函數調用:
對象.函數(實參)
***window對象中屬性和函數在調用時,可以不寫window
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對象
1.根據標簽Id屬性定位關聯的DOM對象
var domObj=document.getElementById("id屬性")
2.根據標簽name屬性定位關聯的DOM對象
var domArray=document.getElementsByName("name屬性")
3.根據標簽類型定位關聯的DOM對象
var domArray=document.getElementsByTagName("標簽類型")
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這個關鍵字(Objective-C中使用的是self),但是JavaScript中的this和常見的面向對象語言中的this不太一樣:
使用this有什么意義呢?下面的代碼中,我們通過對象字面量創建出來一個對象,當我們調用對象的方法時,希望將對象的名稱一起進行打印。
如果沒有this,那么我們的代碼會是下面的寫法:
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來進行優化:
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的場景到處都是,這也是為什么它不容易理解的原因。
我們先說一個最簡單的,this在全局作用域下指向什么?
console.log(this); // window
var name="why";
console.log(this.name); // why
console.log(window.name); // why
但是,開發中很少直接在全局作用域下去使用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"}對象
上面的案例可以給我們什么樣的啟示呢?
那么this到底是怎么樣的綁定規則呢?一起來學習一下吧
我們現在已經知道this無非就是在函數調用時被綁定的一個對象,我們就需要知道它在不同的場景下的綁定規則即可。
什么情況下使用默認綁定呢?獨立函數調用。
案例一:普通函數調用
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);
我們對案例進行一些修改,考慮一下打印結果是否會發生變化:
function foo(func) {
func()
}
var obj={
name: "why",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
另外一種比較常見的調用方式是通過某個對象進行調用的:
案例一:通過對象調用函數
function foo() {
console.log(this); // obj對象
}
var obj={
name: "why",
foo: foo
}
obj.foo();
案例二:案例一的變化
function foo() {
console.log(this); // obj對象
}
var obj1={
name: "obj1",
foo: foo
}
var obj2={
name: "obj2",
obj1: obj1
}
obj2.obj1.foo();
案例三:隱式丟失
function foo() {
console.log(this);
}
var obj1={
name: "obj1",
foo: foo
}
// 講obj1的foo賦值給bar
var bar=obj1.foo;
bar();
隱式綁定有一個前提條件:
如果我們不希望在 對象內部 包含這個函數的引用,同時又希望在這個對象上進行強制調用,該怎么做呢?
因為上面的過程,我們明確地綁定了this指向的對象,所以稱之為 顯示綁定。
通過call或者apply綁定this對象
function foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number對象,存放時123
如果我們希望一個函數總是顯示的綁定到一個對象上,可以怎么做呢?
方案一:自己手寫一個輔助函數(了解)
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對象
有些時候,我們會調用一些JavaScript的內置函數,或者一些第三方庫中的內置函數。
案例一:setTimeout
setTimeout(function() {
console.log(this); // window
}, 1000);
為什么這里是window呢?
案例二:數組的forEach
數組有一個高階函數forEach,用于函數的遍歷:
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>
獲取元素節點,并且監聽點擊:
var box=document.querySelector(".box");
box.onclick=function() {
console.log(this); // box對象
}
所以傳入到內置函數的回調函數this如何確定呢?
JavaScript中的函數可以當做一個類的構造函數來使用,也就是使用new關鍵字。
使用new關鍵字來調用函數時,會執行如下的操作:
// 創建Person
function Person(name) {
console.log(this); // Person {}
this.name=name; // Person {name: "why"}
}
var p=new Person("why");
console.log(p);
學習了四條規則,接下來開發中我們只需要去查找函數的調用應用了哪條規則即可,但是如果一個函數調用位置應用了多條規則,優先級誰更高呢?
1.默認規則的優先級最低
毫無疑問,默認規則的優先級是最低的,因為存在其他規則時,就會通過其他規則的方式來綁定this
2.顯示綁定優先級高于隱式綁定
顯示綁定和隱式綁定哪一個優先級更高呢?這個我們可以測試一下:
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綁定優先級高于隱式綁定
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后的函數同時使用呢?可以
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綁定
優先級總結:
我們講到的規則已經足以應付平時的開發,但是總有一些語法,超出了我們的規則之外。(神話故事和動漫中總是有類似這樣的人物)
如果在顯示綁定中,我們傳入一個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
另外一種情況,創建一個函數的 間接引用,這種情況使用默認綁定規則。
我們先來看下面的案例結果是什么?
var num1=100;
var num2=0;
var result=(num2=num1);
console.log(result); // 100
我們來下面的函數賦值結果:
function foo() {
console.log(this);
}
var obj1={
name: "obj1",
foo: foo
};
var obj2={
name: "obj2"
}
obj1.foo(); // obj1對象
(obj2.foo=obj1.foo)(); // window
在ES6中新增一個非常好用的函數類型:箭頭函數
箭頭函數不使用this的四種標準規則(也就是不綁定this),而是根據外層作用域來決定this。
我們來看一個模擬網絡請求的案例:
var obj={
data: [],
getData: function() {
var _this=this;
setTimeout(function() {
// 模擬獲取到的數據
var res=["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
上面的代碼在ES6之前是我們最常用的方式,從ES6開始,我們會使用箭頭函數:
var obj={
data: [],
getData: function() {
setTimeout(()=> {
// 模擬獲取到的數據
var res=["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
思考:如果getData也是一個箭頭函數,那么setTimeout中的回調函數中的this指向誰呢?
var obj={
data: [],
getData: ()=> {
setTimeout(()=> {
console.log(this); // window
}, 1000);
}
}
obj.getData();
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
}
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);
下面是代碼解析:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。