在JavaScript中另一个基本概念是函数, 它允许你在一个代码块中存储一段用于处理一个任务的代码,然后在任何你需要的时候用一个简短的命令来调用,而不是把相同的代码写很多次。在本文中,我们将探索函数的基本概念,如基本语法、如何定义和调用、范围和参数。

前提条件: 基本的电脑知识,对HTML与CSS有基本的了解,及已阅读: JavaScript first steps(JS的入门)。
目标: 了解 Javascript 函数背后的基本概念。

我能在哪找到函数?

在 JavaScript中, 你将发现函数无处不在 。事实上, 到目前为止,我们一直在使用函数,只是我们还没很好的讨论它们。然而现在是时候了,让我们开始聊聊函数,并探索它们的语法。

几乎任何时候,只要你使用一个带有一对圆括号()的JavaScript结构,并且你不是在使用一个常见的比如for for循环,while或do…while循环,或者if语句这样的内置语言结构时,那么您就正在使用函数。

浏览器内置函数

在这套课程中我们已经使用了很多浏览器内置函数,当我们操作一个字符串的时候,例如:

var myText = 'I am a string';
var newString = myText.replace('string', 'sausage');
console.log(newString);
// the replace() string function takes a string,
// replaces one substring with another, and returns
// a new string with the replacement made

或者当我们操作一个数组的时候:

var myArray = ['I', 'love', 'chocolate', 'frogs'];
var madeAString = myArray.join(' ');
console.log(madeAString);
// the join() function takes an array, joins
// all the array items together into a single
// string, and returns this new string

或者当我们生成一个随机数时:

var myNumber = Math.random()
// the random() function generates a random
// number between 0 and 1, and returns that
// number

...我们已经使用过函数了!

提示:如果需要,你可以随意将这些代码输入浏览器控制台以便于你熟悉其功能。

JavaScript有许多内置的函数,可以让您做很多有用的事情,而无需自己编写所有的代码。事实上, 许多你调用(运行或者执行的专业词语)浏览器内置函数时调用的代码并不是使用JavaScript来编写——大多数调用浏览器后台的函数的代码,是使用像C++这样更低级的系统语言编写的,而不是像JavaScript这样的web编程语言。

请记住,这些内置浏览器函数不是核心JavaScript语言的一部分——被定义为浏览器API的一部分,它建立在默认语言之上,以提供更多的功能(请参阅本课程的早期部分以获得更多的描述)。我们将在以后的模块中更详细地使用浏览器API。

函数与方法

在我们继续前进之前,我们需要澄清一件事——严格说来,内置浏览器函数并不是函数——它们是方法这听起来有点可怕和令人困惑,但不要担心 ——函数和方法在很大程度上是可互换的,至少在我们的学习阶段是这样的

二者区别在于方法是在对象内定义的函数。浏览器内置函数(方法)和变量(称为属性)存储在结构化对象内,以使代码更加高效,易于处理。

您不需要了解结构化JavaScript对象的内部工作 - 您可以等到我们的后续模块将教你所有关于对象的内部工作以及如何创建自己的模块。现在,我们只想清除任何可能的方法与功能的混淆 - 当您查看Web上可用的相关资源时,您可能会遇到这两个术语。

自定义函数

您在过去的课程中还看到很多定制功能 - 在代码中定义的功能,而不是在浏览器中。每当您看到一个自定义名称后面都带有括号,那么您使用的是自定义函数. 在我们循环文章中random-canvas-circles.html示例(另见完整的源代码)中,我们包括一个如下所示的自定义函数:draw()

function draw() {
  ctx.clearRect(0,0,WIDTH,HEIGHT);
  for (var i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255,0,0,0.5)';
    ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
    ctx.fill();
  }
}

 

该函数在<canvas>元素中绘制100个随机圆每次我们想要这样做,我们可以使用这个函数来调用这个功能

draw();

而不是每次我们想重复一遍,都要写出所有的代码。函数可以包含任何您喜欢的代码 - 甚至可以从内部函数调用其他函数。以上函数例如调用random()函数三次,由以下代码定义:

function random(number) {
  return Math.floor(Math.random()*number);
}

我们需要这个函数,因为浏览器的内置Math.random()函数只生成一个0到1之间的随机十进制数。我们想要一个0到一个指定数字之间的随机整数。

调用函数

现在你可能很清楚这一点,但仅仅为了防止……,要在函数定义之后,实际使用它,你必须运行或调用它。这是通过将函数名包含在代码的某个地方,后跟圆括号来完成的。

