摘要:第二種情況是箭頭函數的如果指向普通函數它的繼承于該普通函數。箭頭函數的指向全局,使用會報未聲明的錯誤。
箭頭函數是ES6的API,相信很多人都知道,因為其語法上相對于普通函數更簡潔,深受大家的喜愛。就是這種我們日常開發中一直在使用的API,大部分同學卻對它的了解程度還是不夠深...
普通函數和箭頭函數的區別: 箭頭函數的this指向規則: 1. 箭頭函數沒有prototype(原型),所以箭頭函數本身沒有thislet a = () =>{}; console.log(a.prototype); // undefined2. 箭頭函數的this指向在定義的時候繼承自外層第一個普通函數的this。
下面栗子中在一個函數中定義箭頭函數,然后在另一個函數中執行箭頭函數。
let a, barObj = { msg: "bar的this指向" }; fooObj = { msg: "foo的this指向" }; bar.call(barObj); // 將bar的this指向barObj foo.call(fooObj); // 將foo的this指向fooObj function foo() { a(); // 結果:{ msg: "bar的this指向" } } function bar() { a = () => { console.log(this, "this指向定義的時候外層第一個普通函數"); // }; // 在bar中定義 this繼承于bar函數的this指向 }
從上面栗子中可以得出兩點
箭頭函數的this指向定義時所在的外層第一個普通函數,跟使用位置沒有關系。
被繼承的普通函數的this指向改變,箭頭函數的this指向會跟著改變
3. 不能直接修改箭頭函數的this指向上個栗子中的foo函數修改一下,嘗試直接修改箭頭函數的this指向。
let fnObj = { msg: "嘗試直接修改箭頭函數的this指向" }; function foo() { a.call(fnObj); // 結果:{ msg: "bar的this指向" } }
很明顯,call顯示綁定this指向失敗了,包括aaply、bind都一樣。
它們(call、aaply、bind)會默認忽略第一個參數,但是可以正常傳參。
然后我又通過隱式綁定來嘗試同樣也失敗了,new 調用會報錯,這個稍后再說。
SO,箭頭函數不能直接修改它的this指向。
幸運的是,我們可以通過間接的形式來修改箭頭函數的指向:
去修改被繼承的普通函數的this指向,然后箭頭函數的this指向也會跟著改變,這在上一個栗子中有演示。
bar.call(barObj); // 將bar普通函數的this指向barObj 然后內部的箭頭函數也會指向barObj4. 箭頭函數外層沒有普通函數,嚴格模式和非嚴格模式下它的this都會指向window(全局對象)
唔,這個問題實際上是面試官提出來的,當時我認為的箭頭函數規則就是:箭頭函數的this指向繼承自外層第一個普通函數的this,現在看來真是不嚴謹(少說一個定義的時候),要是面試官問我:定義和執行不在同一個普通函數中,它又指向哪里,肯定歇菜...
既然箭頭函數的this指向在定義的時候繼承自外層第一個普通函數的this,那么:
當箭頭函數外層沒有普通函數,它的this會指向哪里?
這里跟我之前寫的this綁定規則不太一樣(不懂的可以點進去看一下),普通函數的默認綁定規則是:
在非嚴格模式下,默認綁定的this指向全局對象,嚴格模式下this指向undefined
如果箭頭函數外層沒有普通函數繼承,它this指向的規則:
經過測試,箭頭函數在全局作用域下,嚴格模式和非嚴格模式下它的this都會指向window(全局對象)。
Tip:測試的時候發現嚴格模式在中途聲明無效,必須在全局/函數的開頭聲明才會生效:
a = 1; "use strict"; // 嚴格模式無效 必須在一開始就聲明嚴格模式 b = 2; // 不報錯箭頭函數的 箭頭函數的arguments 箭頭函數的this指向全局,使用arguments會報未聲明的錯誤
如果箭頭函數的this指向window(全局對象)使用arguments會報錯,未聲明arguments。
let b = () => { console.log(arguments); }; b(1, 2, 3, 4); // Uncaught ReferenceError: arguments is not defined
PS:如果你聲明了一個全局變量為arguments,那就不會報錯了,但是你為什么要這么做呢?
箭頭函數的this指向普通函數時,它的argumens繼承于該普通函數上面是第一種情況:箭頭函數的this指向全局對象,會報arguments未聲明的錯誤。
第二種情況是:箭頭函數的this如果指向普通函數,它的argumens繼承于該普通函數。
function bar() { console.log(arguments); // ["外層第二個普通函數的參數"] bb("外層第一個普通函數的參數"); function bb() { console.log(arguments); // ["外層第一個普通函數的參數"] let a = () => { console.log(arguments, "arguments繼承this指向的那個普通函數"); // ["外層第一個普通函數的參數"] }; a("箭頭函數的參數"); // this指向bb } } bar("外層第二個普通函數的參數");
那么應該如何來獲取箭頭函數不定數量的參數呢?答案是:ES6的rest參數(...擴展符)
rest參數獲取函數的多余參數這是ES6的API,用于獲取函數不定數量的參數數組,這個API是用來替代arguments的,API用法如下:
let a = (first, ...abc) => { console.log(first, abc); // 1 [2, 3, 4] }; a(1, 2, 3, 4);
上面的栗子展示了,獲取函數除第一個確定的參數,以及用一個變量接收其他剩余參數的示例。
也可以直接接收函數的所有參數,rest參數的用法相對于arguments的優點:
箭頭函數和普通函數都可以使用。
更加靈活,接收參數的數量完全自定義。
可讀性更好
參數都是在函數括號中定義的,不會突然出現一個arguments,以前剛見到的時候,真的好奇怪了!
rest是一個真正的數組,可以使用數組的API。
因為arguments是一個類數組的對象,有些人以為它是真正的數組,所以會出現以下場景:
arguments.push(0); // arguments.push is not a function
如上,如果我們需要使用數組的API,需要使用擴展符/Array.from來將它轉換成真正的數組:
arguments = [...arguments]; 或者 :arguments = Array.from(arguments);
rest參數有兩點需要注意:
rest必須是函數的最后一位參數:
let a = (first, ...rest, three) => { console.log(first, rest,three); // 報錯:Rest parameter must be last formal parameter }; a(1, 2, 3, 4);
函數的length屬性,不包括 rest 參數
(function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
擴展運算符還可以用于數組,這里是阮一峰老師的文檔
PS:感覺這里寫多了,但比較喜歡把一個知識點講清楚...
使用new調用箭頭函數會報錯無論箭頭函數的thsi指向哪里,使用new調用箭頭函數都會報錯,因為箭頭函數沒有constructor
let a = () => {}; let b = new a(); // a is not a constructor箭頭函數不支持new.target:
new.target是ES6新引入的屬性,普通函數如果通過new調用,new.target會返回該函數的引用。
此屬性主要:用于確定構造函數是否為new調用的。
箭頭函數的this指向全局對象,在箭頭函數中使用箭頭函數會報錯
let a = () => { console.log(new.target); // 報錯:new.target 不允許在這里使用 }; a();
箭頭函數的this指向普通函數,它的new.target就是指向該普通函數的引用。
new bb(); function bb() { let a = () => { console.log(new.target); // 指向函數bb:function bb(){...} }; a(); }
更多關于new.target可以看一下阮一峰老師關于這部分的解釋。
箭頭函數不支持重命名函數參數,普通函數的函數參數支持重命名如下示例,普通函數的函數參數支持重命名,后面出現的會覆蓋前面的,箭頭函數會拋出錯誤:
function func1(a, a) { console.log(a, arguments); // 2 [1,2] } var func2 = (a,a) => { console.log(a); // 報錯:在此上下文中不允許重復參數名稱 }; func1(1, 2); func2(1, 2);箭頭函數相對于普通函數語法更簡潔優雅:
講道理,語法上的不同,也屬與它們兩個的區別!
箭頭函數都是匿名函數,并且都不用寫function
只有一個參數的時候可以省略括號:
var f = a => a; // 傳入a 返回a
函數只有一條語句時可以省略{}和return
var f = (a,b,c) => a; // 傳入a,b,c 返回a
簡化回調函數,讓你的回調函數更優雅:
[1,2,3].map(function (x) { return x * x; }); // 普通函數寫法 [1,2,3].map(x => x * x); // 箭頭函數只需要一行箭頭函數的注意事項及不適用場景 箭頭函數的注意事項
一條語句返回對象字面量,需要加括號,或者直接寫成多條語句的return形式,
否則像func中演示的一樣,花括號會被解析為多條語句的花括號,不能正確解析
var func1 = () => { foo: 1 }; // 想返回一個對象,花括號被當成多條語句來解析,執行后返回undefined var func2 = () => ({foo: 1}); // 用圓括號是正確的寫法 var func2 = () => { return { foo: 1 // 更推薦直接當成多條語句的形式來寫,可讀性高 }; };
箭頭函數在參數和箭頭之間不能換行!
var func = () => 1; // 報錯: Unexpected token =>
箭頭函數的解析順序相對靠前
MDN: 雖然箭頭函數中的箭頭不是運算符,但箭頭函數具有與常規函數不同的特殊運算符優先級解析規則
let a = false || function() {}; // ok let b = false || () => {}; // Malformed arrow function parameter list let c = false || (() => {}); // ok箭頭函數不適用場景:
圍繞兩點:箭頭函數的this意外指向和代碼的可讀性。
定義字面量方法,this的意外指向。
因為箭頭函數的簡潔
const obj = { array: [1, 2, 3], sum: () => { // 根據上文學到的:外層沒有普通函數this會指向全局對象 return this.array.push("全局對象下沒有array,這里會報錯"); // 找不到push方法 } }; obj.sum();
上述栗子使用普通函數或者ES6中的方法簡寫的來定義方法,就沒有問題了:
// 這兩種寫法是等價的 sum() { return this.array.push("this指向obj"); } sum: function() { return this.array.push("this指向obj"); }
還有一種情況是給普通函數的原型定義方法的時候,通常會在普通函數的外部進行定義,比如說繼承/添加方法的時候。
這時候因為沒有在普通函數的內部進行定義,所以this會指向其他普通函數,或者全局對象上,導致bug!
回調函數的動態this
下文是一個修改dom文本的操作,因為this指向錯誤,導致修改失敗:
const button = document.getElementById("myButton"); button.addEventListener("click", () => { this.innerHTML = "Clicked button"; // this又指向了全局 });
相信你也知道了,改成普通函數就成了。
考慮代碼的可讀性,使用普通函數
函數體復雜:
具體表現就是箭頭函數中使用多個三元運算符號,就是不換行,非要在一行內寫完,非常惡心!
行數較多
函數內部有大量操作
文章內容小結: 普通函數和箭頭函數的區別:箭頭函數沒有prototype(原型),所以箭頭函數本身沒有this
箭頭函數的this在定義的時候繼承自外層第一個普通函數的this。
如果箭頭函數外層沒有普通函數,嚴格模式和非嚴格模式下它的this都會指向window(全局對象)
箭頭函數本身的this指向不能改變,但可以修改它要繼承的對象的this。
箭頭函數的this指向全局,使用arguments會報未聲明的錯誤。
箭頭函數的this指向普通函數時,它的argumens繼承于該普通函數
使用new調用箭頭函數會報錯,因為箭頭函數沒有constructor
箭頭函數不支持new.target
箭頭函數不支持重命名函數參數,普通函數的函數參數支持重命名
箭頭函數相對于普通函數語法更簡潔優雅
箭頭函數的注意事項及不適用場景箭頭函數的注意事項:
箭頭函數一條語句返回對象字面量,需要加括號
箭頭函數在參數和箭頭之間不能換行
箭頭函數的解析順序相對||靠前
不適用場景:箭頭函數的this意外指向和代碼的可讀性。
結語嘔心瀝血,可以說是很全了,反正第一次問到我的時候只能想到箭頭函數的this是繼承而來的,以及語法上的簡潔性,其他的我都不知道,希望這篇文章能夠幫助各位同學學到知識。
PS:目前找工作中,求大佬們內推,中高級前端,偏JS,Vue,上海楊浦。
博客、前端積累文檔、公眾號、GitHub、wx:OBkoro1、郵箱:obkoro1@foxmail.com
以上2019.03.22
參考資料:
MDN 箭頭函數
阮一峰-ES6入門
什么時候你不能使用箭頭函數?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102838.html
摘要:在這里,如果用箭頭函數,可以這樣改寫箭頭函數并沒有自己的,所以事件處理函數的調用者并不受影響。比如,在需要動態上下文的場景中,使用箭頭函數需要格外地小心,這些場景包括對象的方法原型方法事件的回調構造函數。 showImg(https://segmentfault.com/img/bVboce6?w=1304&h=734); 前言 年味兒漸散,收拾下心情,繼續敲代碼吧。 對于即將到來金三...
摘要:無關緊要的開頭最近因為一些事兒辭了剛剛找到的工作,處在待業狀態,去稍微的面了幾家公司,有大有小,有好有壞,發現大家問起來的一些的問題跟我想的不一樣,下來再去研究發現我說的還是有些缺陷,雖然意思是對的,但是表達的很奇怪,怪不得面試官會誤會,參 /*===無關緊要的開頭start===*/最近因為一些事兒辭了剛剛找到的工作,處在待業狀態,去稍微的面了幾家公司,有大有小,有好有壞,發現大家問...
摘要:顯然,箭頭函數是不能用來做構造函數,實際上會禁止你這么做,如果你這么做了,它就會拋出異常。換句話說,箭頭構造函數的執行并沒有任何意義,并且是有歧義的。 showImg(https://segmentfault.com/img/remote/1460000009180813); 共 2670 字,讀完需 5 分鐘。編譯自 Dmitri Pavlutin 的文章,對原文內容做了精簡和代碼風...
摘要:不可以當作構造函數,也就是說,不可以使用命令,否則會拋出一個錯誤。正是因為它沒有,所以也就不能用作構造函數。總結箭頭函數的永遠指向其上下文的,任何方法都改變不了其指向,如普通函數的指向調用它的那個對象 一、前言 首先看下直觀的區別 // 箭頭函數 let fun = () => { console.log(lalalala); } ...
閱讀 2340·2021-09-30 09:47
閱讀 2959·2019-08-30 11:05
閱讀 2534·2019-08-29 17:20
閱讀 1921·2019-08-29 13:01
閱讀 1727·2019-08-26 13:39
閱讀 1249·2019-08-26 13:26
閱讀 3210·2019-08-23 18:40
閱讀 1828·2019-08-23 17:09