天天看點

好程式員web前端分享詳細了解JavaScript函數

好程式員web前端分享詳細了解JavaScript函數,如果你曾經接觸過JavaScript程式設計,你一定不會陌生如何定義并且調用一個函數。但是你知道在JavaScript中有多少種定義函數的方法嗎?如果想要在Test262中編寫和維護這些方法的測試,那可真是一個很大的挑戰,尤其當一些新特性和現有函數文法相關,或者擴充了函數的API時。但是,想要斷言新提出或被提案的文法、API有效時,測試所有既存變式又是非常必要的。 下面會針對JavaScript中已經存在的函數定義方式進行一個概述。本文不包含Class聲明和表達式,因為這些方式建立的對象是“不可調用的”,本文旨在那些可生成“可調用”對象的函數定義方式。也就是說,我們不會研究那些複雜的參數清單(包含預設參數、結構指派或者尾後逗号),因為那足夠另起文章介紹了。

以前的方式

函數聲明以及函數表達式

最為出名以及應用最廣的同樣也是這些舊方式:函數聲明和函數表達式。前者設計(1995)和出現在第一版的規範(1997)(pdf)中。後者則是出現在第三版中(1999)(pdf)。仔細研究,你會從它們當中提取出三種不同的方式。

// 函數聲明

function BindingIdentifier() {}

// 命名函數表達式

// (BindingIdentifier在函數外部是通路不到的)

(function BindingIdentifier() {});

// 匿名函數表達式

(function() {});

值得注意的是,匿名函數表達式仍然可能有名字,Mike Pennisi在什麼是函數名稱?中有深度解釋。

Function構造函數

當研究一門語言的"function API"的時候,也就到了這門語言的底層。在這門語言的設計之初,函數聲明方式可以被了解為是Function構造函數API的最直接實作。Function構造函數提供了一種定義函數的方式:通過指明Function的參數,其中最後一個參數就是函數的函數體(必須要說明的是,這是一種動态代碼方式,可能存在安全問題)。在大多數情況下,這種方式是不合适的,是以用的人很少,但是在第一版的ECMAScript中,這種方式就出現了。

new Function('x', 'y', 'return x ** y;');

新的方式

自從ES2015釋出以來,幾種新的定義函數方式被引入進來,這些方式的變式更是非常繁多。

另類匿名函數

這是一種新式的匿名函數。如果你曾經接觸過ES的子產品化,那麼你很有可能已經接觸過這種定義函數的方式了。盡管這種方式看起來和匿名函數的定義方式很像,但是他确實有自己的名字:default

// 另類匿名函數聲明

export default function() {}

順便一提,這個名字并不是專屬的辨別,并沒有進行綁定。

方法定義

下面這些方式定義的函數表達式、匿名函數或者命名函數,都是某個對象的屬性。注意這些并不是新的文法,隻是應用上面提及的那些文法,寫在了某個對象的初始化器中。這種方式最早引入在ES3中。

let object = {

propertyName: function() {},

};

// (BindingIdentifier不能再函數外部調用)

propertyName: function BindingIdentifier() {},

下面是存取器屬性,引入在ES5。

get propertyName() {},

set propertyName(value) {},

在ES2015中,JavaScript中提供了一種定義方法的簡潔文法,不管是直接命名的方式還是計算屬性名的方式,都可以使用,而且,存取器同樣适用。

propertyName() {},

["computedName"]() {},

get ["computedAccessorName"]() {},

set

"computedAccessorName"

{},

你也可以把這些定義屬性或者方法的新方式應用在建立類時。

// 類聲明

class C {

methodName() {}

["computedName"]() {}

get ["computedAccessorName"]() {}

{}

}

// 類表達式

