javascript - parameter - variable function
var functionName=function(){} vs function functionName(){} (20)
我最近開始維護其他人的JavaScript代碼。 我正在修復錯誤,添加功能,還試圖整理代碼並使其更加一致。
以前的開發人員使用兩種聲明函數的方法,如果背後有原因,我就無法解決。
這兩種方式是:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
使用這兩種不同方法的原因是什麼?每種方法的優缺點是什麼? 有一種方法可以通過一種方法完成,而另一種方法無法做到嗎?
new Function()
可用於在函數字符串中傳遞函數的主體。因此,這可用於創建動態功能。在不執行腳本的情況下傳遞腳本。
var func = new Function("x", "y", "return x*y;");
function secondFunction(){
var result;
result = func(10,20);
console.log ( result );
}
secondFunction()
Hoisting 是JavaScript解釋器將所有變量和函數聲明移動到當前範圍頂部的操作。
但是,只有實際的聲明才會被提升。 將任務留在原處。
- 在頁面內聲明的變量/函數是全局的,可以訪問該頁面中的任何位置。
- 函數內聲明的變量/函數具有局部範圍。 意味著它們在功能體(範圍)內可用/訪問,它們在功能體外部不可用。
Javascript被稱為鬆散類型的語言。 這意味著Javascript變量可以保存任何Data-Type值。 Javascript自動根據運行時提供的值/文字來更改變量類型。
global_Page = 10; var global_Page; « undefined
« Integer literal, Number Type. ------------------- global_Page = 10; « Number
global_Page = 'Yash'; | Interpreted | global_Page = 'Yash'; « String
« String literal, String Type. « AS « global_Page = true; « Boolean
var global_Page = true; | | global_Page = function (){ « function
« Boolean Type ------------------- var local_functionblock; « undefined
global_Page = function (){ local_functionblock = 777;« Number
var local_functionblock = 777; };
// Assigning function as a data.
};
功能
function Identifier_opt ( FormalParameterList_opt ) {
FunctionBody | sequence of statements
« return; Default undefined
« return 'some data';
}
- 在頁面內聲明的函數被提升到具有全局訪問權限的頁面頂部。
- 在函數塊內聲明的函數被提升到塊的頂部。
函數的默認返回值是' undefined ', Variable聲明默認值也是'undefined'
Scope with respect to function-block global. Scope with respect to page undefined | not available.
功能聲明
function globalAccess() { function globalAccess() {
} ------------------- }
globalAccess(); | | function globalAccess() { « Re-Defined / overridden.
localAccess(); « Hoisted As « function localAccess() {
function globalAccess() { | | }
localAccess(); ------------------- localAccess(); « function accessed with in globalAccess() only.
function localAccess() { }
} globalAccess();
} localAccess(); « ReferenceError as the function is not defined
功能表達
10; « literal
(10); « Expression (10).toString() -> '10'
var a;
a = 10; « Expression var a.toString() -> '10'
(function invoke() { « Expression Function
console.log('Self Invoking'); (function () {
}); }) () -> 'Self Invoking'
var f;
f = function (){ « Expression var Function
console.log('var Function'); f () -> 'var Function'
};
分配給變量的函數示例:
(function selfExecuting(){
console.log('IIFE - Immediately-Invoked Function Expression');
}());
var anonymous = function (){
console.log('anonymous function Expression');
};
var namedExpression = function for_InternalUSE(fact){
if(fact === 1){
return 1;
}
var localExpression = function(){
console.log('Local to the parent Function Scope');
};
globalExpression = function(){
console.log('creates a new global variable, then assigned this function.');
};
//return; //undefined.
return fact * for_InternalUSE( fact - 1);
};
namedExpression();
globalExpression();
javascript解釋為
var anonymous;
var namedExpression;
var globalExpression;
anonymous = function (){
console.log('anonymous function Expression');
};
namedExpression = function for_InternalUSE(fact){
var localExpression;
if(fact === 1){
return 1;
}
localExpression = function(){
console.log('Local to the parent Function Scope');
};
globalExpression = function(){
console.log('creates a new global variable, then assigned this function.');
};
return fact * for_InternalUSE( fact - 1); // DEFAULT UNDEFINED.
};
namedExpression(10);
globalExpression();
您可以使用jsperf Test Runner
檢查不同瀏覽器的函數聲明,表達式測試
ES5構造函數類 :使用Function.prototype.bind創建的函數對象
JavaScript將函數視為第一類對象,因此作為對象,您可以為函數指定屬性。
function Shape(id) { // Function Declaration
this.id = id;
};
// Adding a prototyped method to a function.
Shape.prototype.getID = function () {
return this.id;
};
Shape.prototype.setID = function ( id ) {
this.id = id;
};
var expFn = Shape; // Function Expression
var funObj = new Shape( ); // Function Object
funObj.hasOwnProperty('prototype'); // false
funObj.setID( 10 );
console.log( funObj.getID() ); // 10
ES6引入了箭頭函數 :箭頭函數表達式具有較短的語法,它們最適合非方法函數,並且它們不能用作構造函數。
ArrowFunction : ArrowParameters => ConciseBody
。const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; }; console.log( fn(2) ); // Even console.log( fn(3) ); // Odd
一個重要原因是添加一個且只有一個變量作為命名空間的“根”...
var MyNamespace = {}
MyNamespace.foo= function() {
}
要么
var MyNamespace = {
foo: function() {
},
...
}
命名空間有很多技巧。 隨著大量JavaScript模塊的出現,它變得越來越重要。
一旦建立綁定,分配給變量的函數聲明和函數表達式的行為相同。
然而,函數對象與其變量實際關聯的方式和時間存在差異。這種差異是由於JavaScript中的變量提昇機製造成的。
基本上,所有函數聲明和變量聲明都被提升到聲明發生的函數的頂部(這就是我們說JavaScript具有函數作用域的原因)。
當函數聲明被提升時,函數體“跟隨”,因此當評估函數體時,變量將立即綁定到函數對象。
當一個變量聲明懸掛,初始化並沒有跟隨,而是“留下”。變量初始化為
undefined
函數體的開頭,並在代碼中的原始位置分配一個值。(實際上,它將在每個發生具有相同名稱的變量聲明的位置分配一個值。)
提升的順序也很重要:函數聲明優先於具有相同名稱的變量聲明,並且最後一個函數聲明優先於具有相同名稱的先前函數聲明。
一些例子...
var foo = 1;
function bar() {
if (!foo) {
var foo = 10 }
return foo; }
bar() // 10
變量foo
被提升到的功能,初始化的頂部undefined
,從而使!foo
是true
,所以foo
被分配10
。範圍的foo
外部bar
沒有任何作用,也沒有受到影響。
function f() {
return a;
function a() {return 1};
var a = 4;
function a() {return 2}}
f()() // 2
function f() {
return a;
var a = 4;
function a() {return 1};
function a() {return 2}}
f()() // 2
函數聲明優先於變量聲明,最後一個函數聲明“粘”。
function f() {
var a = 4;
function a() {return 1};
function a() {return 2};
return a; }
f() // 4
在此示例a
中,使用通過計算第二個函數聲明得到的函數對象進行初始化,然後進行分配4
。
var a = 1;
function b() {
a = 10;
return;
function a() {}}
b();
a // 1
這里首先提升函數聲明,聲明並初始化變量a
。接下來,分配此變量10
。換句話說:賦值不分配給外部變量a
。
不同之處在於functionOne
是一個函數表達式,因此只在達到該行時定義,而functionTwo
是一個函數聲明,並且只要執行其周圍的函數或腳本(由於hoisting )就會定義。
例如,一個函數表達式:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
並且,一個函數聲明:
// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
這也意味著您無法使用函數聲明有條件地定義函數:
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
上面實際上定義了functionThree
而不管test
的值 - 除非use strict
有效,在這種情況下它只會引發錯誤。
何時優先考慮第一種方法到第二種方法的說明是當您需要避免覆蓋函數的先前定義時。
同
if (condition){
function myfunction(){
// Some code
}
}
, myfunction
這個定義將覆蓋任何先前的定義,因為它將在分析時完成。
而
if (condition){
var myfunction = function (){
// Some code
}
}
只有在滿足condition
時才能正確定義myfunction
。
其他評論者已經涵蓋了上述兩種變體的語義差異。 我想要注意一個風格差異:只有“賦值”變體可以設置另一個對象的屬性。
我經常用這樣的模式構建JavaScript模塊:
(function(){
var exports = {};
function privateUtil() {
...
}
exports.publicUtil = function() {
...
};
return exports;
})();
使用此模式,您的公共函數將全部使用賦值,而您的私有函數使用聲明。
(另請注意,賦值在語句後應該使用分號,而聲明禁止它。)
在JavaScript中有兩種創建函數的方法:
功能聲明:
function fn(){ console.log("Hello"); } fn();
這是非常基本的,不言自明的,在C語言系列中以多種語言和標準使用。我們聲明了一個函數定義它並通過調用它來執行它。
您應該知道的是,函數實際上是JavaScript中的對象; 在內部,我們為上面的函數創建了一個對象,並給它一個名為fn的名稱,或者對象的引用存儲在fn中。函數是JavaScript中的對象; 函數實例實際上是一個對象實例。
功能表達:
var fn=function(){ console.log("Hello"); } fn();
JavaScript具有一流的功能,即創建一個函數並將其分配給變量,就像創建字符串或數字並將其分配給變量一樣。這裡,fn變量被賦值給一個函數。這個概念的原因是函數是JavaScript中的對象; fn指向上述函數的對象實例。我們初始化了一個函數並將其分配給變量。它沒有執行該功能並分配結果。
參考:JavaScript函數聲明語法:var fn = function(){} vs function fn(){}
如果您將使用這些函數來創建對象,您將獲得:
var objectOne = new functionOne();
console.log(objectOne.__proto__); // prints "Object {}" because constructor is an anonymous function
var objectTwo = new functionTwo();
console.log(objectTwo.__proto__); // prints "functionTwo {}" because constructor is a named function
它們非常相似,只有一些小差異,第一個是分配給匿名函數的變量(函數聲明),第二個是在JavaScript中創建函數的常規方法(匿名函數聲明),兩者都有用法,缺點和優點:
1.功能表達
var functionOne = function() {
// Some code
};
函數表達式將函數定義為更大表達式語法(通常是變量賦值)的一部分。通過函數表達式定義的函數可以命名或匿名。函數表達式不能以“function”開頭(因此下面的自調用示例的括號)。
將一個變量分配給一個函數,意味著沒有提升,因為我們知道JavaScript中的函數可以提升,意味著它們可以在聲明之前被調用,而變量需要在訪問它們之前聲明,所以在這種情況下意味著我們不能在聲明它之前訪問函數,也可以是你編寫函數的一種方式,對於返回另一個函數的函數,這種聲明是有意義的,同樣在ECMA6及以上你可以將它分配給一個箭頭函數可以用來調用匿名函數,這種聲明方式也是在JavaScript中創建構造函數的更好方法。
2.功能聲明
function functionTwo() {
// Some code
}
函數聲明定義了一個命名函數變量,無需變量賦值。函數聲明作為獨立構造出現,不能嵌套在非函數塊中。將它們視為變量聲明的兄弟是有幫助的。正如變量聲明必須以“var”開頭一樣,函數聲明必須以“function”開頭。
這是在JavaScript中調用函數的常規方法,這個函數可以在你將它聲明之前調用,因為在JavaScript中所有函數都被提升,但是如果你有'use strict',這將不會像預期的那樣提升,這是一個好方法調用行中不大的所有普通函數,也不是構造函數。
此外,如果您需要有關如何在JavaScript中提升的更多信息,請訪問以下鏈接:
我列出了以下差異:
函數聲明可以放在代碼中的任何位置。即使在定義出現在代碼之前調用它,它也會在函數聲明被提交到內存或以某種方式被提升之前執行,之後頁面中的任何其他代碼開始執行。
看看下面的功能:
function outerFunction() { function foo() { return 1; } return foo(); function foo() { return 2; } } alert(outerFunction()); // Displays 2
這是因為,在執行期間,它看起來像: -
function foo() { // The first function declaration is moved to top return 1; } function foo() { // The second function declaration is moved to top return 2; } function outerFunction() { return foo(); } alert(outerFunction()); //So executing from top to bottom, //the last foo() returns 2 which gets displayed
函數表達式(如果在調用之前未定義)將導致錯誤。此外,這裡函數定義本身不會像函數聲明一樣移動到頂部或提交到內存。但是我們分配函數的變量被提升並且未定義被分配給它。
使用函數表達式的相同函數
function outerFunction() { var foo = function() { return 1; } return foo(); var foo = function() { return 2; } } alert(outerFunction()); // Displays 1
這是因為在執行期間,它看起來像:
function outerFunction() { var foo = undefined; var foo = undefined; foo = function() { return 1; }; return foo (); foo = function() { // This function expression is not reachable return 2; }; } alert(outerFunction()); // Displays 1
它是不是安全寫在非功能塊函數聲明一樣,如果因為他們將無法訪問。
if (test) { function x() { doSomething(); } }
如下所示的命名函數表達式可能無法在版本9之前的Internet Explorer瀏覽器中使用。
var today = function today() {return new Date()}
我在代碼中使用變量方法是出於一個非常具體的原因,其理論已經在上面以一種抽象的方式進行了介紹,但是一個例子可能會幫助像我這樣的人,JavaScript專業知識有限。
我有代碼,我需要運行160個獨立設計的品牌。大多數代碼都在共享文件中,但品牌特定的東西在一個單獨的文件中,每個品牌一個。
有些品牌需要特定功能,有些則不需要。有時我必須添加新功能來執行新的品牌特定事物。我很樂意更改共享編碼,但我不想更改所有160套品牌文件。
通過使用變量語法,我可以在共享代碼中聲明變量(本質上是一個函數指針),並分配一個普通的存根函數,或設置為null。
然後,需要特定功能實現的一個或兩個品牌可以定義它們的函數版本,並在需要時將其分配給變量,其餘的則不做任何事情。我可以在共享代碼中執行之前測試null函數。
根據上面人們的評論,我認為也可以重新定義靜態函數,但我認為變量解決方案很好而且清晰。
更好地解釋格雷格的答案
functionTwo();
function functionTwo() {
}
為什麼沒有錯誤? 我們總是被告知表達式是從上到下執行的(??)
因為:
函數聲明和變量聲明總是被JavaScript解釋器無形地移動(
hoisted
)到其包含範圍的頂部。 顯然,功能參數和語言定義的名稱已經存在。 本櫻桃
這意味著代碼如下:
functionOne(); --------------- var functionOne;
| is actually | functionOne();
var functionOne = function(){ | interpreted |-->
}; | like | functionOne = function(){
--------------- };
請注意,聲明的賦值部分未被提升。 只有名字被懸掛。
但在函數聲明的情況下,整個函數體也將被提升 :
functionTwo(); --------------- function functionTwo() {
| is actually | };
function functionTwo() { | interpreted |-->
} | like | functionTwo();
---------------
格雷格的答案已經足夠好了,但我仍然想補充一點,我剛才看到道格拉斯·克羅克福德的視頻。
功能表達:
var foo = function foo() {};
功能說明:
function foo() {};
函數語句只是var
帶有function
值的語句的簡寫。
所以
function foo() {};
擴展到
var foo = function foo() {};
其進一步擴展到:
var foo = undefined;
foo = function foo() {};
它們都被提升到代碼的頂部。
第一個(函數doSomething(x))應該是對象表示法的一部分。
第二個(var doSomething = function(x){ alert(x);}
)只是創建一個匿名函數並將其賦值給變量doSomething
。所以doSomething()會調用該函數。
您可能想知道函數聲明和函數表達式是什麼。
函數聲明定義了一個命名函數變量,而不需要變量賦值。函數聲明作為獨立構造出現,不能嵌套在非函數塊中。
function foo() {
return 3;
}
ECMA 5(13.0)將語法定義為
函數Identifier(FormalParameterList opt){FunctionBody}
在上述條件中,函數名稱在其範圍內以及其父級的範圍內可見(否則將無法訪問)。
並在函數表達式中
函數表達式將函數定義為較大表達式語法(通常是變量賦值)的一部分。通過函數表達式定義的函數可以是命名的或匿名的。函數表達式不應以“function”開頭。
// Anonymous function expression
var a = function() {
return 3;
}
// Named function expression
var a = function foo() {
return 3;
}
// Self-invoking function expression
(function foo() {
alert("hello!");
})();
ECMA 5(13.0)將語法定義為
函數Identifier opt(FormalParameterList opt){FunctionBody}
給出了一個例子,他將一個指定的函數命名為能夠shortcut()
用作自身的內部引用。John Resig給出了另一個例子 - 在他的Learning Advanced Javascript教程中復制分配給另一個對象的遞歸函數。雖然向屬性分配函數並不是嚴格意義上的問題,但我建議您主動嘗試教程 - 通過單擊右上角的按鈕運行代碼,然後雙擊代碼進行編輯以符合您的喜好。
本教程中的示例:遞歸調用yell()
:
刪除原始ninja對象時測試失敗。(第13頁)
var ninja = {
yell: function(n){
return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );
var samurai = { yell: ninja.yell };
var ninja = null;
try {
samurai.yell(4);
} catch(e){
assert( false, "Uh, this isn't good! Where'd ninja.yell go?" );
}
如果命名將以遞歸方式調用的函數,則測試將通過。(第14頁)
var ninja = {
yell: function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "Works as we would expect it to!" );
var samurai = { yell: ninja.yell };
var ninja = {};
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." );
這只是聲明函數的兩種可能方式,在第二種方式中,您可以在聲明之前使用該函數。
這是創建函數的標準表單的綱要:( 最初是為另一個問題編寫的,但在被移入規範問題後進行了調整。)
條款:
- ES5 : ECMAScript第5版 ,2009年
- ES2015 : ECMAScript 2015 (也稱為“ES6”)
快速清單:
功能聲明
“匿名”
function
表達式(儘管有術語,有時會創建帶有名稱的函數)命名
function
表達式存取器功能初始化器(ES5 +)
箭頭函數表達式(ES2015 +) (與匿名函數表達式一樣,不涉及顯式名稱,但可以創建帶有名稱的函數)
對像初始化程序中的方法聲明(ES2015 +)
類中的構造函數和方法聲明(ES2015 +)
功能聲明
第一種形式是函數聲明 ,如下所示:
function x() {
console.log('x');
}
函數聲明是一個聲明 ; 這不是陳述或表達。 因此,你不遵循它;
(雖然這樣做是無害的)。
在執行任何逐步執行代碼之前 ,執行進入其出現的上下文時,將處理函數聲明。 它創建的函數有一個專有名稱(上例中的x
),該名稱放在聲明出現的範圍內。
因為它是在同一個上下文中的任何分步代碼之前處理的,所以你可以這樣做:
x(); // Works even though it's above the declaration
function x() {
console.log('x');
}
直到ES2015,規範沒有涵蓋JavaScript引擎應該做的事情,如果你在一個控制結構中放置一個函數聲明,如try
, if
, switch
, while
等,如下所示:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
而且由於它們是在逐步運行代碼之前進行處理的,所以當它們處於控制結構中時,知道該怎麼做是很棘手的。
儘管在ES2015之前沒有指定這樣做,但是它是一個允許的擴展來支持塊中的函數聲明。 不幸的是(並且不可避免地),不同的引擎做了不同的事情。
從ES2015開始,規範說明了該怎麼做。 事實上,它提供了三個單獨的事情:
- 如果鬆散模式不在 Web瀏覽器上,則JavaScript引擎應該做一件事
- 如果在Web瀏覽器上處於鬆散模式,則JavaScript引擎應該執行其他操作
- 如果在嚴格模式下(瀏覽器與否),JavaScript引擎應該做另一件事
鬆散模式的規則很棘手,但在嚴格模式下,塊中的函數聲明很容易:它們是塊的本地(它們具有塊範圍 ,這在ES2015中也是新的),並且它們被提升到頂部塊。 所以:
"use strict";
if (someCondition) {
foo(); // Works just fine
function foo() {
}
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
// because it's not in the same block)
“匿名” function
表達
第二種常見形式稱為匿名函數表達式 :
var y = function () {
console.log('y');
};
與所有表達式一樣,它是在逐步執行代碼時達到的。
在ES5中,它創建的函數沒有名稱(它是匿名的)。 在ES2015中,如果可能,通過從上下文推斷出該函數的名稱。 在上面的示例中,名稱將為y
。 當函數是屬性初始值設定項的值時,會執行類似的操作。 (有關何時發生這種情況以及規則的詳細信息,請在規範中搜索SetFunctionName
- 它遍布整個地方。)
命名function
表達式
第三種形式是命名函數表達式 (“NFE”):
var z = function w() {
console.log('zw')
};
它創建的函數具有正確的名稱(在本例中為w
)。 與所有表達式一樣,在逐步執行代碼時會對其進行評估。 函數的名稱未添加到表達式出現的範圍中; 名稱在函數本身的範圍內:
var z = function w() {
console.log(typeof w); // "function"
};
console.log(typeof w); // "undefined"
請注意,NFE經常成為JavaScript實現的錯誤來源。 例如,IE8及更早版本完全錯誤地處理NFE,在兩個不同的時間創建兩個不同的函數。 早期版本的Safari也存在問題。 好消息是當前版本的瀏覽器(IE9及更高版本,當前的Safari)不再存在這些問題。 (但在撰寫本文時,遺憾的是,IE8仍然廣泛使用,因此使用NFE和Web代碼一般仍然存在問題。)
存取器功能初始化器(ES5 +)
有時功能可以潛入大部分未被注意到; 這是訪問者功能的情況。 這是一個例子:
var obj = {
value: 0,
get f() {
return this.value;
},
set f(v) {
this.value = v;
}
};
console.log(obj.f); // 0
console.log(typeof obj.f); // "number"
注意,當我使用該函數時,我沒有使用()
! 那是因為它是一個屬性的訪問函數 。 我們以正常方式獲取並設置屬性,但在幕後,調用該函數。
您還可以使用Object.defineProperty
, Object.defineProperties
和Object.create
的鮮為人知的第二個參數創建訪問器函數。
箭頭功能表達(ES2015 +)
ES2015為我們帶來了箭頭功能 。 這是一個例子:
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6
看到n => n * 2
隱藏在map()
調用中的東西? 這是一個功能。
關於箭頭功能的一些事情:
他們沒有自己的
this
。 相反,他們關閉了定義它們的上下文。 (它們也關閉了arguments
並且在相關的情況下,super
。)這意味著它們中的這個與創建它們的地方相同,並且不能更改。正如您已經註意到的那樣,您不使用關鍵字
function
; 相反,你使用=>
。
上面的n => n * 2
示例是它們的一種形式。 如果您有多個參數來傳遞函數,則使用parens:
var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6
(請記住, Array#map
將條目作為第一個參數傳遞,將索引作為第二個參數傳遞。)
在這兩種情況下,函數的主體只是一個表達式; 函數的返回值將自動成為該表達式的結果(您不使用顯式return
)。
如果您所做的不僅僅是單個表達式,請使用{}
和顯式return
(如果需要返回值),正常情況下:
var a = [
{first: "Joe", last: "Bloggs"},
{first: "Albert", last: "Bloggs"},
{first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
var rv = a.last.localeCompare(b.last);
if (rv === 0) {
rv = a.first.localeCompare(b.first);
}
return rv;
});
console.log(JSON.stringify(a));
沒有{ ... }
的版本稱為帶有表達體或簡潔體的箭頭函數。 (另外:一個簡潔的箭頭函數。) { ... }
定義主體的那個是帶有函數體的箭頭函數。 (另外:一個冗長的箭頭功能。)
對像初始化程序中的方法聲明(ES2015 +)
ES2015允許更短的形式聲明引用函數的屬性; 它看起來像這樣:
var o = {
foo() {
}
};
ES5及更早版本中的等價物是:
var o = {
foo: function foo() {
}
};
類中的構造函數和方法聲明(ES2015 +)
ES2015為我們帶來了class
語法,包括聲明的構造函數和方法:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
上面有兩個函數聲明:一個用於構造函數,它獲取名稱Person
,另一個用於getFullName
,它是分配給Person.prototype
的函數。
首先,我想糾正Greg: function abc(){}
也是作用域的 - 名稱abc
是在遇到此定義的範圍內定義的。 例:
function xyz(){
function abc(){};
// abc is defined here...
}
// ...but not here
其次,可以結合兩種風格:
var xyz = function abc(){};
xyz
將像往常一樣定義, abc
在所有瀏覽器中都是未定義的,但Internet Explorer - 不依賴於它的定義。 但它將在其內部定義:
var xyz = function abc(){
// xyz is visible here
// abc is visible here
}
// xyz is visible here
// abc is undefined here
如果要在所有瀏覽器上使用別名函數,請使用以下類型的聲明:
function abc(){};
var xyz = abc;
在這種情況下, xyz
和abc
都是同一對象的別名:
console.log(xyz === abc); // prints "true"
使用組合樣式的一個令人信服的理由是函數對象的“名稱”屬性( Internet Explorer不支持 )。 基本上當你定義一個像這樣的函數
function abc(){};
console.log(abc.name); // prints "abc"
其名稱將自動分配。 但是當你定義它時
var abc = function(){};
console.log(abc.name); // prints ""
它的名稱是空的 - 我們創建了一個匿名函數並將其分配給某個變量。
使用組合樣式的另一個好理由是使用簡短的內部名稱來引用自身,同時為外部用戶提供長的非衝突名稱:
// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
// Let it call itself recursively:
shortcut(n - 1);
// ...
// Let it pass itself as a callback:
someFunction(shortcut);
// ...
}
在上面的示例中,我們可以使用外部名稱執行相同操作,但它會過於笨重(並且速度較慢)。
(另一種引用自身的方法是使用arguments.callee
,它仍然相對較長,在嚴格模式下不受支持。)
在內心深處,JavaScript以不同的方式處理兩種語句。 這是一個函數聲明:
function abc(){}
abc
here在當前範圍內的任何位置定義:
// We can call it here
abc(); // Works
// Yet, it is defined down there.
function abc(){}
// We can call it again
abc(); // Works
此外,它通過一個return
聲明提升:
// We can call it here
abc(); // Works
return;
function abc(){}
這是一個函數表達式:
var xyz = function(){};
這裡的xyz
是從賦值點定義的:
// We can't call it here
xyz(); // UNDEFINED!!!
// Now it is defined
xyz = function(){}
// We can call it here
xyz(); // works
函數聲明與函數表達式是Greg證明存在差異的真正原因。
有趣的事實:
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
就個人而言,我更喜歡“函數表達式”聲明,因為這樣我可以控制可見性。 當我定義函數時
var abc = function(){};
我知道我在本地定義了這個函數。 當我定義函數時
abc = function(){};
我知道我在全球範圍內定義它,前提是我沒有在範圍鏈中的任何地方定義abc
。 即使在eval()
使用,這種定義風格也很有彈性。 而定義
function abc(){};
取決於上下文,可能會讓你猜測它實際定義的位置,特別是在eval()
的情況下 - 答案是:它取決於瀏覽器。
兩者都是定義函數的不同方式。不同之處在於瀏覽器如何解釋並將它們加載到執行上下文中。
第一種情況是函數表達式,僅在解釋器到達該行代碼時才加載。因此,如果您執行以下操作,您將收到一個錯誤,即functionOne不是函數。
functionOne();
var functionOne = function() {
// Some code
};
原因是在第一行沒有為functionOne賦值,因此它是未定義的。我們試圖將其稱為函數,因此我們遇到了錯誤。
在第二行,我們將一個匿名函數的引用分配給functionOne。
第二種情況是在執行任何代碼之前加載的函數聲明。因此,如果您喜歡以下內容,則在代碼執行之前加載聲明時不會出現任何錯誤。
functionOne();
function functionOne() {
// Some code
}