JS做爲一種弱類型語言,在定義函數時相對於其餘語言比較靈活,有多種聲明函數的方式,方法不一樣,天然會出現一些細微的差異,本篇內容就一塊兒來探探坑。bash
// 語法格式:fn1 - 函數名, console語句 - 函數體
function fn1(name) {
console.log(`${name}的函數體`);
}
複製代碼
// 語法格式:fn2 - 變量名, function - 匿名函數, console語句 - 函數體
var fn2 = function(name) {
console.log(`${name}的函數體`);
}
複製代碼
// 語法格式:fn3 - 變量名, Function - 構造函數
var fn3 = new Function('name','console.log(`${name}的函數體`)');
複製代碼
fn1('fn1'); // fn1的函數體
fn2('fn2'); // fn2的函數體
fn3('fn3'); // fn3的函數體
複製代碼
我只是想聲明一個函數啊,怎麼有點懵,一會兒整出來三個,看起來都很好用徹底沒問題了啊。閉包
接下來咱們放入一個簡單場景,求和。app
好吃的栗子:函數
// 簡單,求兩個數的和
console.log(fn1(10, 10)); // 20
console.log(fn2(10, 10)); // Uncaught TypeError: fn2 is not a function
function fn1(a, b) {
return a + b;
}
var fn2 = function 函數名(a, b) {
return a + b;
}
複製代碼
不對啊,明明剛纔都好用...ui
console.log(typeof fn1); // function
console.log(typeof fn2); // undefined
複製代碼
看一眼fn1
和fn2
是啥this
console.log(fn1); // 'ƒ fn1(a, b) {return a + b;}'
console.log(fn2); // 'ƒ (a, b) {return a + b;}'
複製代碼
函數位於一個初始化語句中,不是一個函數聲明,不會被提早,而只會把var fn2
提早,用typeof
操做符顯示fn2
是undefined
,因此報錯(fn3
同理)。spa
注意: 在fn2中出現的函數名
一樣不可以被訪問,存在的緣由是 - 若是該函數體內的代碼報錯,會提示該函數名; 遞歸匿名函數時你須要它。(手動滑稽)翻譯
原理: 解釋器會率先讀取函數聲明,並使其在執行以前能夠訪問,而使用表達式則必須等到解析器執行到它所在的代碼行,纔會真正被解釋執行,函數聲明會正常執行code
可是....既然是匿名函數,那徹底能夠這麼寫:對象
// 聲明後當即執行
var fn2 = function(a, b) {
return a + b;
}(10, 25)
console.log(fn2); // 35
複製代碼
function fn1(a, b) {
return a + b;
}(10, 25)
// Uncaught SyntaxError: Unexpected token )
複製代碼
補1(若是我就想讓聲明函數自調用怎麼辦呢)
我只是想聲明一個函數啊,爲何要搞這麼多事情,那new Function()
老是沒毛病的吧,你們再怎麼牛,都是繼承這位來的。
經過函數表達式定義的函數和經過函數聲明定義的函數只會被解析一次,而Function構造函數定義的函數卻不一樣。也就是說,每次構造函數被調用,傳遞給Function構造函數的函數體字符串都要被解析一次 。雖然函數表達式每次都建立了一個閉包,但函數體不會被重複解析,所以函數表達式仍然要快於
new Function(...)
。 因此Function構造函數應儘量地避免使用。 來源:MDN
漢譯漢: 簡單粗暴的翻譯一下,同內容下,聲明函數最快,直接調用,函數表達式每次調用都會建立一個閉包,可是函數體不會再次解析,new表達式每次調用函數體都要解析一遍。
爲何會每次都解析一遍函數體呢? 由於JS解釋器在解析代碼時是分塊執行,不管聲明仍是仍是函數表達式,在第一次被解析完成後,都被看作'一塊代碼',構造函數採用'動態解析',每次都認爲是全新的字符串,從新解析。 (但但是,若是構造函數中含有
function
,那麼這裏的function
不會被再次解析)
好的,那麼看一下這段代碼 可怕的栗子:
var isSay= true;
if (isSay) {
function say() {
console.log('Hello')
}
}
say(); // Hello
複製代碼
徹底沒毛病,你想幹啥?(聲明函數能夠在執行代碼前聲明,函數表達式要執行到才能夠賦值函數體)
那麼。。。
say(); // Uncaught TypeError: say is not a function
var isSay= true;
if (say) {
function say() {
console.log('Hello')
}
}
複製代碼
不對啊親,我老老實實憑實力聲明的函數,怎麼就很差用了啊。 固然還有。。。
play(); // Uncaught TypeError: play is not a function
for( let item of new Array(10)) {
function play() {
console.log('happy')
}
}
複製代碼
函數聲明很是容易(常常是意外地)轉換爲函數表達式。當它再也不是一個函數聲明: 成爲表達式的一部分,再也不是函數或者腳本自身的「源元素」 (source element)。「源元素」是腳本或函數體中的非嵌套語句。 來源:MDN
有多容易轉變呢,基本上除了腳本或函數體之外,嵌套進去了本身就變了。。。
上面簡單而艱難的建立過程,咱們彷佛忽略了一個狀況,那就是同名函數不一樣聲明,到底誰是老大(優先級);
眼花的栗子:
// fn1(); // fn1聲明2
var fn1 = function() {
console.log('fn1 表達式1')
}
function fn1() {
console.log('fn1聲明1')
}
var fn1 = function() {
console.log('fn1 表達式2')
}
function fn1() {
function fn1() {
console.log('fn1聲明3')
}
console.log('fn1聲明2')
}
fn1(); // fn1 表達式2
複製代碼
總結:
fn1
時,函數表達式未執行,var
變量提高但未報錯,說明函數聲明的提高比var要高。(說了解析前就聲明瞭,var
也只能算解析中)fn1
聲明函數的函數體覆蓋第一個;fn1
時,聲明函數已經優先聲明fn1
後被賦值,最終賦值有效;構造函數
?不是告訴你不要用了嗎。(應用場景決定它不會被如此錯誤的使用;補2)終於,能夠安心 不報錯 建立函數了
補1:
()
自調用是由於解析器找不到函數名,又沒有對應的函數表達式,因此能夠經過將聲明函數所有用()
括起來或者以+ ~ !
等特殊符號加在function
關鍵字前,將聲明函數轉爲表達式便可。補2:
new Function
經過實例化一個Function原型,獲得一個數據類型爲function的對象,也就是一個函數,而該變量就是函數名,這原本和咱們最初的目標建立一個函數時符合的,可是new
操做符自己會改變關鍵字this
指向,會對建立的函數形成未知影響。new Function
時,函數體內的字符串會被解析爲腳本語言並執行,也是有可能對平常開發產生影響的緣由之一,所以在平常開發中不建議使用構造函數引用: