1. 主页
  2. 文档
  3. 学习君土脚本
  4. 创建君土脚本代码块
  5. 函数-可复用代码块

函数-可复用代码块

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

我能在哪找到函数?

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

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

系统内置函数

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

定 字符串0 = '这是一个字符串';
定 新字符串 = 字符串0.替('字符串', '函数');
控制台.日志(新字符串); // 输出 '这是一个函数'
// 替()函数接受一个字符串,
// 将一个子字符串替换为另一个子字符串,并返回
// 一个新的字符串

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

定 数组0 = ['我', '喜欢', '编写', '程序'];
定 生产字符串 = 数组0.串('');
控制台.日志(生产字符串);  // 输出 '我喜欢编写程序'
// 串()函数将一个数组(或一个类数组对象)
// 的所有元素连接成一个字符串并返回这个字
// 符串。如果数组只有一个项目,那么将返回
// 该项目而不使用分隔符。

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

定 数0 = 算.随机()
// 算.随机()函数返回一个浮点数,  
// 伪随机数在范围从0到小于1,也就是说,
// 从0(包括0)往上,但是不包括1(排除1),
// 然后您可以缩放到所需的范围。

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

提示:如果需要,你可以随意将这些代码输入君土集成开发环境以便于你熟悉其功能。

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

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

函数与方法

程序员把函数称为对象方法的一部分。你还不必了解君土脚本中已建构的对象在更深层次上是如何运作的——你可以等到下一小节,我们会教给你有关对象运作方式的一切。在我们继续前进之前,我们需要澄清一些有关方法和函数概念之间可能存在的误会——当你在网络上浏览相关信息的时候,你很可能会碰上这两个术语。

到目前为止我们所使用的内置代码同属于这两种形式:函数和方法。你可以在这里查看内置函数,内置对象以及其相关方法的完整列表。

严格说来,内置系统函数并不是函数——它们是方法。这听起来有点可怕和令人困惑,但不要担心 ——函数和方法在很大程度上是可互换的,至少在我们的学习阶段是这样的。

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

自定义函数

您在过去的课程中还看到很多定制功能 – 在代码中定义的功能,而不是在运行系统中。每当您看到一个自定义名称后面都带有括号,那么您使用的是自定义函数. 在我们的循环文章中使用 断/*break*/ 退出循环示例中,我们包括一个如下所示的自定义函数:查找(查找名字: 文)

  务 查找(查找名字: 文) {
    为 (定 甲 = 0; 甲 < 联系人.长; 甲++) {
      定 联系人分解 = 联系人[甲].分(':');
      若 (联系人分解[0] === 查找名字) {
        控制台.日志(联系人分解[0] + '的号码是' + 联系人分解[1] + '。');
        断;
      } 别 若 (甲 === 联系人.长 - 1) {
        控制台.日志('没有找到联系人');
      }
    }
  }

该函数在联系人中查找指定名字的电话号码。每次我们想要这样做,我们可以使用这个函数来调用这个功能

查找('张三');

而不是每次我们想重复一遍,都要写出所有的代码。函数可以包含任何您喜欢的代码 – 甚至可以从内部函数调用其他函数。

调用函数

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

务 函数0() {
  控制台.日志('你好');
}

函数0()
// 调用一次函数

匿名函数

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

务 函数0() {
  控制台.日志('你好');
}

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

务() {
  控制台.日志('你好');
}

这个函数叫做匿名函数 — 它没有函数名! 它也不会自己做任何事情。 你通常将匿名函数与事件处理程序一起使用, 例如,设置一个超时,以下操作将在函数内运行代码:

设置超时(务 () {
  控制台.日志('世界');
}, 1000);

控制台.日志('你好');

上述示例将在一秒(1000 毫秒)后,在控制台显示’世界’字符串。

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

定 问候 = 务() {
  控制台.日志('你好');
}

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

问候();

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

定 另一个问候 = 问候;

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

问候();
另一个问候();

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

务 问候() {
  控制台.日志('你好');
}

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

设置超时(务 () {
  控制台.日志('世界');
  // 我可以添加更多的代码在这里
}, 1000);

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

