跳至内容

JavaScript/函数

来自维基教科书,开放的书籍,为开放的世界



函数是一段代码块,它解决一个特定的问题并将其解决方案返回给调用语句。该函数存在于其自身上下文中。因此,函数将大型程序划分为较小的“砖块”,这些砖块构建了软件以及软件开发过程。

// define a function
function <function_name> (<parameters>) {
  <function_body>
}

// call a function
<variable> = <function_name> (<arguments>);

JavaScript 支持软件开发范式 函数式编程。函数是一种从 Object 派生的数据类型;它们可以绑定到变量、作为参数传递以及从其他函数返回,就像任何其他数据类型一样。

函数可以通过三种主要方式构建。第一个版本可以进一步缩写;见下文

传统方式

"use strict";

// conventional declaration (or 'definition')
function duplication(p) {
  return p + "! " + p + "!";
}

// call the function
const ret = duplication("Go");
alert(ret);

通过变量和表达式进行构建

"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 种变体。对于它们的调用,也有 3 种变体。声明和调用彼此独立,您可以随意组合它们。

传统的调用变体使用函数名称,后面跟着圆括号 ( )。圆括号内包含函数的参数(如果有的话)。

"use strict";

function duplication(p) {
  return p + "! " + p + "!";
}

// the conventional invocation method
const ret = duplication("Go");
alert(ret);

如果脚本在浏览器中运行,则还有另外两种可能性。它们使用浏览器提供的 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 + "!";
}

立即执行函数

[编辑 | 编辑源代码]

到目前为止,我们已经看到了声明调用这两个独立步骤。还有一种语法变体允许将两者结合起来。它以围绕函数声明的圆括号为特征,后面跟着 () 来调用该声明。

"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")
);

这种语法被称为 立即执行函数表达式 (IIFE)

当调用函数时,声明阶段的参数将被调用的参数替换。在上面的声明中,我们使用了变量名 p 作为参数名。调用函数时,我们主要使用字面量“Go”作为参数。在运行时,它会替换函数中所有出现的 p。上面的示例演示了这种技术。

按值调用

[编辑 | 编辑源代码]

这种替换是 '按值' 而不是'按引用'完成的。参数的原始值的副本将传递给函数。如果该复制的值在函数内部发生变化,则函数外部的原始值不会发生变化。

"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));

当示例运行时,它表明在调用 duplication 之后,函数所做的更改不仅体现在返回值中。此外,原始对象 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:这种参数传递技术的命名在不同的语言中并不一致。有时它被称为“按共享调用”。维基百科 提供了一个概述。

注意 2:JavaScript 参数传递所描述的结果与使用关键字 const 的结果相当,该关键字将变量声明为常量。此类变量无法更改。但是,如果它们引用对象,则可以更改对象的属性。

默认值

[编辑 | 编辑源代码]

如果函数调用时传递的参数少于其包含的参数,则多余的参数保持未定义。但是,您可以通过在函数签名中分配一个值来定义此情况的默认值。缺失的参数将获得这些值作为其默认值。

"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

可变数量的参数

[编辑 | 编辑源代码]

对于某些函数,它们接受不同数量的参数是“正常”的。例如,考虑一个显示姓名的函数。firstNamefamilyName 在任何情况下都必须给出,但也可以显示 academicTitletitleOfNobility。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"));

必须单独检查每个可能未给出的参数。

“剩余”参数

[编辑 | 编辑源代码]

如果可选参数的处理在结构上相同,则可以通过使用剩余运算符语法来简化代码 - 主要与循环结合使用。该功能的语法在函数签名中包含三个点 - 就像在扩展语法中一样。

它是如何工作的?作为函数调用的一部分,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 属性。

其功能与上述剩余语法相当。主要区别在于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 语句返回给调用程序。

其语法为return <expression>,其中<expression> 为可选。

函数运行,直到它遇到这样的return 语句(或者发生未捕获的异常,或者在最后一个语句之后)。<expression> 可以是一个简单的任何数据类型的变量,例如return 5,或者是一个复杂的表达式,例如return myString.length,或者完全省略:return

如果return 语句中没有<expression>,或者根本没有达到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);
}

箭头函数 (=>)

[编辑 | 编辑源代码]

箭头函数是上述传统函数语法的简洁替代方案。它们缩写了某些语言元素,省略了其他语言元素,并且与原始语法相比,只有很少的语义区别

它们始终是匿名的,但可以分配给变量。

"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。箭头函数显示e 以及一段简短的文本。

"use strict";

const myArray = ['a', 'b', 'c'];

myArray.forEach(e => alert("The element of the array is: " + e));

其他编程语言在诸如匿名函数 或 lambda 表达式之类的术语下提供箭头函数的概念。

递归调用

[编辑 | 编辑源代码]

函数可以调用其他函数。在实际应用程序中,这种情况经常发生。

当函数调用自身时,会出现特殊情况。这被称为递归调用。当然,这隐含了无限循环的危险。您必须更改参数中的某些内容以避免此障碍。

通常,当应用程序处理树结构(例如物料清单DOM 树家谱信息)时,会出现对这种递归调用的需求。在这里,我们展示了易于实现的 阶乘 计算的数学问题。

阶乘是小于或等于某个数字 的所有正整数的乘积,写成 。例如, 。它可以通过从 的循环来解决,但也有递归解。 的阶乘是已经计算出的 的阶乘乘以 ,或者用公式表示: 。这个想法导致了函数的相应递归构造

"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));

只要 大于 ,脚本将再次调用 factorial,但这次以 作为参数。因此,参数收敛到 。当 被到达时,这是 factorial 函数第一次不再被再次调用。它返回 的值。这个数字乘以来自 factorial 上次调用的下一个更大的数字。乘法的结果被返回到 factorial 的上一次调用,... 。

... 在另一个页面上提供(点击此处)。

另请参阅

[编辑 | 编辑源代码]
华夏公益教科书