函數(shù)是代碼語句的容器,可以使用括號 () 運(yùn)算符調(diào)用。調(diào)用時可以在括號內(nèi)傳遞參數(shù),以便函數(shù)調(diào)用時函數(shù)中的語句可以訪問某些值。
在以下代碼中,我們使用 new 運(yùn)算符創(chuàng)建 addNumbers 函數(shù) objectone 的兩個版本,另一個版本使用更常見的文字模式。兩者都需要兩個參數(shù)。在每種情況下,我們都調(diào)用該函數(shù),并在括號中傳遞參數(shù) () 運(yùn)算符。
示例:sample76.html
<script> var addNumbersA = new Function('num1', 'num2', 'return num1 + num2'); console.log(addNumbersA(2, 2)); // Logs 4. // Could also be written the literal way, which is much more common. var addNumbersB = function (num1, num2) { return num1 + num2; }; console.log(addNumbersB(2, 2)); // Logs 4. </script>
函數(shù)可用于返回值、構(gòu)造對象或作為簡單運(yùn)行代碼的機(jī)制。 JavaScript 對函數(shù)有多種用途,但就其最基本的形式而言,函數(shù)只是可執(zhí)行語句的唯一范圍。
函數(shù)() 參數(shù)
Function() 構(gòu)造函數(shù)采用無限數(shù)量的參數(shù),但 Function() 構(gòu)造函數(shù)期望的最后一個參數(shù)是一個字符串,其中包含構(gòu)成函數(shù)體的語句。在最后一個之前傳遞給構(gòu)造函數(shù)的任何參數(shù)都可用于正在創(chuàng)建的函數(shù)。還可以以逗號分隔的字符串形式發(fā)送多個參數(shù)。
在下面的代碼中,我將 Function() 構(gòu)造函數(shù)的用法與實(shí)例化函數(shù)對象的更常見模式進(jìn)行了對比。
示例:sample77.html
<script> var addFunction = new Function('num1', 'num2', 'return num1 + num2'); /* Alternately, a single comma-separated string with arguments can be the first parameter of the constructor, with the function body following. */ var timesFunction = new Function('num1,num2', 'return num1 * num2'); console.log(addFunction(2, 2), timesFunction(2, 2)); // Logs '4 4' // Versus the more common patterns for instantiating a function: var addFunction = function (num1, num2) { return num1 + num2; }; // Expression form. function addFunction(num1, num2) { return num1 + num2; } // Statement form. </script>
不建議或通常不直接利用 Function() 構(gòu)造函數(shù),因?yàn)?JavaScript 將使用 eval() 來解析包含函數(shù)邏輯的字符串。許多人認(rèn)為 eval() 是不必要的開銷。如果使用它,則代碼設(shè)計中很可能存在缺陷。
使用 Function() 構(gòu)造函數(shù)而不使用 new 關(guān)鍵字與僅使用構(gòu)造函數(shù)創(chuàng)建函數(shù)對象具有相同的效果(new Function(‘x’,’return x’) 與 函數(shù)((‘x’,’返回x’))。
直接調(diào)用 Function() 構(gòu)造函數(shù)時不會創(chuàng)建閉包。
Function() 屬性和方法
函數(shù)對象具有以下屬性(不包括繼承的屬性和方法):
屬性(Function.prototype;):
- 原型
函數(shù)對象實(shí)例屬性和方法
函數(shù)對象實(shí)例具有以下屬性和方法(不包括繼承的屬性和方法):
實(shí)例屬性 (var myFunction = function(x, y, z) {}; myFunction.length;):
- 參數(shù)
- 構(gòu)造函數(shù)
- 長度
實(shí)例方法 (var myFunction = function(x, y, z) {}; myFunction.toString();):
- apply()
- call()
- toString()
函數(shù)總是返回一個值
雖然可以創(chuàng)建一個函數(shù)來簡單地執(zhí)行代碼語句,但函數(shù)返回一個值也很常見。在以下示例中,我們從 sayHi 函數(shù)返回一個字符串。
示例:sample78.html
<script> var sayHi = function () { return 'Hi'; }; console.log(sayHi()); // Logs "Hi". </script>
如果函數(shù)沒有指定返回值,則返回 undefined。在以下示例中,我們調(diào)用 yelp 函數(shù),該函數(shù)將字符串“yelp”記錄到控制臺,而不顯式返回值。
示例:sample79.html
<script> var yelp = function () { console.log('I am yelping!'); // Functions return undefined even if we don't. } /* Logs true because a value is always returned, even if we don't specifically return one. */ console.log(yelp() === undefined); </script>
這里要記住的概念是,即使您沒有顯式提供要返回的值,所有函數(shù)都會返回一個值。如果不指定返回值,則返回值為 undefined。
函數(shù)是一等公民(不僅僅是語法,還有值)
在 JavaScript 中,函數(shù)就是對象。這意味著函數(shù)可以存儲在變量、數(shù)組或?qū)ο笾小4送猓瘮?shù)可以傳遞給函數(shù)或從函數(shù)返回。函數(shù)具有屬性,因?yàn)樗且粋€對象。所有這些因素使得函數(shù)成為 JavaScript 中的一等公民。
示例:sample80.html
<script> // Functions can be stored in variables (funcA), arrays (funcB), and objects (funcC). var funcA = function () { }; // Called like so: funcA() var funcB = [function () { } ]; // Called like so: funcB[0]() var funcC = { method: function () { } }; // too.method() or funcC['method']() // Functions can be sent to and sent back from functions. var funcD = function (func) { return func }; var runFuncPassedToFuncD = funcD(function () { console.log('Hi'); }); runFuncPassedToFuncD(); // Functions are objects, which means they can have properties. var funcE = function () { }; funcE.answer = 'yup'; // Instance property. console.log(funcE.answer); // Logs 'yup'. </script>
認(rèn)識到函數(shù)是一個對象,因此也是一個值,這一點(diǎn)至關(guān)重要。它可以像 JavaScript 中的任何其他表達(dá)式一樣傳遞或增強(qiáng)。
將參數(shù)傳遞給函數(shù)
參數(shù)是在調(diào)用函數(shù)時將值傳遞到函數(shù)作用域的工具。在下面的示例中,我們調(diào)用 addFunction()。由于我們已預(yù)定義它采用兩個參數(shù),因此在其范圍內(nèi)可以使用兩個附加值。
示例:sample81.html
<script> var addFunction = function (number1, number2) { var sum = number1 + number2; return sum; } console.log(addFunction(3, 3)); // Logs 6. </script>
與其他一些編程語言相比,在 JavaScript 中省略參數(shù)是完全合法的,即使函數(shù)已被定義為接受這些參數(shù)。缺少的參數(shù)僅被賦予值 undefined。當(dāng)然,如果省略參數(shù)值,該函數(shù)可能無法正常工作。
如果向函數(shù)傳遞意外參數(shù)(創(chuàng)建函數(shù)時未定義的參數(shù)),則不會發(fā)生錯誤。并且可以從 arguments 對象訪問這些參數(shù),該對象可用于所有函數(shù)。
this 和 arguments 值可用于所有函數(shù)
在所有函數(shù)的范圍和主體內(nèi),this 和 arguments 值可用。
arguments 對象是一個類似數(shù)組的對象,包含傳遞給函數(shù)的所有參數(shù)。在下面的代碼中,即使我們在定義函數(shù)時放棄指定參數(shù),我們也可以依賴傳遞給函數(shù)的 arguments 數(shù)組來訪問在調(diào)用時發(fā)送的參數(shù)。
示例:sample82.html
<script> var add = function () { return arguments[0] + arguments[1]; }; console.log(add(4, 4)); // Returns 8. </script>
this 關(guān)鍵字,傳遞給所有函數(shù),是對包含該函數(shù)的對象的引用。正如您所期望的,對象中包含的作為屬性(方法)的函數(shù)可以使用 this 來獲取對父對象的引用。當(dāng)函數(shù)定義在全局作用域時,this 的值為全局對象。查看以下代碼并確保您了解 this 返回的內(nèi)容。
示例:sample83.html
<script> var myObject1 = { name: 'myObject1', myMethod: function () { console.log(this); } }; myObject1.myMethod(); // Logs 'myObject1'. var myObject2 = function () { console.log(this); }; myObject2(); // Logs window. </script>
arguments.callee 屬性
arguments 對象有一個名為 callee 的屬性,它是對當(dāng)前正在執(zhí)行的函數(shù)的引用。此屬性可用于從函數(shù)范圍內(nèi)引用該函數(shù) (arguments.callee)a 自引用。在下面的代碼中,我們使用此屬性來獲取對調(diào)用函數(shù)的引用。
示例:sample84.html
<script> var foo = function foo() { console.log(arguments.callee); // Logs foo() // callee could be used to invoke recursively the foo function (arguments.callee()) } (); </script>
當(dāng)需要遞歸調(diào)用函數(shù)時,這非常有用。
函數(shù)實(shí)例 length 屬性和 arguments.length
arguments 對象具有唯一的 length 屬性。雖然您可能認(rèn)為這個 length 屬性將為您提供已定義參數(shù)的數(shù)量,但它實(shí)際上提供了在調(diào)用期間發(fā)送到函數(shù)的參數(shù)數(shù)量。
示例:sample85.html
<script> var myFunction = function (z, s, d) { return arguments.length; }; console.log(myFunction()); // Logs 0 because no parameters were passed to the function. </script>
使用所有 Function() 實(shí)例的 length 屬性,我們實(shí)際上可以獲取函數(shù)期望的參數(shù)總數(shù)。
示例:sample86.html
<script> var myFunction = function (z, s, d, e, r, m, q) { return myFunction.length; }; console.log(myFunction()); // Logs 7. </script>
arguments.length 屬性在 JavaScript 1.4 中已棄用,但可以從函數(shù)對象的 length 屬性訪問發(fā)送到函數(shù)的參數(shù)數(shù)量。接下來,您可以通過利用 callee 屬性來首先獲取對正在調(diào)用的函數(shù)的引用 (arguments.callee.length) 來獲取長度值。
重新定義函數(shù)參數(shù)
函數(shù)參數(shù)可以直接在函數(shù)內(nèi)部重新定義,也可以使用 arguments 數(shù)組。看一下這段代碼:
示例:sample87.html
<script> var foo = false; var bar = false; var myFunction = function (foo, bar) { arguments[0] = true; bar = true; console.log(arguments[0], bar); // Logs true true. } myFunction(); </script>
請注意,我可以使用 arguments 索引或直接為參數(shù)重新分配新值來重新定義 bar 參數(shù)的值。
在函數(shù)完成之前返回函數(shù)(取消函數(shù)執(zhí)行)
通過使用帶或不帶值的 return 關(guān)鍵字,可以在調(diào)用期間隨時取消函數(shù)。在下面的示例中,如果參數(shù)未定義或不是數(shù)字,我們將取消 add 函數(shù)。
示例:sample88.html
<script> var add = function (x, y) { // If the parameters are not numbers, return error. if (typeof x !== 'number' || typeof y !== 'number') { return 'pass in numbers'; } return x + y; } console.log(add(3, 3)); // Logs 6. console.log(add('2', '2')); // Logs 'pass in numbers'. </script>
這里要講的概念是,您可以在函數(shù)執(zhí)行過程中的任何時刻使用 return 關(guān)鍵字來取消函數(shù)的執(zhí)行。
定義函數(shù)(語句、表達(dá)式或構(gòu)造函數(shù))
函數(shù)可以用三種不同的方式定義:函數(shù)構(gòu)造函數(shù)、函數(shù)語句或函數(shù)表達(dá)式。在下面的示例中,我演示了每種變體。
示例:sample89.html
<script> /* Function constructor: The last parameter is the function logic, everything before it is a parameter. */ var addConstructor = new Function('x', 'y', 'return x + y'); // Function statement. function addStatement(x, y) { return x + y; } // Function expression. var addExpression = function (x, y) { return x + y; }; console.log(addConstructor(2, 2), addStatement(2, 2), addExpression(2, 2)); // Logs '4 4 4'. </script>
有人說函數(shù)還有第四種類型的定義,稱為“命名函數(shù)表達(dá)式”。命名函數(shù)表達(dá)式只是一個包含名稱的函數(shù)表達(dá)式(例如, var add = function add(x, y) {return x+y})。
調(diào)用函數(shù)(函數(shù)、方法、構(gòu)造函數(shù)或 call() 和 apply())
使用四種不同的場景或模式調(diào)用函數(shù)。
- 作為函數(shù)
- 作為一種方法
- 作為構(gòu)造函數(shù)
- 使用 apply() 或 call()
在下面的示例中,我們將檢查每種調(diào)用模式。
示例:sample90.html
<script> // Function pattern. var myFunction = function () { return 'foo' }; console.log(myFunction()); // Logs 'foo'. // Method pattern. var myObject = { myFunction: function () { return 'bar'; } } console.log(myObject.myFunction()); // Logs 'bar'. // Constructor pattern. var Cody = function () { this.living = true; this.age = 33; this.gender = 'male'; this.getGender = function () { return this.gender; }; } var cody = new Cody(); // Invoke via the Cody constructor. console.log(cody); // Logs the cody object and properties. // apply() and call() pattern. var greet = { runGreet: function () { console.log(this.name, arguments[0], arguments[1]); } } var cody = { name: 'cody' }; var lisa = { name: 'lisa' }; // Invoke the runGreet function as if it were inside of the cody object. greet.runGreet.call(cody, 'foo', 'bar'); // Logs 'cody foo bar'. // Invoke the runGreet function as if it were inside of the lisa object. greet.runGreet.apply(lisa, ['foo', 'bar']); // Logs 'lisa foo bar'. /* Notice the difference between call() and apply() in how parameters are sent to the function being invoked. */ </script>
確保您了解所有四種調(diào)用模式,因?yàn)槟鷮⒂龅降拇a可能包含其中任何一種。
匿名函數(shù)
匿名函數(shù)是沒有給出標(biāo)識符的函數(shù)。匿名函數(shù)主要用于將函數(shù)作為參數(shù)傳遞給另一個函數(shù)。
示例:sample91.html
<script> // function(){console.log('hi');}; // Anonymous function, but no way to invoke it. // Create a function that can invoke our anonymous function. var sayHi = function (f) { f(); // Invoke the anonymous function. } // Pass an anonymous function as a parameter. sayHi(function () { console.log('hi'); }); // Logs 'hi'. </script>
自調(diào)用函數(shù)表達(dá)式
函數(shù)表達(dá)式(實(shí)際上是除從 Function() 構(gòu)造函數(shù)創(chuàng)建的函數(shù)之外的任何函數(shù))可以在定義后使用括號運(yùn)算符立即調(diào)用。在以下示例中,我們創(chuàng)建 sayWord() 函數(shù)表達(dá)式,然后立即調(diào)用該函數(shù)。這被認(rèn)為是一個自調(diào)用函數(shù)。
示例:sample92.html
<script> var sayWord = function () { console.log('Word 2 yo mo!'); } (); // Logs 'Word 2 yo mo!' </script>
自調(diào)用匿名函數(shù)語句
可以創(chuàng)建自調(diào)用的匿名函數(shù)語句。這稱為自調(diào)用匿名函數(shù)。在下面的示例中,我們創(chuàng)建了幾個立即調(diào)用的匿名函數(shù)。
示例:sample93.html
<script> // Most commonly used/seen in the wild. (function (msg) { console.log(msg); })('Hi'); // Slightly different, but achieving the same thing: (function (msg) { console.log(msg) } ('Hi')); // The shortest possible solution. !function sayHi(msg) { console.log(msg); } ('Hi'); // FYI, this does NOT work! // function sayHi() {console.log('hi');}(); </script>
根據(jù) ECMAScript 標(biāo)準(zhǔn),如果要立即調(diào)用函數(shù),則需要在函數(shù)兩邊加上括號(或?qū)⒑瘮?shù)轉(zhuǎn)換為表達(dá)式的任何內(nèi)容)。
函數(shù)可以嵌套
函數(shù)可以無限期地嵌套在其他函數(shù)中。在下面的代碼示例中,我們將 goo 函數(shù)封裝在 bar 函數(shù)內(nèi)部,該函數(shù)位于 foo 函數(shù)內(nèi)部。
示例:sample94.html
<script> var foo = function () { var bar = function () { var goo = function () { console.log(this); // Logs reference to head window object. } (); } (); } (); </script>
這里的簡單概念是函數(shù)可以嵌套,并且嵌套的深度沒有限制。
請記住,嵌套函數(shù)的 this 的值將是 JavaScript 1.5、ECMA-262 第 3 版中的頭對象(Web 瀏覽器中的 window 對象)。
將函數(shù)傳遞給函數(shù)以及從函數(shù)返回函數(shù)
如前所述,函數(shù)是 JavaScript 中的一等公民。由于函數(shù)是一個值,并且函數(shù)可以傳遞任何類型的值,因此函數(shù)可以傳遞給函數(shù)。接受和/或返回其他函數(shù)的函數(shù)有時稱為“高階函數(shù)”。
在下面的代碼中,我們將一個匿名函數(shù)傳遞給 foo 函數(shù),然后立即從 foo 函數(shù)返回。變量 bar 所指向的正是這個匿名函數(shù),因?yàn)?foo 接受并返回匿名函數(shù)。
示例:sample95.html
<script> // Functions can be sent to, and sent back from, functions. var foo = function (f) { return f; } var bar = foo(function () { console.log('Hi'); }); bar(); // Logs 'Hi'. </script>
因此,當(dāng)調(diào)用 bar 時,它會調(diào)用傳遞給 foo() 函數(shù)的匿名函數(shù),然后從 foo() 函數(shù)傳回并從 bar 引用多變的。所有這些都是為了展示函數(shù)可以像任何其他值一樣傳遞的事實(shí)。
在定義函數(shù)語句之前調(diào)用函數(shù)語句(又名函數(shù)提升)
函數(shù)語句可以在執(zhí)行期間在其實(shí)際定義之前調(diào)用。這有點(diǎn)奇怪,但您應(yīng)該意識到這一點(diǎn),以便您可以利用它,或者至少知道當(dāng)您遇到它時會發(fā)生什么。在下面的示例中,我在定義 sayYo() 和 sum() 函數(shù)語句之前調(diào)用它們。
示例:sample96.html
<script> // Example 1 var speak = function () { sayYo(); // sayYo() has not been defined yet, but it can still be invoked, logs 'yo'. function sayYo() { console.log('Yo'); } } (); // Invoke // Example 2 console.log(sum(2, 2)); // Invoke sum(), which is not defined yet, but can still be invoked. function sum(x, y) { return x + y; } </script>
發(fā)生這種情況是因?yàn)樵诖a運(yùn)行之前,函數(shù)語句被解釋并添加到執(zhí)行堆棧/上下文中。確保您在使用函數(shù)語句時意識到這一點(diǎn)。
定義為函數(shù)表達(dá)式的函數(shù)不會被提升。僅提升函數(shù)語句。
函數(shù)可以調(diào)用自身(又名遞歸)
函數(shù)調(diào)用自身是完全合法的。事實(shí)上,這經(jīng)常被用在眾所周知的編碼模式中。在下面的代碼中,我們啟動 countDownFrom 函數(shù),然后該函數(shù)通過函數(shù)名稱 countDownFrom 調(diào)用自身。本質(zhì)上,這會創(chuàng)建一個從 5 倒數(shù)到 0 的循環(huán)。
示例:sample97.html
<script> var countDownFrom = function countDownFrom(num) { console.log(num); num--; // Change the parameter value. if (num < 0) { return false; } // If num < 0 return function with no recursion. // Could have also done arguments.callee(num) if it was an anonymous function. countDownFrom(num); }; countDownFrom(5); // Kick off the function, which logs separately 5, 4, 3, 2, 1, 0. </script>
您應(yīng)該意識到,函數(shù)調(diào)用自身(也稱為遞歸)或重復(fù)執(zhí)行此操作是很自然的。
結(jié)論
函數(shù)是 JavaScript 最常用的方面之一,希望您現(xiàn)在對如何使用它們有了更好的了解。