1. 主页
  2. 文档
  3. 学习君土脚本
  4. 君土脚本对象入门
  5. 模块

模块

介绍

从ECMAScript 2015开始,JavaScript引入了模块的概念。君土脚本也沿用这个概念。

模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用出/*export*/形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用引/*import*/形式之一。

模块是自声明的;两个模块之间的关系是通过在文件级别上使用建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于Node.js的CommonJS和服务于Web应用的Require.js

君土脚本与ECMAScript 2015一样,任何包含顶级或者的文件都被当成一个模块。相反,没有任何顶级或者声明的文件被视为脚本,其内容在全局作用域中可用(因此模块也可用)。

导出

导出声明

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加关键字来导出。

字串验证.式

出 型 字串验证 {
  可接受乎(字0: 文): 两;
}

邮编验证.式

引 { 字串验证 } 自 "./验证";

出 常 数字则式 = /^[0-9]+$/;

出 类 邮编验证 具 字串验证 {
  可接受乎(邮编: 文) {
    回 邮编.长 === 5 && 数字则式.试(邮编);
  }
}

导出语句

导出语句很便利,因为我们可能需要对导出的部分重命名,所以上面的例子可以这样改写:

引 { 字串验证 } 自 "./验证";

出 常 数字则式 = /^[0-9]+$/;

类 邮编验证 具 字串验证 {
  可接受乎(邮编: 文) {
    回 邮编.长 === 5 && 数字则式.试(邮编);
  }
}

出 { 邮编验证 };
出 { 邮编验证 作 主要验证 };

重新导出

我们经常会去扩展其它模块,并且只导出那个模块的部分内容。 重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。

基础解析整数邮编验证.式

出 类 基础解析整数邮编验证 {
  可接受乎(邮编: 文) {
      回 邮编.长 === 5 && 析整(邮编).转字() === 邮编;
  }
}

// 导出原先的验证器但做了重命名
出 {邮编验证 作 基础则式邮编验证} 自 "./邮编验证";

或者一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:出 * 自 "模块"

全部验证.式

出 * 自 "./字串验证"; // 导出 型 字串验证
出 * 自 "./基础解析整数邮编验证"; // 导出 类 基础解析整数邮编验证
出 * 自 "./邮编验证";  // 导出 类 邮编验证

导入

模块的导入操作与导出一样简单。 可以使用以下形式之一来导入其它模块中的导出内容。

导入一个模块中的某个导出内容

引 { 邮编验证 } 自 "./邮编验证";

定 验证器 = 启 邮编验证();

可以对导入内容重命名

引 { 邮编验证 作 验证} 自 "./邮编验证";

定 验证器 = 启 验证();

将整个模块导入到一个变量,并通过它来访问模块的导出部分

引 * 作 验证 自 "./邮编验证";
定 验证器 = 启 验证.邮编验证();

具有副作用的导入模块

尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:

引 "./我的模块.js";

默认导出

每个模块都可以有一个默/*default*/导出。 默认导出使用关键字标记;并且一个模块只能够有一个导出。 需要使用一种特殊的导入形式来导入导出。

导出十分便利。 类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。

邮编验证.式

出 默 类 邮编验证 {
  静 数字则式 = /^[0-9]+$/;
  可接受乎(邮编: 文) {
    回 邮编.长 === 5 && 邮编验证.数字则式.试(邮编);
  }
}

测试.式

引 验证 自 "./邮编验证";

定 验证0 = 启 验证();

或者

静态邮编验证.式

常 数字则式 = /^[0-9]+$/;

出 默 务(邮编: 文) {
  回 邮编.长 === 5 && 数字则式.试(邮编);
}

测试.式

引 验证 自 "./静态邮编验证";

定 字串数组 = ["你好", "98052", "101"];

// 使用 函数 验证
字串数组.每(s => {
  控制台.日志(`"${s}" ${验证(s) ? " 匹配" : " 不匹配"}`);
});

导出也可以是一个值

一二三.式

出 默 "123";

日志.式

引 数字 自 "./一二三";

控制台.日志(数字); // "123"

出 = 和 引 = 需()

CommonJS和AMD都有一个导出对象的概念,它包含了一个模块的所有导出内容。