function myFunction() {
  alert('hello');
}

myFunction()
// calls the function once

匿名函数

您可能会以稍微不同的方式看到定义和调用的函数。到目前为止,我们刚刚创建了如下函数:

function myFunction() {
  alert('hello');
}

但是您也可以创建一个没有名称的函数:

function() {
  alert('hello');
}

这个函数叫做匿名函数 — 它没有函数名! I它也不会自己做任何事情。 你通常使用匿名函数以及事件处理程序, 例如,如果单击相关按钮,以下操作将在函数内运行代码:

var myButton = document.querySelector('button');

myButton.onclick = function() {
  alert('hello');
}

上述示例将要求<button> 在页面上提供可用于选择并单击元素。您在整个课程中已经看到过这种结构了几次,您将在下一篇文章中了解更多信息并在其中使用。

你还可以将匿名函数分配为变量的值,例如:

var myGreeting = function() {
  alert('hello');
}

现在可以使用以下方式调用此函数:

myGreeting();

有效地给变量一个名字;还可以将该函数分配为多个变量的值,例如:

var anotherGreeting = function() {
  alert('hello');
}

现在可以使用以下任一方法调用此函数

myGreeting();
anotherGreeting();

但这只会令人费解,所以不要这样做!创建功能时,最好只要坚持下列形式:

function myGreeting() {
  alert('hello');
}

您将主要使用匿名函数来运行负载的代码以响应事件触发(如点击按钮) - 使用事件处理程序。再次,这看起来像这样:

myButton.onclick = function() {
  alert('hello');
  // I can put as much code
  // inside here as I want
}

匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。

函数参数

一些函数需要在调用它们时指定参数 - 这些值需要包含在函数括号内,它需要正确地完成其工作。

Note: 参数有时称为参数,属性(properties)或甚至属性(attributes)

例如,浏览器的内置Math.random()函数不需要任何参数。当被调用时,它总是返回0到1之间的随机数:

var myNumber = Math.random();

浏览器的内置字符串replace()函数需要两个参数:在主字符串中查找的子字符串,以及用以下替换该字符串的子字符串:

var myText = 'I am a string';
var newString = myText.replace('string', 'sausage');

Note:当您需要指定多个参数时,它们以逗号分隔。

还应该注意,有时参数是可选的 - 您不必指定它们。如果没有,该功能一般会采用某种默认行为。作为示例,数组join()函数的参数是可选的:

var myArray = ['I', 'love', 'chocolate', 'frogs'];
var madeAString = myArray.join(' ');
// returns 'I love chocolate frogs'
var madeAString = myArray.join();
// returns 'I,love,chocolate,frogs'

如果没有包含参数来指定加入/分隔符,默认情况下会使用逗号

函数作用域和冲突

我们来谈一谈 scope即作用域 — 处理函数时一个非常重要的概念。当你创建一个函数时,函数内定义的变量和其他东西都在它们自己的单独的范围内, 意味着它们被锁在自己独立的隔间中, 不能从函数外代码其它函数内访问。

所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。

JavaScript由于各种原因而建立,但主要是由于安全性和组织性。有时您不希望变量可以在代码中的任何地方访问 - 您从其他地方调用的外部脚本可能会开始搞乱您的代码并导致问题,因为它们恰好与代码的其他部分使用了相同的变量名称,造成冲突。这可能是恶意的,或者是偶然的。

例如,假设您有一个HTML文件,它调用两个外部JavaScript文件,并且它们都有一个使用相同名称定义的变量和函数:

<!-- Excerpt from my HTML -->
<script src="first.js"></script>
<script src="second.js"></script>
<script>
  greeting();
</script>
// first.js
var name = 'Chris';
function greeting() {
  alert('Hello ' + name + ': welcome to our company.');
}
// second.js
var name = 'Zaptec';
function greeting() {
  alert('Our company is called ' + name + '.');
}

这两个函数都使用 greeting() 形式调用,但是你只能访问到 second.js文件的greeting()函数。second.js 在源代码中后应用到HTML中,所以它的变量和函数覆盖了 first.js 中的。

Note: 您可以参考这个例子 running live on GitHub (查看完整 源代码).

将代码锁定在函数中的部分避免了这样的问题,并被认为是最佳实践。