函数参数

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

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

定 数0 = 算.随机();

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

定 字符串0 = '这是一个字符串';
定 新字符串 = 字符串0.替('字符串', '函数');

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

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

定 数组0 = ['我', '喜欢', '编写', '程序'];
定 生成字符串 = 数组0.串('');
控制台.日志(生成字符串);  // 输出 '我喜欢编写程序'
生成字符串 = 数组0.串();
控制台.日志(生成字符串);  // 输出 '我,喜欢,编写,程序'

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

函数作用域和冲突

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

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

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

例如,假如我们有以下两代码文件并且它们都有一个使用相同名称定义的变量和函数:

函数作用域和冲突1.式
函数作用域和冲突2.式
// 函数作用域和冲突1.式
定 名称 = '老虎';
务 问候() {
  控制台.日志('你好,我是' + 名称);
}
// 函数作用域和冲突2.式
定 名称 = '狮子';
务 问候() {
  控制台.日志('你好,我是' + 名称);
}

这两个函数都使用 问候() 形式调用,导致错误——函数实现重复。另外,第二次尝试使用  关键字定义 名称 变量导致了一个错误——无法重新声明块范围变量“名称”。

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

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

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

主动学习: 和 作用域 玩耍

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

  1. 首先,制作我们示例的本地副本。它包含两个函数子()丑(),和三个变量—— ——其中两个在函数中被定义,另一个被定义在全局作用域内。它还包含一个名为输出()的函数,它接收一个参数,并将其显示到控制台。
定 甲 = 1;

务 子() {
  定 乙 = 2;
}

务 丑() {
  定 丙 = 3;
}

务 输出(值0: 数) {
  控制台.日志(值0);
}
  1. 君土集成开发环境中打开示例。
  1. 现在尝试输入以下代码
输出(乙);
输出(丙);

这应该提示错误:找不到名称“乙”和找不到名称“丙”。这是为什么?由于函数作用域 – 乙和丙被锁定在函数子()丑()函数中,所以输出()从全局作用域调用时无法访问它们。

  1. 但是,从另一个函数里面调用回怎么样呢?尝试编辑子()丑()所以他们看起来像这样:
务 子() {
  定 乙 = 2;
  输出(乙);
}

务 丑() {
  定 丙 = 3;
  输出(丙);
}

增加以下代码,保存、编译,再运行:

子();
丑();

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

  1. 现在尝试更新您的代码,如下所示:
务 子() {
  定 乙 = 2;
  输出(甲);
}

务 丑() {
  定 丙 = 3;
  输出(甲);
}

保留以下代码,再次保存、编译和运行:

子();
丑();

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

  1. 最后,尝试更新您的代码,如下所示:
务 子() {
  定 乙 = 2;
  输出(丙);
}

务 丑() {
  定 丙 = 3;
  输出(乙);
}

保留以下代码,再次保存、编译和运行:

子();
丑();

这次子()丑()调用都会有问题提示 找不到名称“找不到名称“乙” — 这是因为输出()函数的调用和输出变量的定义不在同一个函数作用域内 – 变量对这些函数调用是不可见的。

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

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

函数内部的函数

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

务 大函数() {
  定 值0 = 1;

  子函数1();
  子函数2();
  子函数3();
}

务 子函数1() {
  控制台.日志(值0);
}

务 子函数2() {
  控制台.日志(值0);
}

务 子函数3() {
  控制台.日志(值0);
}

要确保函数调取的数值处在有效的作用域内。上面的例子中会产生一个错误提示,找不到名称“值0”,因为尽管值0变量与函数调用指令处在同一个作用域中, 但它却没有在函数内被定义 —— 实际代码在调用函数时就开始运行了。为了使代码正确运作,你必须将值作为参数传递给函数,如下所示:

务 大函数() {
  定 值0 = 1;

  子函数1(值0);
  子函数2(值0);
  子函数3(值0);
}

务 子函数1(值1:数) {
  控制台.日志(值1);
}

务 子函数2(值1:数) {
  控制台.日志(值1);
}

务 子函数3(值1:数) {
  控制台.日志(值1);
}

总结

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