它们也支持把导出替换为一个自定义对象。 默认导出就好比这样一个功能;然而,它们却并不相互兼容。 君土脚本模块支持出 =语法以支持传统的CommonJS和AMD的工作流模型。

出 =语法定义一个模块的导出对象。 它可以是类,接口,命名空间,函数或枚举。

若要导入一个使用了出 =的模块时,必须使用君土脚本提供的特定语法引 模块 = 需("模块")

邮编验证.式

常 数字则式 = /^[0-9]+$/;

类 邮编验证 {
  可接受乎(邮编: 文) {
    回 邮编.长 === 5 && 数字则式.试(邮编);
  }
}
出 = 邮编验证;

测试.式

引 邮编 自 "./邮编验证";

定 字串数组 = ["你好", "98052", "101"];

// 使用的验证器
定 验证 = 启 邮编();

// 显示每个字符串是否通过验证
字串数组.每(串0 => {
  控制台.日志(`"${串0}" - ${验证.可接受乎(串0) ? " 匹配" : " 不匹配"}`);
});

创建模块结构指导

尽可能地在顶层导出

用户应该更容易地使用你模块导出的内容。 嵌套层次过多会变得难以处理,因此仔细考虑一下如何组织你的代码。

从你的模块中导出一个命名空间就是一个增加嵌套的例子。 虽然命名空间有时候有它们的用处,在使用模块的时候它们额外地增加了一层。 这对用户来说是很不便的并且通常是多余的。

导出类的静态方法也有同样的问题 – 这个类本身就增加了一层嵌套。 除非它能方便表述或便于清晰使用,否则请考虑直接导出一个辅助方法。

如果仅导出单个 类 或 函数,使用 出 默

就像“在顶层上导出”帮助减少用户使用的难度,一个默认的导出也能起到这个效果。 如果一个模块就是为了导出特定的内容,那么你应该考虑使用一个默认导出。 这会令模块的导入和使用变得些许简单。 比如:

类0.式

出 默 类 类0 {
  构() { /*...*/ }
}

函数0.式

出 默 务 取物() { 回 '物'; }

使用.式

引 类0 自 "./类0";
引 函数0 自 "./函数0";
定 对象0 = 启 类0();
控制台.日志(函数0());

对用户来说这是最理想的。他们可以随意命名导入模块的类型(本例为类0)并且不需要多余的(.)来找到相关对象。

如果要导出多个对象,把它们放在顶层里导出

多事物.式

出 类 一个类型 { /* ... */ }
出 务 一个函数() { /* ... */ }

相反地,当导入的时候:

明确地列出导入的名字

使用.式

引 { 一个类型, 一个函数 } 自 "./多事物";
定 对象0 = 启 一个类型();
定 函数0 = 一个函数();

使用命名空间导入模式当你要导出大量内容的时候

大模块.式

出 类 狗 { /*...*/ }
出 类 猫 { /*...*/ }
出 类 树 { /*...*/ }
出 类 花 { /*...*/ }

使用.式

引 * 作 大模块 自 "./大模块";
定 狗 = 启 大模块.狗();

模块里不要使用命名空间

当初次进入基于模块的开发模式时,可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。

在组织方面,命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。 通过将类型有层次地组织在命名空间里,可以方便用户找到与使用那些类型。 然而,模块本身已经存在于文件系统之中,这是必须的。 我们必须通过路径和文件名找到它们,这已经提供了一种逻辑上的组织形式。

命名空间对解决全局作用域里命名冲突来说是很重要的。 比如,你可以有一个My.Application.Customer.AddFormMy.Application.Order.AddForm – 两个类型的名字相同,但命名空间不同。 然而,这对于模块来说却不是一个问题。 在一个模块里,没有理由两个对象拥有同一个名字。 从模块的使用角度来说,使用者会挑出他们用来引用模块的名字,所以也没有理由发生重名的情况。

更多关于模块和命名空间的资料查看命名空间和模块

危险信号

以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使用命名空间:

  • 文件的顶层声明是出 名 示例 { ... } (删除示例并把所有内容向上层移动一层)
  • 文件只有一个出 类出 务(考虑使用出 默
  • 多个文件的顶层具有同样的出 名 示例 { (不要以为这些会合并到一个示例中!)