这有点像一个动物园。狮子,斑马,老虎和企鹅都保留在自己的园子中,只能拿到到它们园子中的东西 —— 与其函数作用域相同。如果他们能进入其他园子,就会出现问题。不同的动物会在不熟悉的栖息地内感到真的不舒服 - 一只狮子或老虎会在企鹅的水多的,冰冷的的领域中感到可怕。最糟糕的是,狮子和老虎可能会尝试吃企鹅!

动物园管理员就像全局作用域 - 他或她有钥匙访问每个园子,重新投喂食物,照顾生病的动物等。

主动学习: 和 scope 玩耍

我们来看一个真正的例子来展示范围

  1. 首先,制作我们的function-scope.html示例的本地副本。它包含两个函数a()b(),和三个变量—— xyz——其中两个在函数中被定义,另一个被定义在全局作用域内。它还包含一个名为output()的函数,它接收一个参数,并将其输出到页面的一个段落中。
  2. 在浏览器和文本编辑器中打开示例。
  3. 在浏览器开发工具中打开JavaScript控制台。在JavaScript控制台中,输入以下命令:
output(x);

您应该看到变量x输出到屏幕的值

       4.现在尝试在您的控制台中输入以下内容

output(y);
output(z);

这两个都应该返回错误沿“ ReferenceError:y未定义 ”。这是为什么?由于函数作用域 - yz锁定在函数a()b()函数中,所以output()从全局作用域调用时无法访问它们。

        5.但是,从另一个函数里面调用什么呢?尝试编辑a()b()所以他们看起来像这样:

function a() {
  var y = 2;
  output(y);
}

function b() {
  var z = 3;
  output(z);
}

保存代码并重新加载到浏览器中,然后尝试从JavaScript控制台调用a()b()函数:

a();
b();

您应该看到页面中输出的yz值。这样就没问题,因为output()函数在其他函数的内部被调用 - 在这种情况下,输出变量的定义和函数的调用都在同一个作用域中(译者注:即函数作用域)。output()它可以从任何地方被调用,因为它在全局作用域中被定义。

       6.现在尝试更新您的代码,如下所示:

function a() {
  var y = 2;
  output(x);
}

function b() {
  var z = 3;
  output(x);
}

再次保存并重新加载,并在JavaScript控制台中再次尝试:

a();
b();

函数 a()b()都应该输出x---1的值。这些没有问题,因为即使output()调用与x定义不在同一个作用域内,但x是一个全局变量,所以在所有代码中都可用。

7.最后,尝试更新您的代码,如下所示:

function a() {
  var y = 2;
  output(z);
}

function b() {
  var z = 3;
  output(y);
}

再次保存并重新加载,并在JavaScript控制台中再次尝试:

a();
b();

这次a()b()调用都会返回那个令人讨厌的 "ReferenceError: z is not defined" error — 这是因为output()函数的调用和输出变量的定义不在同一个函数作用域内 - 变量对这些函数调用是不可见的。

注意:相同的范围规则不适用于循环(for(){...})和条件块(if(){...}) - 它们看起来非常相似,但它们不一样!小心不要让这些困惑。

注意:ReferenceError:“x”未定义错误是您遇到的最常见的错误。如果您收到此错误,并且确定您已经定义了该问题的变量,请检查它的范围。

函数内部的函数

请记住,您可以从任何地方调用函数,甚至可以在另一个函数中调用函数。这通常被用作保持代码整理的方式 - 如果您有一个复杂的函数,如果将其分解成几个子函数,它更容易理解:

function myBigFunction() {
  var myValue;

  subFunction1();
  subFunction2();
  subFunction3();
}

function subFunction1() {
  console.log(myValue);
}

function subFunction2() {
  console.log(myValue);
}

function subFunction3() {
  console.log(myValue);
}

只需确保在函数内使用的值正确的范围. 上面的例子会抛出一个错误ReferenceError:MyValue没有被定义,因为尽管myValue变量与函数调用的作用域相同, 但函数定义内没有定义 - 调用函数时运行的实际代码。为了使这个工作,你必须将值作为参数传递给函数,如下所示:

function myBigFunction() {
  var myValue = 1;
      
  subFunction1(myValue);
  subFunction2(myValue);
  subFunction3(myValue);
}

function subFunction1(value) {
  console.log(value);
}

function subFunction2(value) {
  console.log(value);
}

function subFunction3(value) {
  console.log(value);
}

总结

 

本文探讨了函数背后的基本概念,为之后的学习奠定了基础。下一步,我们将进行实践,并带你一步步建立起你自己的函数。

 

参见

文档标签和贡献者

最后编辑者: BusyToDie,