JavaScript函數的多種寫法
前言
讓我想起茴香豆的茴有幾個寫法,今日早讀文章由@Liubara 翻譯授權分享。
可能現在的你已經在去旅行的路上,那前端早讀課也可以伴你相形。
正文從這開始~
如果你曾與JavaScript代碼打過交道,你應該會很熟悉如何定義和調用函數,但是你真的知道有多少種定義函數的方法嗎?對於編寫和維護測試Test262(瀏覽器JavaScript標準測試)來說,這是一個十分常見的挑戰,尤其是當一個新特性出現且與現有的函數語法有關聯,或者擴展了現有函數的API時。有必要確保新的或被提議的語法和API是有效的,且對語言中的每一個現有變體都是有效的。
本文內容是對JavaScript中已經存在的函數語法格式的概述說明。本文檔不包括類聲明和表達式,因為這些形式生成的對象不是「可調用的」,對於本文來說,我們只關注生成「可調用的」函數對象的格式。此外,這篇文章也不包括參數列表(包括默認參數、解構,或者尾逗號),因為這些話題足夠再寫一篇文章了。
譯者註:類聲明、默認參數、解構和尾逗號是ES6/7的新特性~~不妨礙看這篇文章哈
舊方法
函數聲明和表達式
大家都知道,最廣泛應用也是最早的函數定義形式就是函數聲明和函數表達式。前者是最初設計的一部分(1995)並出現在第一個版本的規範(1997年)中,後者是在第三個版本(1999年)引入的。我們可以從這些規範中提取三種不同的定義形式:
// 函數聲明
functionBindingIdentifier(){}
// 命名函數表達式
// (BindingIdentifier對函數外部不可訪問)
(functionBindingIdentifier(){});
// 匿名函數表達式
(function(){});
要注意的是匿名函數表達式可能仍然有一個「名稱」,Mike Pennisi在這篇文章What s in a Function Name?中解釋得很清楚。
Function構造器
當我們在討論一種語言的「函數API」的時候,就已經開始討論Function構造器。在考慮最初的語言設計時,函數聲明的語法形式可以被解釋為函數構造器的API的「文字」形式。Function構造器為定義函數提供了一種方法:通過N個字元串參數指定函數參數和函數主體,(如下面的例子)最後一個字元串參數始終是函數主體(需要指出的是,這是一種動態求值形式,會有潛在的安全風險)。對於大多數情況來說,這種形式並不適合,因此它的使用非常稀少——但是自從第一個版本的ECMAScript以來,它就一直存在在JavaScript 中了。
newFunction( x , y , return x * y; );
新方法
自從ES2015推出以來,已經引入了幾種新的語法形式。這些形式的變化是巨大的!
not-so-anonymous函數聲明
這是一種新的匿名函數聲明形式,如果你曾用過ES Modules,應該清楚這種語法。雖然它可能看起來與匿名函數表達式非常相似,但它實際上有一個默認名稱,即「default」
// not-so-anonymous 函數聲明
exportdefaultfunction(){}
順便說一下,這個「名稱」(指「default」)本身並不是有效的標識符,並且沒有綁定在該匿名函數上。
方法定義
對於下面這個例子,大家應該能很快發現它定義了匿名和命名函數表達式作為屬性的值。注意,這些不是不同的語法形式。它們是之前討論過的函數表達式的示例,是在初始對象時編寫的。這種形式最初是在ES3中引入的。
letobject={
propertyName:function(){},
};
letobject={
// (BindingIdentifier在這個函數中不可訪問)
propertyName:functionBindingIdentifier(){},
};
在ES5中引入了訪問器屬性定義:
letobject={
getpropertyName(){},
setpropertyName(value){},
};
從ES2015開始,JavaScript提供了一個簡單的語法來定義方法,這種語法包括文字屬性名稱和計算屬性名稱形式,以及訪問器形式:
letobject={
propertyName(){},
["computedName"](){},
get["computedAccessorName"](){},
set["computedAccessorName"](value){},
};
我們還可以使用這些新形式作為類聲明和表達式中的原型方法的定義:
// 類聲明
classC{
methodName(){}
["computedName"](){}
get["computedAccessorName"](){}
set["computedAccessorName"](value){}
}
// 類表達式
letC=class{
methodName(){}
["computedName"](){}
get["computedAccessorName"](){}
set["computedAccessorName"](value){}
};
和定義靜態方法:
// 類聲明
classC{
staticmethodName(){}
static["computedName"](){}
staticget["computedAccessorName"](){}
staticset["computedAccessorName"](value){}
}
// 類表達式
letC=class{
staticmethodName(){}
static["computedName"](){}
staticget["computedAccessorName"](){}
staticset["computedAccessorName"](value){}
};
箭頭函數
作為ES2015最具爭議性的函數之一,箭頭函數已經變得眾所周知且無處不在。箭頭函數語法是這樣定義的,它為函數聲明提供了兩種不同的格式:賦值表達式(箭頭後面沒有跟「{」大括弧時為賦值表達式)和函數體(代碼中包括0到多個語句時為函數體)。這個語法還允許在描述單個參數時不加圓括弧,然而0個或一個以上參數需要加圓括弧。這些語法結構允許箭頭函數擁有多種書寫形式:
// 木有參數的賦值表達式
(()=>2**2);
// 單個參數,忽略括弧的賦值表達式
(x=>x**2);
// 單個參數,忽略括弧且直接跟函數體
(x=>{returnx**2;});
// 括起來的參數列表和賦值表達式
((x,y)=>x**y);
在上面所示的最後一種形式中,參數被描述為一個括起來的參數列表,因為它們被包裝在括弧內。這提供了一種語法來標記參數列表或特殊的解構模式,就像({ x })= > x。
未被括起來的形式——也就是沒有圓括弧的形式——即在箭頭函數只能表現為只用一個標識符名稱作為參數的形式。當箭頭函數在非同步函數或生成器中定義時,這個標識符名稱需要以await或yeild作為前綴定義。但這是我們在箭頭函數中能得到的最大程度的不用括弧括起來參數列表的情況。
箭頭函數可以(也經常)出現在初始化值或屬性定義的賦值中,上面所示的箭頭函數表達式已經包括了這種情況,如下面的例子所示:
letfoo=x=>x**2;
letobject={
propertyName:x=>x**2
};
生成器
生成器有一種特殊的語法,除了箭頭函數和定義setter / getter方法的時候不能添加之外,可以被添加在其他所有語法形式中。我們可以用其生成函數聲明、表達式、定義,甚至構造函數。讓我們把它們列出來:
// 生成器聲明
function*BindingIdentifer(){}
// 另一種 not-so-anonymous 生成器聲明
exportdefaultfunction*(){}
// 命名生成器表達式
// (BindingIdentifier 對函數外部不可訪問)
(function*BindingIdentifier(){});
// 匿名生成器表達式
(function*(){});
// 方法定義
letobject={
*methodName(){},
*["computedName"](){},
};
// 類聲明中的方法定義
classC{
*methodName(){}
*["computedName"](){}
}
// 類聲明中的靜態方法定義
classC{
static*methodName(){}
static*["computedName"](){}
}
// 類表達式中的方法定義
letC=class{
*methodName(){}
*["computedName"](){}
};
// 類表達式中的靜態方法定義
letC=class{
static*methodName(){}
static*["computedName"](){}
};
ES2017
非同步函數
經歷了幾年的發展,非同步函數將於2017年6月發布ES2017的EcmaScript語言規範的第8版引入。儘管如此,許多開發人員已經使用了這個特性,這要歸功於Babel的早期實現支持!
Async函數語法為描述非同步操作提供了一種乾淨而統一的方式。調用時,Async函數對象將返回一個Promise對象,這個對象將在非同步函數返回時被解析。當包含一個await表達式時,非同步函數可能暫停函數的執行,然後將其用作非同步函數的返回值。
它的語法和我們從其他形式中所知道的一樣:
// 非同步函數聲明
asyncfunctionBindingIdentifier(){/**/}
// not-so-anonymous 非同步函數聲明
exportdefaultasyncfunction(){/**/}
// 命名非同步函數表達式
// (BindingIdentifier is not accessible outside of this function)
(asyncfunctionBindingIdentifier(){});
// 匿名非同步函數表達式
(asyncfunction(){});
// 非同步方法
letobject={
asyncmethodName(){},
async["computedName"](){},
};
// 類聲明中的非同步方法
classC{
asyncmethodName(){}
async["computedName"](){}
}
// 類聲明中的靜態非同步方法
classC{
staticasyncmethodName(){}
staticasync["computedName"](){}
}
// 類聲明中的非同步方法
letC=class{
asyncmethodName(){}
async["computedName"](){}
};
// 類表達式中的非同步方法
letC=class{
staticasyncmethodName(){}
staticasync["computedName"](){}
};
非同步箭頭函數
async和await並不局限於普通的聲明和表達式形式,它們也可以用於箭頭函數:
// 單個參數的賦值表達式
(asyncx=>x**2);
// 單個參數的函數體
(asyncx=>{returnx**2;});
// 括起來的參數列表後跟賦值表達式
(async(x,y)=>x**y);
// 括起來的參數列表後跟函數體
(async(x,y)=>{returnx**y;});
非同步生成器 Async Generators
在接下來的ES017中,async和await關鍵字將被擴展以支持新的非同步生成器形式。這個特性的進展可以通過proposal』s github repository進行跟蹤。您可能已經猜到,這是async、await和現有的生成器聲明和生成器表達式語法的組合。調用時,非同步生成器返回一個迭代器,它的next()方法返回Promise對象然後用迭代器對象解析,而不是直接返回迭代器對象。
可以在許多地方發現非同步生成器,你可能已經生成器函數中見到它了。
// 非同步生成器聲明
asyncfunction*BindingIdentifier(){/**/}
// not-so-anonymous 非同步生成器聲明
exportdefaultasyncfunction*(){}
// 非同步生成器表達式
// (BindingIdentifier在函數外部不可訪問)
(asyncfunction*BindingIdentifier(){});
// 匿名函數表達式
(asyncfunction*(){});
// 方法定義
letobject={
async*propertyName(){},
async*["computedName"](){},
};
// 類聲明中的原型方法定義
classC{
async*propertyName(){}
async*["computedName"](){}
}
// 類表達式中的原型方法定義
letC=class{
async*propertyName(){}
async*["computedName"](){}
};
// 類聲明中的靜態方法定義
classC{
staticasync*propertyName(){}
staticasync*["computedName"](){}
}
// 類表達式中的靜態方法定義
letC=class{
staticasync*propertyName(){}
staticasync*["computedName"](){}
};
複雜的挑戰
每個函數語法格式不僅對學習和使用是挑戰,而且對JS運行時間和Test262的實現和維護也是一個挑戰。當引入新的語法形式時,Test262必須與所有相關的語法規則一起測試該新形式。例如,將默認參數語法的測試形式限制在簡單的函數聲明形式中,並假設在其他格式下該語法也正常起作用是不明智的。每一個語法規則都必須經過測試,將這些測試任務分配給一個人是不合理的。所以導致了測試生成工具的設計和實現。測試生成工具提供了一種確保能夠覆蓋(函數格式的多少)更詳盡的方法。
這個項目現在包含了一系列由不同的測試用例和模板組成的源文件,例如,如何檢查每個函數格式的參數,或者函數格式測試,甚至更多超出範圍的函數形式,在這些函數形式中,解構綁定和解構賦值都是適用的。
儘管它可能導緻密集的和長時間的發送請求,但是覆蓋率總是會提高,而且可能總是會發現新的錯誤。
為什麼了解所有的函數格式是很重要的?
如果不需要在Test262上編寫測試,計算和列出所有函數表單可能並不重要。這裡已經列出了許多格式的模板。新的測試可以很容易地使用現有的模板作為起點。
確保EcmaScript規範的良好測試是Test262的主要任務。這對所有的JavaScript運行時間都有直接的影響,我們識別的格式越多,覆蓋率就越全面,這將幫助新功能更無縫地集成,不管您使用的平台是什麼。
關於本文
譯者:@Liubara
原文:The Many Faces of Functions in JavaScript
作者:@Leo Balter、@Rick Waldron
點擊展開全文


※讀前端架構設計——我眼中的前端架構
※你不懂JS:ES6與未來 新增API
※代碼審查應該關注什麼:性能
※域名劫持資源重載入方案
TAG:前端早讀課 |
※Monique Lhuillier 細寫法國鄉村的詩意光景
※華美禮服 | Monique Lhuillier 細寫法國鄉村的詩意光景
※thinkphp框架利用MVC模式使用模型查詢資料庫數組的四種寫法
※Python 生成一段隨機字元串的三種寫法
※到底是iPhone XS還是Xs?告訴你蘋果官方正確寫法
※自信優雅還有另一種「寫法」:百達翡麗Twenty 4!
※自信優雅還有另一種「寫法」:百達翡麗Twenty~4!
※Jhin的四種寫法你知道么 戲命師翻譯揭秘
※品牌 T 的寫法
※LAMP建站時配置文件寫法實例
※品牌|T 的寫法
※詩詞賞析:讀絕句-學寫法21
※讀?懂這11個字的寫法,舉一反三悟筆法
※「壽」字的100種寫法
※「壽」字寫法100種,不看虧大了
※歐冠抽籤點評:死亡的N種寫法
※學歐體,這幾個筆畫的寫法,你可以參考
※楷書對聯的100種寫法!
※你的「貳」有多少種寫法?
※名家示範:隸書對聯的100種寫法!