常見函數錯誤引發的思考.

来源:http://www.cnblogs.com/zhuanzhuanfe/archive/2017/12/02/7953985.html
-Advertisement-
Play Games

今天在寫代碼的時候,我犯了一個很low的錯誤,廢話不多說,直接上代碼: 大家看到之後,第一反應肯定會認為是個語法錯誤,可是自己仔細想想,這是什麼原因?似乎還不能解釋清楚,好奇寶寶模式立即啟動,經過查閱相關資料得到了答案,接下來我們一起來探討下其中的原理。 疑惑解答 大家有沒有考慮過為什麼上面這種寫法 ...


今天在寫代碼的時候,我犯了一個很low的錯誤,廢話不多說,直接上代碼:

1 function () {
2   console.log('hello world');
3 }()

 

大家看到之後,第一反應肯定會認為是個語法錯誤,可是自己仔細想想,這是什麼原因?似乎還不能解釋清楚,好奇寶寶模式立即啟動,經過查閱相關資料得到了答案,接下來我們一起來探討下其中的原理。

疑惑解答

大家有沒有考慮過為什麼上面這種寫法會報錯?

原來,瀏覽器遇到function關鍵字的時候會認為這是一個函數聲明,函數聲明必須包括:關鍵字function、函數名、形參、函數體。在解析上面代碼的時候,解析器發現沒有出現函數名而直接出現了(),瀏覽器便會認為這種定義不符合規範,所以就報錯了。

既然是缺少函數名,如果我們給它添加函數名,是不是會正確調用?

1 function hello () {
2     console.log('hello world');
3 }()

 

讓我們靜靜等待奇跡出現!

哎,瀏覽器在解析的時候怎麼又報錯了?

其實是函數聲明的緣故,也就是說通過聲明的函數會被提升到其他代碼的前面。提升之後應該是這樣了:

1 function hello () {
2     console.log('hello world');
3 }
4 ...// do something 
5 (); // 咦?這是什麼東東?

 

解析器對此也很茫然,不知道該按照什麼標準去解析,只能告訴你寫的不夠規範了。 大家看一下下麵的這種用法,會不會感覺很熟悉?

1 var hello = function () {
2     console.log('hello world');
3 }
4 hello();

 

試想一下,如果用()把上面的函數名hello包裹起來,會發生什麼

1 var hello = function () {
2     console.log('hello world');
3 }
4 (hello)();

 

Bingo,瀏覽器輸出了“hello world”,這種寫法是不是特別像數學中的結合律。

繼續對上面的函數調用做一些改變,如果把函數名hello替換成匿名函數,猜想應該也可以調用成功。

1 (function () {
2   console.log('hello world');
3 })();

 

果然達到了預期的結果,調用成功了!這是為什麼?

因為用()把匿名函數包裹起來,解析器便會認為這是一個函數表達式,函數表達式的後面添加()顯然是可以正確執行的。此外,除了()之外,也可以使用!等常見的一元運算符來執行匿名函數。

1 !function() {
2   console.log('hello world');
3 }();

 

上面這段代碼會輸出“hello world”。

Tips:

說到函數定義,需要註意函數聲明和函數表達式兩者的區別(大神可以跳過喔~):前者有個函數聲明提升的過程,在代碼預解析的時候,會把函數聲明提升到代碼的頂部,所以在聲明函數位置之前調用函數並不會出錯;而後者只是進行變數提升,因此,解析器只有執行函數表達式的代碼之後才可以調用該函數。


說到這裡,想起了一個老生常談的問題,函數總是在特定的作用域中執行,函數中this的指向是不是也把好多同學弄的一知半解呢?讓我們來繼續研究一下~~~

走進函數的this

一般情況下,哪個對象調用函數(方法),則this便指向哪個對象。

通過一個例子來說明該如何確定this的指向。

 1 var obj= {
 2     number: 1,
 3     getOwnNumber: function () {
 4         var number = 2;
 5         return this.number;
 6     },
 7     getNumber: function () {
 8         var number = 3;
 9         return function () {
10             var number = 4;
11             return this.number;
12         };
13     }
14 }
15 console.log(obj.getOwnNumber());
16 console.log(obj.getNumber()());

 

大家猜一下,第一個輸出結果是多少?大家不要猶豫,答案是1,就這麼簡單!

很明顯,是obj調用的getOwnNumber方法,而obj對象內部定義的number值為1,所以第一個輸出結果理所應當是1。

問題來了,第二個輸出結果是多少?1?2?3?4?

還是同樣的方法,我們找一找this到底指向哪個對象。obj.getNumber()運行結果是一個匿名函數,然後再執行匿名函數。我們可以把這個過程拆分一下,如下:

1 var fun = obj.getNumber() // 返回一個匿名函數
2 fun(); 

 

經過分解之後,很明顯,函數是在全局作用域中調用的,所以此處的this理應指向window對象,window對象中沒有定義number,所以結果是undefined。

假想一下,當代碼複雜之後,確定this的指向是不是更加麻煩?別急,ES6規範提供了箭頭函數的語法,可以幫助我們解決這個問題。箭頭函數是何方神聖,該怎麼定義呢?箭頭函數的基本寫法如下:

1    const hello = () => {
2         console.log('hello');
3     }

 

我們嘗試使用箭頭函數來改造obj對象的getNumber方法:

 1 const obj= {
 2     number: 1,
 3     getOwnNumber() {
 4         const number = 2;
 5         return this.number;
 6     },
 7     getNumber() {
 8         const number = 3;
 9         return () => {
10             const number = 4;
11             return this.number;
12         };
13     }
14 }
15 console.log(obj.getOwnNumber());
16 console.log(obj.getNumber()());

 

