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

扩展阅读

编辑

<=目录