函數是代碼語句的容器,可以使用括號 () 運算符調用。調用時可以在括號內傳遞參數,以便函數調用時函數中的語句可以訪問某些值。
在以下代碼中,我們使用 new 運算符創建 addNumbers 函數 objectone 的兩個版本,另一個版本使用更常見的文字模式。兩者都需要兩個參數。在每種情況下,我們都調用該函數,并在括號中傳遞參數 () 運算符。
示例: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>
函數可用于返回值、構造對象或作為簡單運行代碼的機制。 JavaScript 對函數有多種用途,但就其最基本的形式而言,函數只是可執行語句的唯一范圍。
函數() 參數
Function() 構造函數采用無限數量的參數,但 Function() 構造函數期望的最后一個參數是一個字符串,其中包含構成函數體的語句。在最后一個之前傳遞給構造函數的任何參數都可用于正在創建的函數。還可以以逗號分隔的字符串形式發送多個參數。
在下面的代碼中,我將 Function() 構造函數的用法與實例化函數對象的更常見模式進行了對比。
示例: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() 構造函數,因為 JavaScript 將使用 eval() 來解析包含函數邏輯的字符串。許多人認為 eval() 是不必要的開銷。如果使用它,則代碼設計中很可能存在缺陷。
使用 Function() 構造函數而不使用 new 關鍵字與僅使用構造函數創建函數對象具有相同的效果(new Function(‘x’,’return x’) 與 函數((‘x’,’返回x’))。
直接調用 Function() 構造函數時不會創建閉包。
Function() 屬性和方法
函數對象具有以下屬性(不包括繼承的屬性和方法):
屬性(Function.prototype;):
- 原型
函數對象實例屬性和方法
函數對象實例具有以下屬性和方法(不包括繼承的屬性和方法):
實例屬性 (var myFunction = function(x, y, z) {}; myFunction.length;):
- 參數
- 構造函數
- 長度
實例方法 (var myFunction = function(x, y, z) {}; myFunction.toString();):
- apply()
- call()
- toString()
函數總是返回一個值
雖然可以創建一個函數來簡單地執行代碼語句,但函數返回一個值也很常見。在以下示例中,我們從 sayHi 函數返回一個字符串。
示例:sample78.html
<script> var sayHi = function () { return 'Hi'; }; console.log(sayHi()); // Logs "Hi". </script>
如果函數沒有指定返回值,則返回 undefined。在以下示例中,我們調用 yelp 函數,該函數將字符串“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>
這里要記住的概念是,即使您沒有顯式提供要返回的值,所有函數都會返回一個值。如果不指定返回值,則返回值為 undefined。
函數是一等公民(不僅僅是語法,還有值)
在 JavaScript 中,函數就是對象。這意味著函數可以存儲在變量、數組或對象中。此外,函數可以傳遞給函數或從函數返回。函數具有屬性,因為它是一個對象。所有這些因素使得函數成為 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>
認識到函數是一個對象,因此也是一個值,這一點至關重要。它可以像 JavaScript 中的任何其他表達式一樣傳遞或增強。
將參數傳遞給函數
參數是在調用函數時將值傳遞到函數作用域的工具。在下面的示例中,我們調用 addFunction()。由于我們已預定義它采用兩個參數,因此在其范圍內可以使用兩個附加值。
示例:sample81.html
<script> var addFunction = function (number1, number2) { var sum = number1 + number2; return sum; } console.log(addFunction(3, 3)); // Logs 6. </script>
與其他一些編程語言相比,在 JavaScript 中省略參數是完全合法的,即使函數已被定義為接受這些參數。缺少的參數僅被賦予值 undefined。當然,如果省略參數值,該函數可能無法正常工作。
如果向函數傳遞意外參數(創建函數時未定義的參數),則不會發生錯誤。并且可以從 arguments 對象訪問這些參數,該對象可用于所有函數。
this 和 arguments 值可用于所有函數
在所有函數的范圍和主體內,this 和 arguments 值可用。
arguments 對象是一個類似數組的對象,包含傳遞給函數的所有參數。在下面的代碼中,即使我們在定義函數時放棄指定參數,我們也可以依賴傳遞給函數的 arguments 數組來訪問在調用時發送的參數。
示例:sample82.html
<script> var add = function () { return arguments[0] + arguments[1]; }; console.log(add(4, 4)); // Returns 8. </script>
this 關鍵字,傳遞給所有函數,是對包含該函數的對象的引用。正如您所期望的,對象中包含的作為屬性(方法)的函數可以使用 this 來獲取對父對象的引用。當函數定義在全局作用域時,this 的值為全局對象。查看以下代碼并確保您了解 this 返回的內容。
示例: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 的屬性,它是對當前正在執行的函數的引用。此屬性可用于從函數范圍內引用該函數 (arguments.callee)a 自引用。在下面的代碼中,我們使用此屬性來獲取對調用函數的引用。
示例: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>
當需要遞歸調用函數時,這非常有用。
函數實例 length 屬性和 arguments.length
arguments 對象具有唯一的 length 屬性。雖然您可能認為這個 length 屬性將為您提供已定義參數的數量,但它實際上提供了在調用期間發送到函數的參數數量。
示例: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() 實例的 length 屬性,我們實際上可以獲取函數期望的參數總數。
示例: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 中已棄用,但可以從函數對象的 length 屬性訪問發送到函數的參數數量。接下來,您可以通過利用 callee 屬性來首先獲取對正在調用的函數的引用 (arguments.callee.length) 來獲取長度值。
重新定義函數參數
函數參數可以直接在函數內部重新定義,也可以使用 arguments 數組。看一下這段代碼:
示例: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 索引或直接為參數重新分配新值來重新定義 bar 參數的值。
在函數完成之前返回函數(取消函數執行)
通過使用帶或不帶值的 return 關鍵字,可以在調用期間隨時取消函數。在下面的示例中,如果參數未定義或不是數字,我們將取消 add 函數。
示例: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>
這里要講的概念是,您可以在函數執行過程中的任何時刻使用 return 關鍵字來取消函數的執行。
定義函數(語句、表達式或構造函數)
函數可以用三種不同的方式定義:函數構造函數、函數語句或函數表達式。在下面的示例中,我演示了每種變體。
示例: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>
有人說函數還有第四種類型的定義,稱為“命名函數表達式”。命名函數表達式只是一個包含名稱的函數表達式(例如, var add = function add(x, y) {return x+y})。
調用函數(函數、方法、構造函數或 call() 和 apply())
使用四種不同的場景或模式調用函數。
- 作為函數
- 作為一種方法
- 作為構造函數
- 使用 apply() 或 call()
在下面的示例中,我們將檢查每種調用模式。
示例: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>
確保您了解所有四種調用模式,因為您將遇到的代碼可能包含其中任何一種。
匿名函數
匿名函數是沒有給出標識符的函數。匿名函數主要用于將函數作為參數傳遞給另一個函數。
示例: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>
自調用函數表達式
函數表達式(實際上是除從 Function() 構造函數創建的函數之外的任何函數)可以在定義后使用括號運算符立即調用。在以下示例中,我們創建 sayWord() 函數表達式,然后立即調用該函數。這被認為是一個自調用函數。
示例:sample92.html
<script> var sayWord = function () { console.log('Word 2 yo mo!'); } (); // Logs 'Word 2 yo mo!' </script>
自調用匿名函數語句
可以創建自調用的匿名函數語句。這稱為自調用匿名函數。在下面的示例中,我們創建了幾個立即調用的匿名函數。
示例: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>
根據 ECMAScript 標準,如果要立即調用函數,則需要在函數兩邊加上括號(或將函數轉換為表達式的任何內容)。
函數可以嵌套
函數可以無限期地嵌套在其他函數中。在下面的代碼示例中,我們將 goo 函數封裝在 bar 函數內部,該函數位于 foo 函數內部。
示例:sample94.html
<script> var foo = function () { var bar = function () { var goo = function () { console.log(this); // Logs reference to head window object. } (); } (); } (); </script>
這里的簡單概念是函數可以嵌套,并且嵌套的深度沒有限制。
請記住,嵌套函數的 this 的值將是 JavaScript 1.5、ECMA-262 第 3 版中的頭對象(Web 瀏覽器中的 window 對象)。
將函數傳遞給函數以及從函數返回函數
如前所述,函數是 JavaScript 中的一等公民。由于函數是一個值,并且函數可以傳遞任何類型的值,因此函數可以傳遞給函數。接受和/或返回其他函數的函數有時稱為“高階函數”。
在下面的代碼中,我們將一個匿名函數傳遞給 foo 函數,然后立即從 foo 函數返回。變量 bar 所指向的正是這個匿名函數,因為 foo 接受并返回匿名函數。
示例: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>
因此,當調用 bar 時,它會調用傳遞給 foo() 函數的匿名函數,然后從 foo() 函數傳回并從 bar 引用多變的。所有這些都是為了展示函數可以像任何其他值一樣傳遞的事實。
在定義函數語句之前調用函數語句(又名函數提升)
函數語句可以在執行期間在其實際定義之前調用。這有點奇怪,但您應該意識到這一點,以便您可以利用它,或者至少知道當您遇到它時會發生什么。在下面的示例中,我在定義 sayYo() 和 sum() 函數語句之前調用它們。
示例: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>
發生這種情況是因為在代碼運行之前,函數語句被解釋并添加到執行堆棧/上下文中。確保您在使用函數語句時意識到這一點。
定義為函數表達式的函數不會被提升。僅提升函數語句。
函數可以調用自身(又名遞歸)
函數調用自身是完全合法的。事實上,這經常被用在眾所周知的編碼模式中。在下面的代碼中,我們啟動 countDownFrom 函數,然后該函數通過函數名稱 countDownFrom 調用自身。本質上,這會創建一個從 5 倒數到 0 的循環。
示例: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>
您應該意識到,函數調用自身(也稱為遞歸)或重復執行此操作是很自然的。
結論
函數是 JavaScript 最常用的方面之一,希望您現在對如何使用它們有了更好的了解。