好程序員web前端分享詳細瞭解JavaScript函數,若是你曾經接觸過JavaScript編程,你必定不會陌生如何定義而且調用一個函數。可是你知道在JavaScript中有多少種定義函數的方法嗎?若是想要在Test262中編寫和維護這些方法的測試,那可真是一個很大的挑戰,尤爲當一些新特性和現有函數語法相關,或者擴展了函數的API時。可是,想要斷言新提出或被提案的語法、API有效時,測試全部既存變式又是很是必要的。 下面會針對JavaScript中已經存在的函數定義方式進行一個概述。本文不包含Class聲明和表達式,由於這些方式建立的對象是「不可調用的」,本文旨在那些可生成「可調用」對象的函數定義方式。也就是說,咱們不會研究那些複雜的參數列表(包含默認參數、結構賦值或者尾後逗號),由於那足夠另起文章介紹了。前端
之前的方式git
函數聲明以及函數表達式程序員
最爲出名以及應用最廣的一樣也是這些舊方式:函數聲明和函數表達式。前者設計(1995)和出如今初版的規範(1997)(pdf)中。後者則是出如今第三版中(1999)(pdf)。仔細研究,你會從它們當中提取出三種不一樣的方式。github
// 函數聲明
function BindingIdentifier() {}web
// 命名函數表達式
// (BindingIdentifier在函數外部是訪問不到的)
(function BindingIdentifier() {}); 編程
// 匿名函數表達式
(function() {});
值得注意的是,匿名函數表達式仍然可能有名字,Mike Pennisi在什麼是函數名稱?中有深度解釋。安全
Function構造函數異步
當研究一門語言的"function API"的時候,也就到了這門語言的底層。在這門語言的設計之初,函數聲明方式能夠被理解爲是Function構造函數API的最直接實現。Function構造函數提供了一種定義函數的方式:經過指明Function的參數,其中最後一個參數就是函數的函數體(必需要說明的是,這是一種動態代碼方式,可能存在安全問題)。在大多數狀況下,這種方式是不合適的,因此用的人不多,可是在初版的ECMAScript中,這種方式就出現了。async
new Function('x', 'y', 'return x ** y;');
新的方式模塊化
自從ES2015發佈以來,幾種新的定義函數方式被引入進來,這些方式的變式更是很是繁多。
另類匿名函數
這是一種新式的匿名函數。若是你曾經接觸過ES的模塊化,那麼你頗有可能已經接觸過這種定義函數的方式了。儘管這種方式看起來和匿名函數的定義方式很像,可是他確實有本身的名字:default
// 另類匿名函數聲明
export default function() {}
順便一提,這個名字並非專屬的標識,並無進行綁定。
方法定義
下面這些方式定義的函數表達式、匿名函數或者命名函數,都是某個對象的屬性。注意這些並非新的語法,只是應用上面說起的那些語法,寫在了某個對象的初始化器中。這種方式最先引入在ES3中。
let object = {
propertyName: function() {},
};
let object = {
// (BindingIdentifier不能再函數外部調用)
propertyName: function BindingIdentifier() {},
};
下面是存取器屬性,引入在ES5。
let object = {
get propertyName() {},
set propertyName(value) {},
};
在ES2015中,JavaScript中提供了一種定義方法的簡潔語法,不論是直接命名的方式仍是計算屬性名的方式,均可以使用,並且,存取器一樣適用。
let object = {
propertyName() {},
["computedName"]() {},
get ["computedAccessorName"]() {},
set "computedAccessorName" {},
};
你也能夠把這些定義屬性或者方法的新方式應用在建立類時。
// 類聲明
class C {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
}
// 類表達式
let C = class {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
};
...在定義靜態方法時,一樣可使用。
// 類聲明
class C {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
}
// 類表達式
let C = class {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
};
箭頭函數
箭頭函數首次出如今ES2015中,儘管起初飽受爭議,可是如今已經被普遍應用了。箭頭函數的定義根據是否簡寫有兩種不一樣的語法:賦值表達式(在箭頭後面沒有花括號)和函數體(當函數包含零個或者多個表達式時)。語法規定,當函數只有一個參數時,能夠不用小括號括起來,可是當沒有參數或者多餘一個參數時,就必須用小括號括起來了。(這種語法就決定了箭頭函數會有多種定義形式)
// 零參數, 賦值表達式
(() => 2 ** 2);
// 一個參數, 能夠省略小括號, 賦值表達式
(x => x ** 2);
// 一個參數, 能夠省略小括號, 函數體
(x => { return x ** 2; });
// 多個參數, 賦值表達式
((x, y) => x ** y);
上面的最後一種形式中,參數是用參數列表來表示的,由於它們用小括號括了起來。相似於用小括號來標識參數列表的語法,還有其餘形式,諸如({ x }) => x。 若是參數不用小括號括起來,那麼只能給參數起一個獨一的標識符名稱,以在箭頭函數中使用。當箭頭函數被定義爲異步函數或者Generator函數時,這個標識符名稱還能夠加上await 或者 yield的前綴,但那也已是在不用括號情形中,考慮足夠深遠的了。 箭頭函數能夠而且也常常出如今初始化器或者對象屬性的定義中,可是這種狀況大部分使用的是上面介紹的賦值表達式形式,舉例以下:
let foo = x => x ** 2;
let object = {
propertyName: x => x ** 2
};
Generators
Generator函數的語法是在其餘定義函數的方式上加點東西,但箭頭函數和存取器方法除外。你可使用和以前函數聲明,函數表達式,函數定義甚至是構造函數等類似的方式。全部方法列舉以下:
// Generator 聲明
function *BindingIdentifer() {}
// 另類匿名 Generator 聲明
export default function *() {}
// Generator 表達式
// (BindingIdentifier只能在函數內部調用)
(function *BindingIdentifier() {});
// 匿名 Generator 表達式
(function *() {});
// 方法定義
let object = {
*methodName() {},
*["computedName"]() {},
};
// 在類聲明中定義方法
class C {
*methodName() {}
*["computedName"]() {}
}
// 在類聲明中定義靜態方法
class C {
static *methodName() {}
static *["computedName"]() {}
}
// 在類表達式中定義方法
let C = class {
*methodName() {}
*["computedName"]() {}
};
// 在類表達式中定義靜態方法
let C = class {
static *methodName() {}
static *["computedName"]() {}
};
ES2017
異步函數
通過幾年的發展,異步函數將會發布ES2017——第八版EcmaScript語言規範——規範會在2017年6月在正式發佈。但其實,不少開發者早已經開始使用異步函數了,這還要歸功於Babel的支持。 異步函數語法提供了一個乾淨的、統一的方式來描述異步操做。當被調用時,異步函數會返回一個Promise對象。當異步執行結束後,這個Promise對象即會被相應執行。當函數中含有await表達式時,異步函數就會暫停執行,這時,await表達式結果就會做爲異步函數的返回值。 異步函數的語法並無太多的不一樣,只是在咱們熟知的那些方式前面加上一個前綴:
// 異步函數聲明
async function BindingIdentifier() { /**/ }
// 另類匿名異步函數聲明
export default async function() { /**/ }
// 命名異步函數表達式
// (BindingIdentifier只能在函數內部調用)
(async function BindingIdentifier() {});
// 匿名異步函數表達式
(async function() {});
// 異步方法
let object = {
async methodName() {},
async ["computedName"]() {},
};
// 類聲明中的異步方法
class C {
async methodName() {}
async ["computedName"]() {}
}
// 類聲明中的靜態異步方法
class C {
static async methodName() {}
static async ["computedName"]() {}
}
// 類表達式中的異步方法
let C = class {
async methodName() {}
async ["computedName"]() {}
};
// 類表達式中的靜態異步方法
let C = class {
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方法定義
let object = {
async *propertyName() {},
async *["computedName"]() {},
};
// 類聲明中異步Generator原型方法定義
class C {
async *propertyName() {}
async *["computedName"]() {}
}
// 類表達式中異步Generator原型方法定義
let C = class {
async *propertyName() {}
async *["computedName"]() {}
};
// 類聲明中異步Generator靜態方法定義
class C {
static async *propertyName() {}
static async *["computedName"]() {}
}
// 類表達式中異步Generator靜態方法定義let C = class { static async *propertyName() {} static async *["computedName"]() {}};