Learn web development

函数-可复用代码块

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

Prerequisites: Basic computer literacy, a basic understanding of HTML and CSS, JavaScript first steps.
Objective: To understand the fundamental concepts behind JavaScript functions.

我能在哪找到函数?

在 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。

函数与方法

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

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

You don't need to learn about the inner workings of structured JavaScript objects yet — you can wait until our later module that will teach you all about the inner workings of objects, and how to create your own. For now, we just wanted to clear up any possible confusion of method versus function — you are likely to meet both terms as you look at the available related resources across the Web.

自定义函数

您在过去的课程中还看到很多定制功能 - 在代码中定义的功能,而不是在浏览器中。每当您看到一个自定义名称后面都带有括号,那么您使用的是自定义函数. In our random-canvas-circles.html example (see also the full source code) from our loops article, we included a custom draw() function that looked like this:

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

This function draws 100 random circles inside an <canvas> element. Every time we want to do that, we can just invoke the function with this

draw();

rather than having to write all that code out again every time we want to repeat it. And functions can contain whatever code you like — you can even call other functions from inside functions. The above function for example calls the random() function three times, which is defined by the following code:

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

We needed this function because the browser's built-in Math.random() function only generates a random decimal number between 0 and 1. We wanted a random whole number between 0 and a specified number.

调用函数

你现在可能很清楚, but just in case ... to actually use a function after it has been defined, you've got to run — or invoke — it. This is done by including the name of the function in the code somewhere, followed by parentheses.

function myFunction() {
  alert('hello');
}
myFunction()
// calls the function once

匿名函数

You may see functions defined and invoked in slightly different ways. So far we have just created a function like so:

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

But you can also create a function that doesn't have a name:

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

这个函数叫做匿名函数 — 它没有函数名! It also won't do anything on its own. 你通常使用匿名函数以及事件处理程序, for example the following would run the code inside the function whenever the associated button is clicked:

var myButton = document.querySelector('button');
myButton.onclick = function() {
  alert('hello');
}

The above example would require there to be a <button> element available on the page to select and click. You've already seen this structure a few times throughout the course, and you'll learn more about and see it in use in the next article.

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

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

This function could now be invoked using:

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
}

函数参数

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

Note: 参数有时称为参数,属性或甚至属性

As an example, the browser's built-in Math.random() function doesn't require any parameters. When called, it always returns a random number between 0 and 1:

var myNumber = Math.random();

The browser's built-in string replace() function however needs two parameters — the substring to find in the main string, and the substring to replace that string with:

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

Note: When you need to specify multiple parameters, they are separated by commas.

It should also be noted that sometimes parameters are optional — you don't have to specify them. If you don't, the function will generally adopt some kind of default behavior. As an example, the array join() function's parameter is optional:

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

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

功能范围和冲突

Let's talk a bit about scope — a very important concept when dealing with functions. 当你创建一个函数时,函数内定义的变量和其他东西都在它们自己的单独的范围内, 意味着它们被锁在自己独立的隔间中, 不能访问内部的其他函数或函数之外的代码.

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

JavaScript is set up like this for various reasons — but mainly because of security and organization. Sometimes you don't want variables to be accessible from everywhere in the code — external scripts that you call in from elsewhere could start to mess with your code and cause problems because they happen to be using the same variable names as other parts of the code, causing conflicts. This might be done maliciously, or just by accident.

For example, say you have an HTML file that is calling in two external JavaScript files, and both of them have a variable and a function defined that use the same name:

<!-- 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 + '.');
}

Both functions you want to call are called greeting(), but you can only ever access the second.js file's greeting() function — it is applied to the HTML later on in the source code, so its variable and function overwrite the ones in first.js.

Note: You can see this example running live on GitHub (see also the source code).

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

It is a bit like a zoo. The lions, zebras, tigers, and penguins are kept in their own enclosures, and only have access to the things inside their enclosures — in the same manner as the function scopes. If they were able to get into other enclosures, problems would occur. At best, different animals would feel really uncomfortable inside unfamiliar habitats — a lion or tiger would feel terrible inside the penguins' watery, icy domain. At worst, the lions and tigers might try to eat the penguins!

The zoo keeper is like the global scope — he or she has the keys to access every enclosure, to restock food, tend to sick animals, etc.

Active learning: Playing with scope

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

  1. First, make a local copy of our function-scope.html example. This contains two functions called a() and b(), and three variables — x, y, and z — two of which are defined inside the functions, and one in the global scope. It also contains a third function called output(), which takes a single parameter and outputs it in a paragraph on the page.
  2. Open the example up in a browser and in your text editor.
  3. Open the JavaScript console in your browser developer tools. In the JavaScript console, enter the following command:
    output(x);
    You should see the value of variable x output to the screen.
  4. Now try entering the following in your console
    output(y);
    output(z);
    Both of these should return an error along the lines of "ReferenceError: y is not defined". Why is that? Because of function scope — y and z are locked inside the a() and b() functions, so output() can't access them when called from the global scope.
  5. However, what about when it's called from inside another function? Try editing a() and b() so they look like this:
    function a() {
      var y = 2;
      output(y);
    }
    function b() {
      var z = 3;
      output(z);
    }
    Save the code and reload it in your browser, then try calling the a() and b() functions from the JavaScript console:
    a();
    b();
    You should see the y and z values output in the page. This works fine, as the output() function is being called inside the other functions — in the same scope as the variables it is printing are defined in, in each case. output() itself is available from anywhere, as it is defined in the global scope.
  6. Now try updating your code like this:
    function a() {
      var y = 2;
      output(x);
    }
    function b() {
      var z = 3;
      output(x);
    }
    再次保存并重新加载,并在JavaScript控制台中再次尝试:
    a();
    b();
    Both the a() and b() call should output the value of x — 1. These work fine because even though the output() calls are not in the same scope as x is defined in, x is a global variable so is available inside all code, everywhere.
  7. Finally, try updating your code like this:
    function a() {
      var y = 2;
      output(z);
    }
    function b() {
      var z = 3;
      output(y);
    }
    再次保存并重新加载,并在JavaScript控制台中再次尝试:
    a();
    b();
    This time the a() and b() calls will both return that annoying "ReferenceError: z is not defined" error — 这是因为在同一个函数范围内没有定义output()调用和它们尝试打印的变量 - 变量对这些函数调用是无效的。

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

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

函数内部的函数

Keep in mind that you can call a function from anywhere, even inside another function.  This is often used as a way to keep code tidy — if you have a big complex function, it is easier to understand if you break it down into several sub-functions:

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

总结

 

本文探讨了函数背后的基本概念,为下一步的实践开辟了道路,并介绍了建立自己的自定义功能的步骤。

 

See also

文档标签和贡献者