運行之後會發現兩個輸出結果都是1。這是為什麼?

因為箭頭函數內部的this是指向定義函數時所在的對象,在上面的代碼中,this指向了obj對象,所以輸出1。有了箭頭函數之後,我們不再需要通過變數保存對象的this指針或者通過bind方法改變this的指針,輕鬆實現我們預期的效果。

箭頭函數和普通函數的主要區別:(摘抄自阮一峰大神的《ES6標準入門》):

  • 箭頭函數體內的this就是定義時所在的對象,而不是使用時所在的對象。
  • 箭頭函數不可以當做構造函數。也就是說,不可以使用new命令,否則會拋出一個錯誤。
  • 不可以使用arguments對象,該對象在函數體內不存在。可以使用rest參數代替。
  • 不可以使用yield命令,因此箭頭函數不能用作Generator函數

但是箭頭函數也不是可以在任何場景下都能使用,如果我們要改變this的指向該怎麼辦?在ES6出現之前,我們可以使用call、apply和bind方法來改變函數中this的指向,ES7提出了使用雙冒號(::)的運算符,使得我們可以通過這個運算符來改變箭頭函數中this的指向。


除了箭頭函數之外,ES6又對之前的函數做了哪些優化?

構造函數

在其他語言中可以通過類實現面向對象的功能,但是在ES6規範公佈之前,JavaScript中並沒有類的概念,面向對象的語法只能通過構造函數的方式來實現。

1 function Zhuanzhuan (name) {
2     this.name = name;
3 }
4 Zhuanzhuan.prototype.say = function () {
5    console.log('hi~I am zhuanzhuan');
6 }

 

要想實例化這個“類”,必須使用new關鍵字

1 var zhuanzhuan = new Zhuanzhuan('zhuanzhuan');

 

在ES6之前,使用構造函數來創建對象,閱讀起來並不是很清晰。ES6提供的class語法跟其他面向對象的語言語法較為接近。 使用ES6的語法來改寫一下上面用構造函數定義的“類”。

1 class Zhuanzhuan {
2     constructor (name) {
3         this.name = name;
4     }
5     say () {
6         console.log('hi~I am zhuanzhuan');
7     }
8 }
9 let zhuanzhuan = new Zhuanzhuan('zhuanzhuan');

 

看起來是不是特別像C++語言中的面向對象風格呢,出現class之後,媽媽再也不用擔心我寫的類不夠清晰了~~~

JavaScript語言中的函數內容相當豐富,需要我們不斷去通過實踐去加深理解。騏驥一躍,不能十步,駑馬十駕,功在不捨。多總結,多思考。大家如果有好的想法,可以相互交流和分享,每天進步一點點,不斷提高自己的專業技能。

這就是我因為一個小錯誤,引發的思考。

 

如果你喜歡我們的文章,關註我們的公眾號和我們互動吧。


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

-Advertisement-
Play Games
更多相關文章
  • 你可能會疑惑為什麼我們使用6位數來表示一種顏色而不是只用一位或二位,答案是使用6位數可提供給我們巨大數量的顏色變化。 會有多少種可能的顏色?16 個值(0~F)和 6 個位置意味著我們有 16 的 6 次方,或者說超過 1600 萬種可能的顏色。 Hex code 遵循 red-green-blue ...
  • 很多情況下,你會使用 CSS 庫,這些庫可能會意外覆蓋掉你自己的 CSS。所以當你需要確保某元素具有指定的 CSS 時,你可以使用 !important。 Hello World!是粉色的 ...
  • 1、js的簡介 (1)js是什麼? js是可以嵌入到html中,是基於對象和事件驅動的腳本語言。 特點: 交互性 安全性:js不能訪問本地磁碟 跨平臺:瀏覽器中都具備js解析器 (2)js能做什麼 js能動態的修改(增刪)html和css的代碼 能動態的校驗數據 (3)js的歷史及組成 BOM(瀏覽 ...
  • 內容為整理博主文章: "https://juejin.im/user/58870f04128fe10065efc8d9/article" 個人覺得他對Operators的解說較容易理解和全面,顧把它們整理在一起,也方面查找。 Operators: Observable 的 Operators 是實例 ...
  • s中的this總是讓人,是js眾所周知的坑之一。 總結一下,this的坑分為5種。 1.全局代碼中的this。 alert(this);//window 全局範圍內this將會全局,在瀏覽器window 2.作為單純函數調用 這裡this指向全局對象,就是window。在嚴格模式中,則undefie ...
  • 一般認為:嚴格模式下this不允許指向全局對象。 如:http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html 需要說明的是:本身指向全局的this是沒有問題的。 示例代碼: 控制台輸出為window對象(全局對象): 嚴格 ...
  • 查找基本分類如下: 1. 線性表的查找 順序查找 折半查找 分塊查找 2. 樹表的查找 二叉排序樹 平衡二叉樹 B樹 B+樹 3. 散列表的查找 今天介紹 二叉排序樹 。 二叉排序樹 ( Binary Sort Tree ) 又稱為 二叉查找樹 ,它是一種對排序和查找都很有用的特殊二叉樹。 1. 二 ...
  • 常用無返回值的事件 onabort圖像載入被中斷 onblur 元素失去焦點 onfocus 元素獲得焦點 onreset 重置按鈕被點擊 onselect 文本被選中 onsubmit 確認按鈕被點擊 onchange 域的內用被改變 onclick 當用戶點擊某個對象時調用的事件 ondblcl ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...