JavaScript/子程序和对象
函數(function)是 JavaScript 最重要部分之一。你可以用它整理代碼、減少代碼長度或建立參數。
無參數的函數
[编辑]無參數的函數是十分簡單的。
<script language="javascript">
function helloworld()
{
alert("Hello, World!");
}
</script>
當我們要在 HTML 文檔內調用這個函數,則用:
helloworld();
現在組裝入網頁:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML>
<HEAD>
<TITLE>Some Page</TITLE>
<script language="javascript">
function helloworld()
{
alert("Hello World!");
}
</script>
</HEAD>
<BODY>
<script lang="javascript">
helloworld();
</script>
</BODY>
</HTML>
不難吧?在這個問題上,我們只採用函數去節省不必要的代码輸入(而事實上上述示例確實增加了代码輸入,但只要把握這個思想,大多數情況下都是合用的),但當你學到帶有參數的函數時,那才是函數發光的原因!
帶參數的函數
[编辑]我們就以一個簡單的例子開始,然後才深入瞭解。 包含有过程和结果返回。
function addsubtract(thenumber)
{
if(thenumber>5){
thenumber--;
}
if(thenumber<5){
thenumber++;
}
return thenumber;
}
這個程序以數字為變數。如果thenumber大於5,那麼thenumber就減一;如果thenumber小於5,thenumber則加一。
分解程序:
function addsubtract(thenumber){......}
你應該見過這個東西,只不過是有點不一樣而已。現在在函數名稱後面多了一個(thenumber)
。那裡就是我們定義一個將要使用的參數的地方(記得定義變量嗎?它們有相似的一面,但定義參數我們不用var都可以)。
if(thenumber>5){......}
這是假定(if)語句。如果圓括號內的語句是真(true),那麼就繼續執行波形括號內的語句。相似的方法有:
X>Y(如果X大於Y) X<Y(如果X小於Y) X>=Y(如果X大於或等於Y) X<=Y(如果X小於或等於Y) X==Y(如果X等於Y)
將有更多類似方法,將在日後說明。
thenumber--;
假如Javascript是你的第一編程語言,你將不知道這是什麼意思。這十分簡單,就是對變量thenumber
減一。這是thenumber = thenumber - 1;
的縮寫版本。
thenumber++;
與上述一樣,只不過不是減而是加。
return thenumber;
這表示返回thenumber的值,後述將詳細提及。
下面的代碼就自己研究吧:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML>
<HEAD>
<TITLE>Some Page</TITLE>
<script language="javascript">
function addsubtract(thenumber)
{
if(thenumber>5){
thenumber--;
}
if(thenumber<5){
thenumber++;
}
return thenumber;
}
</script>
</HEAD>
<BODY>
<script language="javascript">
var number = addsubtract(6);
alert(number);
</script>
</BODY>
</HTML>
還有...
var number = addsubtract(6);
這就是方便的地方了。number的值將為5,因為程序addsubtract的參數為6。
函数概论
[编辑]函数存在于它自己的上下文(context)中。
// define a function
function <function_name> (<parameters>) {
<function_body>
}
// call a function
<variable> = <function_name> (<arguments>);
JavaScript支持函数式编程。函数派生自Object的一种数据类型。是头等公民。
函数声明
[编辑]函数有3种声明方式。传统方式为:
"use strict";
// conventional declaration (or 'definition')
function duplication(p) {
return p + "! " + p + "!";
}
// call the function
const ret = duplication("Go");
alert(ret);
Construction via a variable and an expression:
"use strict";
// assign the function to a variable
let duplication = function (p) {
return p + "! " + p + "!";
};
const ret = duplication("Go");
alert(ret);
通过new
运算符构造函数:
"use strict";
// using the 'new' constructor
let duplication = new Function ("p",
"return p + '! ' + p + '!'");
const ret = duplication("Go");
alert(ret);
调用
[编辑]有3种函数调用的办法。传统办法是函数名后面跟随一对圆括号( )
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// the conventional invocation method
const ret = duplication("Go");
alert(ret);
如果脚本运行在浏览器中,有2种调用方式。使用浏览器提供的window
对象。
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// via 'call'
let ret = duplication.call(window, "Go");
alert(ret);
// via 'apply'
ret = duplication.apply(window, ["Go"]);
alert(ret);
如果函数名后未跟随圆括号,那么表达式只是函数自身,而不是函数调用:
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
alert(duplication); // 'function duplication (p) { ... }'
提升
[编辑]函数声明会自动提升到作用域顶部。因此在源代码中可以先于函数声明就调用函数。
"use strict";
// use a function above (in source code) its declaration
const ret = duplication("Go");
alert(ret);
function duplication(p) {
return p + "! " + p + "!";
}
立即被调函数
[编辑]可以把函数声明与函数调用写在一起,称“立即被调函数表达式”(Immediately Invoked Function Expression, IIEF):
"use strict";
alert( // 'alert' to show the result
// declaration plus invocation
(function (p) {
return p + "! " + p + "!";
})("Go") // ("Go"): invocation with the argument "Go"
);
alert(
// the same with 'arrow' syntax
((p) => {
return p + "! " + p + "!";
})("Gooo")
);
实参
[编辑]函数调用时,函数的形参会被实参替换。
传值调用
[编辑]函数的实参值复制给形参。形参值改编不影响实参。
"use strict";
// there is one parameter 'p'
function duplication(p) {
// In this example, we change the parameter's value
p = "NoGo";
alert("In function: " + p);
return p + "! " + p + "!";
};
let x = "Go";
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Variable: " + x);
对于非基本类型的对象,这种传值调用有令人惊讶的效果。如果函数修改了对象的数学,这种改编在函数之外也是可见的。
"use strict";
function duplication(p) {
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? Yes.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
为什么会这样?它与基础数据类型有不同的行为吗?并不!
函数接收了对象引用的拷贝。 因此,在函数内引用了同一个对象。对象本身只存在一次,但有两个(相同的)对该对象的引用。对象的属性是否由一个引用或另一个引用修改并没有什么区别。
另一个后果是引用本身的修改(例如,通过创建新对象)在外部例程中将不可见。直观上这与基础数据类型有相同行为。对新对象的引用存储在原引用的“拷贝”中。 现在我们不仅有两个引用(具有不同的值)而且还有两个不同的对象。
"use strict";
function duplication(p) {
// modify the reference by creating a new object
p = {};
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
注 1:这种参数传递技术的命名在不同语言中的使用并不一致。 有时它被称为“共享调用”(call-by-sharing)。
注 2:上述描述的 JavaScript 参数传递的后果与使用关键字 const
的后果相当。后者将变量声明为“constant”。 此类变量无法更改。然而,如果它们引用了一个对象,则该对象的属性可以更改。
缺省值
[编辑]如果调用函数时使用的参数少于其声明的形式参数,则多余的形式参数将保持未定义状态。 但是您可以通过在函数签名内赋一个值来定义这种情况的缺省值。 缺少对应实参的形式参数将得到它们的缺省值。
"use strict";
// two nearly identical functions; only the signature is slightly different
function f1(a, b) {
alert("The second parameter is: " + b)
};
function f2(a, b = 10) {
alert("The second parameter is: " + b)
};
// identical invocations; different results
f1(5); // undefined
f1(5, 100); // 100
f2(5); // 10
f2(5, 100); // 100
可变数量的实参
[编辑]对于某些函数,它们涉及不同数量的参数是“正常”的。 例如,考虑一个显示名称的函数。 在任何情况下都必须给出名字和姓氏,但也可能必须显示学术头衔或贵族头衔。 JavaScript 提供了不同的可能性来处理这种情况。
单独检查
[编辑]可以检查“正常”参数以及附加参数以确定它们是否包含值。
"use strict";
function showName(firstName, familyName, academicTitle, titleOfNobility) {
"use strict";
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
if (academicTitle) {
ret = ret + ", " + academicTitle;
}
if (titleOfNobility) {
ret = ret + ", " + titleOfNobility;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D."));
alert(showName("Tom"));
必须单独检查可能未给出的每个参数。
“其余”参数
[编辑]如果可选参数的处理在结构上是相同的,则可以通过使用剩余运算符(rest operator)语法来简化代码 - 主要与循环结合使用。 该功能的语法由函数签名中的三个点组成 - 就像扩展语法(spread syntax)一样。
它是如何工作的?作为函数调用的一部分,JavaScript引擎将给定的可选参数组合到一个数组中。(请注意,调用脚本不使用数组。)该数组作为最后一个参数提供给被调函数。
"use strict";
// the three dots (...) introduces the 'rest syntax'
function showName(firstName, familyName, ...titles) {
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
for (const title of titles) {
ret = ret + ", " + title;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D.", "Duke"));
alert(showName("Tom"));
调用的第三个和所有后续参数被收集到一个数组中,该数组可在函数中作为最后一个参数使用。 这允许使用循环并简化函数的源代码。
'arguments'关键字
[编辑]与C系列其他编程语言一样,JavaScript在函数中提供了关键字arguments
。 它是一个类似数组的对象,包含函数调用的所有给定参数。 您可以循环访问它或使用它的length
属性。
它的功能与上面的“剩余语法”(rest syntax)相当。 主要区别在于 arguments
包含“所有”参数,而“其余语法”不一定影响所有参数。
"use strict";
function showName(firstName, familyName, academicTitles, titlesOfNobility) {
// handle ALL parameters with a single keyword
for (const arg of arguments) {
alert(arg);
}
}
showName("Mike", "Spencer", "Ph.D.", "Duke");
Return
[编辑]如果没有返回值,则undefined
被返回.
"use strict";
function duplication(p) {
if (typeof p === 'object') {
return; // return value: 'undefined'
}
else if (typeof p === 'string') {
return p + "! " + p + "!";
}
// implicit return with 'undefined'
}
let arg = ["Go", 4, {a: 1}];
for (let i = 0; i < arg.length; i++) {
const ret = duplication(arg[i]);
alert(ret);
}
箭头函数(=>)
[编辑]箭头函数(arrow function)是上述传统函数语法的紧凑可选形式。
"use strict";
// original conventional syntax
function duplication(p) {
return p + "! " + p + "!";
}
// 1. remove keyword 'function' and function name
// 2. introduce '=>' instead
// 3. remove 'return'; the last value is automatically returned
(p) => {
p + "! " + p + "!"
}
// remove { }, if only one statement
(p) => p + "! " + p + "!"
// Remove parameter parentheses if it is only one parameter
// -----------------------------
p => p + "! " + p + "!" // that's all!
// -----------------------------
alert(
(p => p + "! " + p + "!")("Go")
);
这是另一个使用数组的示例。forEach
方法循环遍历数组。被放入箭头函数的单个参数e
。
"use strict";
const myArray = ['a', 'b', 'c'];
myArray.forEach(e => alert("The element of the array is: " + e));
其他编程语言在匿名函数或lambda表达式等术语下提供箭头函数的概念。
递归调用
[编辑]"use strict";
function factorial(n) {
if (n > 0) {
const ret = n * factorial(n-1);
return ret;
} else {
// n = 0; 0! is 1
return 1;
}
}
const n = 4;
alert(factorial(n));
擴展閱讀
[编辑]- 十分鐘搞定JavaScript(Javascript in Ten Minutes):快速講解對像形的JavaScript。 (目前已經失連)
- MDN: Functions
- MDN: Rest parameter