let C = class {

...在定義靜态方法時,同樣可以使用。

static methodName() {}

static ["computedName"]() {}

static get ["computedAccessorName"]() {}

static set

箭頭函數

箭頭函數首次出現在ES2015中,盡管起初飽受争議,但是現在已經被廣泛應用了。箭頭函數的定義根據是否簡寫有兩種不同的文法:指派表達式(在箭頭後面沒有花括号)和函數體(當函數包含零個或者多個表達式時)。文法規定,當函數隻有一個參數時,可以不用小括号括起來,但是當沒有參數或者多餘一個參數時,就必須用小括号括起來了。(這種文法就決定了箭頭函數會有多種定義形式)

// 零參數, 指派表達式

(() => 2 ** 2);

// 一個參數, 可以省略小括号, 指派表達式

(x => x ** 2);

// 一個參數, 可以省略小括号, 函數體

(x => { return x ** 2; });

// 多個參數, 指派表達式

((x, y) => x ** y);

上面的最後一種形式中,參數是用參數清單來表示的,因為它們用小括号括了起來。類似于用小括号來辨別參數清單的文法,還有其他形式,諸如({ x }) => x。 如果參數不用小括号括起來,那麼隻能給參數起一個獨一的辨別符名稱,以在箭頭函數中使用。當箭頭函數被定義為異步函數或者Generator函數時,這個辨別符名稱還可以加上await 或者 yield的字首,但那也已經是在不用括号情形中,考慮足夠深遠的了。 箭頭函數可以并且也經常出現在初始化器或者對象屬性的定義中,但是這種情況大部分使用的是上面介紹的指派表達式形式,舉例如下:

let foo = x => x ** 2;

propertyName: x => x ** 2

Generators

Generator函數的文法是在其他定義函數的方式上加點東西,但箭頭函數和存取器方法除外。你可以使用和之前函數聲明,函數表達式,函數定義甚至是構造函數等相似的方式。所有方法列舉如下:

// Generator 聲明

function *BindingIdentifer() {}

// 另類匿名 Generator 聲明

export default function *() {}

// Generator 表達式

// (BindingIdentifier隻能在函數内部調用)

(function *BindingIdentifier() {});

// 匿名 Generator 表達式

(function *() {});

// 方法定義

*methodName() {},

*["computedName"]() {},

// 在類聲明中定義方法

*methodName() {}

*["computedName"]() {}

// 在類聲明中定義靜态方法

static *methodName() {}

static *["computedName"]() {}

// 在類表達式中定義方法

// 在類表達式中定義靜态方法

ES2017

異步函數

經過幾年的發展,異步函數将會釋出ES2017——第八版EcmaScript語言規範——規範會在2017年6月在正式釋出。但其實,很多開發者早已經開始使用異步函數了,這還要歸功于Babel的支援。 異步函數文法提供了一個幹淨的、統一的方式來描述異步操作。當被調用時,異步函數會傳回一個Promise對象。當異步執行結束後,這個Promise對象即會被相應執行。當函數中含有await表達式時,異步函數就會暫停執行,這時,await表達式結果就會作為異步函數的傳回值。 異步函數的文法并沒有太多的不同,隻是在我們熟知的那些方式前面加上一個字首:

// 異步函數聲明

async function BindingIdentifier() { /**/ }

// 另類匿名異步函數聲明

export default async function() { /**/ }

// 命名異步函數表達式

(async function BindingIdentifier() {});

// 匿名異步函數表達式

(async function() {});

// 異步方法

async methodName() {},

async ["computedName"]() {},

// 類聲明中的異步方法

async methodName() {}

async ["computedName"]() {}

// 類聲明中的靜态異步方法

static async methodName() {}

static async ["computedName"]() {}

// 類表達式中的異步方法

// 類表達式中的靜态異步方法

異步箭頭函數

async 和 await并不是隻局限在正常函數的聲明或者表達式中,它們同樣适用于箭頭函數:

// 單一參數,指派表達式

(async x => x ** 2);

// 單一參數,函數體

(async x => { return x ** 2; });

// 參數清單,指派表達式

(async (x, y) => x ** y);

// 參數清單,函數體

(async (x, y) => { return x ** y; });

與ES2017結合

異步Generator函數

和ES2017結合,async 和 await将會被擴充支援異步函數。這一特性的發展可以追蹤推薦的github倉儲。正如你猜想到的,異步Generator函數的文法是結合async、await以及已經存在的Generator函數聲明和表達式而來。當異步Generator函數被調用的時候,會傳回一個疊代器,這個疊代器的next()方法會傳回一個Promise對象來處理疊代器的傳回對象,而不是直接傳回疊代器的結果對象。 異步Generator函數已經開始在很多地方使用,你很有可能已經碰到過。

// 異步Generator聲明

async function BindingIdentifier() { /*/ }

// 另類匿名異步Generator聲明

export default async function *() {}

// 異步Generator表達式

// (BindingIdentifier隻能在函數内部通路)

(async function *BindingIdentifier() {});

// 匿名異步Generator表達式

(async function *() {});

// 匿名異步Generator方法定義

async *propertyName() {},

async *["computedName"]() {},

// 類聲明中異步Generator原型方法定義

async *propertyName() {}

async *["computedName"]() {}

// 類表達式中異步Generator原型方法定義

// 類聲明中異步Generator靜态方法定義

static async *propertyName() {}

static async *["computedName"]() {}

// 類表達式中異步Generator靜态方法定義

繼續閱讀