JavaScript執行(三):你知道現在有多少種函式嗎?
函式
在ES2018中,函式已經是一個很複雜的體系了,我在這裡整體了一下。
1、普通函式:用function關鍵字定義的函式
function foo(){ // code }
2、箭頭函式:用=>運算子定義的函式
const foo = () => { // code }
3、方法:在class中定義的函式
class C { foo(){ //code } }
4、生成器函式,用function*定義的函式
function foo*(){ // code }
5、類:用class定義的類,實際上也是函式
class Foo { constructor(){ //code } }
6-8、非同步函式:普通函式、箭頭函式、生成器函式加上async關鍵字
async function foo(){ // code } const foo = async () => { // code } async function foo*(){ // code }
對於普通變數而言,這些函式沒有本質的區別,都遵循了“繼承定義時環境”的規則,他們的一個行為差異在於this關鍵字
this 關鍵字的行為
this 是執行上下文中很重要的一個組成部分。同一個函式呼叫方式不同,得到的this值也不同
function showThis(){ console.log(this); } var o = { showThis: showThis } showThis(); // global o.showThis(); // o
普通函式的this值由“呼叫它所使用的引用”決定,其中奧祕就在於:我們獲取函式的表示式,實際上返回的並非函式本身,而是一個Reference型別
Reference 型別由兩部分組成:一個物件和一個屬性值。
不難理解o.showThis產生的Reference型別,即由物件o和屬性“showThis”構成。
當做一些算術運算(或者其他運算時),Reference 型別會被解引用,即獲取真正的值(被引用的內容)來參與運算,而類似函式呼叫、delete 等操作,都需要用到Reference型別中的物件。
在這個例子中,Reference 型別中的物件被當作this值,傳入了執行函式時的上下文當中。
至此,我們對this的解釋已經非常清晰了:呼叫函式時使用的引用,決定了函式執行時刻的this值。
但是換一種方式:
const showThis = () => { console.log(this); } var o = { showThis: showThis } showThis(); // global o.showThis(); // global
我們看到,改成箭頭函式後,不論用什麼引用來調取它,都不影響this的值
class C { showThis() { console.log(this); } } var o = new C(); var showThis = o.showThis; showThis(); // undefined o.showThis(); // global
可以得出結論:生成器函式、非同步生成器函式和非同步普通函式跟普通函式行為是一致的,非同步箭頭函式與箭頭函式行為是一致的。
this關鍵字的機制
函式能夠弓用定義時的變數,如上文分析,函式也能記住定義時的this,因此,函式內部必定有一個機制來儲存這些資訊。
在JavaScript標準中,為函式規定了用來儲存定義時上下文的私有屬性[[Environment]。
當一個函式執行時,會建立一條新的執行環境記錄, 記錄的外層詞法環境(outer lexicalenvironment)會被設定成函式的[Environment]。
操作this的內建函式
Function.prototype.call和Function.prototype.apply 可以指定函式呼叫時傳入的this值
function foo(a, b, c){ console.log(this); console.log(a, b, c); } foo.call({}, 1, 2, 3); foo.apply({}, [1, 2, 3]);
另外,還有Function.prototype.bind 它可以生成一個繫結過的函式,這個函式的this值固定了引數
function foo(a, b, c){ console.log(this); console.log(a, b, c); } foo.bind({}, 1, 2, 3)();