做網(wǎng)站_你的出路在哪里創(chuàng)建自己的網(wǎng)站
什么是箭頭函數(shù)?
箭頭函數(shù)是指通過箭頭函數(shù)表達式創(chuàng)建的函數(shù),是匿名函數(shù)。
箭頭函數(shù)表達式的語法更簡潔,但語義有差異,所以用法上也有一些限制。盡管如此,箭頭函數(shù)依舊被廣泛運用在需要執(zhí)行“小函數(shù)”的場景。
箭頭函數(shù)表達式的基本語法:
/* 函數(shù)體只有一個語句 */
() => 單個語句
單個參數(shù) => 單個語句
(單個參數(shù)) => 單個語句
(參數(shù)1, 參數(shù)2) => 單個語句
let f = ()=>console.log(111)
f() === undefined // true/* 函數(shù)體有多個語句,使用{} */
() => { 多個語句 }
單個參數(shù) => { 多個語句 }
(單個參數(shù)) => { 多個語句 }
(參數(shù)1, 參數(shù)2) => { 多個語句 }
let g = ()=>{}
箭頭函數(shù)表達式的語法說明:
- 只有一個參數(shù)時,
()
可以省略; - 函數(shù)體只有單個語句,
{}
可以省略,返回值就是該語句/表達式的值; - 函數(shù)體有多個語句,必須使用
{}
包圍; - 語法上,返回一個對象字面量時,加
()
; - 箭頭是復合符號,不要空格或換行;
- 參數(shù)列表和箭頭之間不要換行;
- 箭頭
=>
的優(yōu)先級不高,在符合運算中盡量用()
包圍。
let f = ()=>{ a:1 } // 不報錯,a被當成一個標簽,執(zhí)行了表達式 “1”,返回 undefined
let f = ()=>{ a: 1, b: 2 } // 報錯
let f = ()=>{ a: 1; b: 2 } // 不報錯,a,b都時標簽
let f = ()=>( {a:1, b:2} ) // 不報錯,()改變了優(yōu)先級let g = () = > {} // 報錯
let g = () // 報錯=> {}let x = f || ( ()=>{} )
箭頭函數(shù)的限制:
- 箭頭函數(shù)不綁定
this
,會捕獲其聲明所在上下文的this
,且call/apply/bind
方法都不會改變其指向; - 不能用作構造函數(shù),不能使用
new
調(diào)用,會引發(fā)類型錯誤,函數(shù)體內(nèi)也無法訪問new.target
; - 不能使用
yield
,不能用做生成器函數(shù); - 沒有
arguments
對象,可以使用剩余參數(shù)取而代之; - 沒有原型對象
prototype
這個屬性。
this 的綁定問題
箭頭函數(shù)沒有獨立的 this
,下面將依次總結其如何綁定上下文的 this
、避免用作方法等限制問題。
綁定上下文的 this
箭頭函數(shù)捕獲聲明所在上下文的 this
并綁定,考慮捕獲綁定順序如下:
- 外層函數(shù):內(nèi)部的箭頭函數(shù)和外部(臨近)的普通函數(shù)有相同的
this
綁定; - 類:類具有封閉獨立的
this
上下文,箭頭函數(shù)作為方法時,將自動綁定實例的this
;本質(zhì)就像閉包,有獨立的變量環(huán)境,不會因為屬性解構等操作改變其綁定。 - 全局:當以上不符合,一般和全局
globalThis
綁定。
由于箭頭函數(shù)會綁定外部函數(shù)的 t h i s this this,所以在一些需要訪問外部屬性時常用,諸如函數(shù) setTimeout/setInterval
、方法 forEach/map
等。下面一個例子中:
showGroup2
內(nèi)部的箭頭函數(shù)綁定的是外部函數(shù)的this
,指向 w b 對象 wb對象 wb對象,所以能夠訪問其屬性group
;showGroup3
綁定的是全局(globalThis
),指向 全局對象 全局對象 全局對象,訪問的是全局屬性;convert
內(nèi)部的箭頭函數(shù)綁定的是 this.data 的this
,指向 w b 對象 wb對象 wb對象,能夠正確訪問屬性group
;convert2
內(nèi)部的箭頭函數(shù)綁定的也是 this.data 的this
,但外部也是箭頭函數(shù),指向 全局對象 全局對象 全局對象,將會報錯或者得到錯誤的結果,所以盡量不使用箭頭函數(shù)作為方法。
const wb = {group: '第一組',data: [1, 2, 3, 4],showGroup: function () { console.log(this.group); },showGroup2: function () { (() => { console.log(this.group) })(); },showGroup3: () => { console.log(this.group) },convert: function() {let res = new Array;this.data.forEach( (elem)=>{ res.push(this.group + ':' + elem); } );return res;},convert2: () => {let res = new Array;// console.log(this == globalThis), // true// console.log(this.data); // undefined/其他this.data.forEach( (elem)=>{ res.push(this.group + ':' + elem); } );return res;},
}
類的封閉性使得箭頭函數(shù)與實例自動綁定,每創(chuàng)建一個實例,都分配一個閉包,和實例環(huán)境自動綁定。下面一個例子中:
- 箭頭函數(shù)
fa
綁定具體的實例對象,解構賦值不會改變 this 指向; - 普通方法
fd
在構造時綁定自身,解構賦值也不會改變 this 指向; - 另外的
fb/fc
作為普通方法,在構造時沒有綁定自身,解構賦值會改變 this 指向。
class C {constructor() { this.fd = this.fd.bind(this) };a = 1;fa = ()=>{ console.log(this.a); };fb = function () { console.log(this.a); };fc () { console.log(this.a); };fd = function () { console.log(this.a); };
}let c = new C();
c.fa(); // 1
let { fa, fb, fc, fd } = c;
fa(); // 1
fb(); // 報錯,屬性未定義
fc(); // 報錯,屬性未定義
fd(); // 1
call、bind 和 apply 不會改變箭頭函數(shù)的 this 指向
箭頭函數(shù)表達式是執(zhí)行完創(chuàng)建的,所以 this 綁定聲明定義所在上下文的 this。call
、bind
和 apply
方法都不會改變其指向。下面一個例子中:
- 普通函數(shù)的
this
綁定,可以根據(jù)需求被call/apply/bind
改變,指向 o o o 對象; - 箭頭函數(shù)的
this
綁定,不可以被call/apply/bind
改變,依舊指向全局對象。
const o = { a: 10 }
globalThis.a = 100;const add = function (m, n) { return this.a + m + n; }
const add2 = n => this.a + m + n;add.call(o, 2, 3) // 15
add.apply(o, [2, 3]) // 15
let boundedAdd = add.bind(o);
boundedAdd(2, 3) // 15add2.call(o, 2, 3) // 105
add2.apply(o, [2, 3]) // 105
let boundedAdd2 = add.bind(o);
boundedAdd2(2, 3) // 105
在 setTimeout 中使用箭頭函數(shù)
setTimeout
函數(shù),使用一定封閉環(huán)境內(nèi)的屬性時,常用箭頭函數(shù)作為回調(diào)函數(shù)。下面一個例子中:
loading
中箭頭函數(shù),綁定外部方法loading
的this
,指向 o 對象 o對象 o對象;loading2
中由function
引導的聲明式函數(shù),將綁定全局(globalThis
),指向 全局對象 全局對象 全局對象;- setTimeout 不會影響內(nèi)部箭頭函數(shù)的 this 綁定,也不會影響內(nèi)部普通函數(shù)的 this 綁定。
const o = {n: 0,loading () {setTimeout(() => {console.log(++this.n); // 箭頭函數(shù),this綁定聲明所在上下文}, 1000)},loading2 () {setTimeout(function () { console.log(++this.n); // 聲明式函數(shù),this綁定提升到全局}, 1000)},
}o.loading() // 1秒后,控制臺輸出 1
o.loading() // 再1秒后,控制臺輸出 2
o.loading2() // 再1秒后,控制臺輸出 NaN 或其他
更一般的,在需要控制函數(shù)內(nèi) this
綁定指定區(qū)域時,采用箭頭函數(shù),一個例子如下:
const o = {n: 1,f() {let fn = function () { console.log(++this.n); }; // 聲明式函數(shù),this綁定提升到全局fn();},g() {let gn = () => { console.log(++this.n); }; // 箭頭函數(shù),this綁定聲明所在上下文gn();},
}o.g() // 2
o.f() // NaN
不僅如此,箭頭函數(shù)常出現(xiàn)在 promise
鏈中?;蛘哒f,箭頭函數(shù)經(jīng)常作為一個回調(diào)函數(shù)出現(xiàn)。
沒有 arguments 對象
箭頭函數(shù)沒有自己的 arguments
對象。類似 this
的綁定,箭頭函數(shù)會使用上下文 / 外部的 arguments
。下面一個例子中:
- 內(nèi)部箭頭函數(shù)與外部函數(shù)
f
的參數(shù)隱式綁定; - 即
arguments[0]
為f
的第一個參數(shù)n
。
function f(n) {let g = () => arguments[0] + n; // f 的隱式參數(shù)綁定。arguments[0] 為 f 的第一個參數(shù) nreturn g();
}f(3); // 3 + 3 = 6
JS 類沒有默認的 arguments
字段,下面的例子中:
- 全局箭頭函數(shù)可以訪問全局變量
arguments
(如果存在); - 類可以自定義
arguments
字段并進行訪問(使用this
指定)。
globalThis.arguments = [1, 2, 3]
let f = () => { console.log(arguments[0]); }
class C {constructor(a, ...r) { this.arguments = [a, ...r]; };// arguments = [1, 2, 3];g = () => { console.log(this.arguments[0]); };
}f() // 1
(new C(10, 1, 2)).g() // 10
由于箭頭函數(shù)的以上特性,在需要指定作用域的 this/arguments
時(setTimeout/Promise/閉包/裝飾器/...
),能夠起到簡化的作用。下面一個裝飾器的例子中:
- 內(nèi)部箭頭函數(shù)可以對外部函數(shù)的
this
和arguments
訪問,
function sayHi(name) { console.log(`Hello, ${name}!`); }function decorator1(fn, ms) {return function () {setTimeout(() => fn.apply(this, arguments), ms); }
}function decorator2(fn, ms) {return function (...args) {let nt = this;setTimeout(function() { return f.apply(nt, args); }, ms);}
}let f = decorator1(sayHi, 1000);
let g = decorator2(sayHi, 1000);
f('Lily');
g('Jack');
在大多情況下,使用剩余參比使用 arguments
是更好的選擇。
不能用作構造函數(shù)
- 箭頭函數(shù)不能用作構造函數(shù),當使用
new
調(diào)用時會出錯。
const F = () => {this.a = 10;
}let f = new F() // 報錯,不能作為構造器
- 更不允許在箭頭函數(shù)中使用
new.target
。
const G = (v) => {if (!new.target) return new G(v); // 報錯,不允許在箭頭函數(shù)中出現(xiàn) new.targetthis.k = v;
}