热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[ES6深度解析]15:模块Module

JavaScript项目已经发展到令人瞠目结舌的规模,社区已经开发了用于大规模工作的工具。你需要的最基本的东西之一是一个模块系统,这是一种将你的工作分散到多个文件和目录的方法——但

Javascript项目已经发展到令人瞠目结舌的规模,社区已经开发了用于大规模工作的工具。你需要的最基本的东西之一是一个模块系统,这是一种将你的工作分散到多个文件和目录的方法——但仍然要确保你的所有代码片段可以根据需要相互访问——而且还要能够有效地加载所有代码。所以很自然,Javascript有一个模块系统。实际上,有不少模块系统。还有一些包管理器,用于安装所有这些软件和处理高级依赖关系的工具。你可能会认为,拥有新的模块语法的ES6有点晚了。

今天我们将看到ES6是否会在这些现有系统中添加任何东西,以及未来的标准和工具是否能够在它的基础上构建。但首先,让我们深入了解一下ES6模块是什么样子的。

Module 基础知识

ES6模块是一个包含JS代码的文件。没有特殊的module关键字;模块读起来就像脚本。有两个区别。

  • ES6模块是自动的严格模式代码,即使你没有写use strict
  • 可以在模块中使用importexport

让我们先谈谈export。默认情况下,在模块中声明的所有内容都是该模块的局部内容。如果你希望在模块中声明的某些特性是公共的,以便其他模块可以使用它,则必须export该特性。有几种方法可以做到这一点。最简单的方法是添加export关键字。

// kittydar.js - Find the locations of all the cats in an image.

export function detectCats(canvas, options) {
  var kittydar = new Kittydar(options);
  return kittydar.detectCats(canvas);
}

export class Kittydar {
  ... several methods doing image processing ...
}

// This helper function isn't exported.
function resizeCanvas() {
  ...
}

可以export任何***functionclassvarletconst

这就是编写模块所需要知道的全部内容!你不需要把所有的东西都放到IIFE回调中。去声明你需要的东西吧。由于代码是一个模块,而不是一个脚本,所以所有的声明都将作用域限定在该模块,而不是在所有脚本和模块中全局可见

除了export之外,模块中的代码基本上都是普通代码。它可以使用全局变量,如ObjectArray。如果您的模块在web浏览器中运行,它可以使用documentXMLHttpRequest

在一个单独的文件中,我们可以导入并使用detectCats()函数:

// demo.js - Kittydar demo program

import {detectCats} from "kittydar.js";

function go() {
    var canvas = document.getElementById("catpix");
    var cats = detectCats(canvas);
    drawRectangles(canvas, cats);
}

要从一个模块中导入多个名称,可以这样写:

import {detectCats, Kittydar} from "kittydar.js";

当你运行包含import声明的模块时,它首先加载所导入的模块,然后在依赖关系图的深度优先遍历中执行每个模块主体,通过跳过已经执行的任何内容来避免循环遍历。这些是模块的基础。这真的很简单。;-)

export 列表

你可以用花括号{}括起来,列出你想要导出的所有(方法,变量,类等)名称,而不是给每个导出的特性加上标签:

export {detectCats, Kittydar};

// no `export` keyword required here
function detectCats(canvas, options) { ... }
class Kittydar { ... }

export列表不一定是文件中的第一项内容;它可以出现在模块文件的***作用域中的任何地方。您可以有多个export列表,或者将export列表与其他export声明混合在一起,只要没有名称被多次重复导出。

重命名导入和导出

偶尔,导入的名称会与你需要使用的其他名称发生冲突。所以ES6允许你在导入时重命名:

// suburbia.js

// Both these modules export something named `flip`.
// To import them both, we must rename at least one.
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
...

类似地,您可以在导出时重命名它们。如果你想在两个不同的名称下导出相同的值,这是很方便的,这偶尔会发生:

// unlicensed_nuclear_accelerator.js - media streaming without drm
// (not a real library, but maybe it should be)

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

默认导出

新标准旨在与现有的CommonJSAMD模块互操作。假设你有一个Node项目,你已经完成了npm install lodash。你的ES6代码可以从Lodash中导入单独的函数:

import {each, map} from "lodash";

each([3, 2, 1], x => console.log(x));

但也许你已经习惯看到_.each,而不是each,你仍然想要这样写。或者你把_作为函数使用,因为这在Lodash中很常用。为此,你可以使用稍微不同的语法:导入没有花括号的模块

import _ from "lodash";

这种简写等价于import {default as _} from "lodash"。所有的CommonJS和AMD模块在ES6中都有一个default export,这和你在require()函数中调用该模块时得到的是一样的,也就是exports对象

ES6模块被设计成允许你导出多个东西,但是对于现有的CommonJS模块,你只能得到默认的导出。例如,在撰写本文时,据我所知,著名的colors包没有任何特殊的ES6支持。它是CommonJS模块的集合,就像npm上的大多数包一样。但是你可以直接导入到你的ES6代码中。

// ES6 equivalent of `var colors = require("colors/safe");`
import colors from "colors/safe";

如果你想要自己的ES6模块有一个默认的导出,这很容易做到。默认导出没有什么魔力;它就像任何其他导出一样,除了它被命名为default。你可以使用我们已经讨论过的重命名语法:

let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};

或者更好的做法是,使用以下简写:

export default {
  field1: value1,
  field2: value2
};

关键字export default后面可以跟任何值:函数、类、对象字面量。

模块对象

import * as cows from "cows";

当你import *时,所导入的是一个模块名称空间对象(module namespace object)。它的属性是模块的exports。因此,如果cows模块导出了一个名为moo()的函数,那么在以这种方式导入cows之后,你可以写:cows.moo()

聚合模块

有时候,一个包的主模块比导入包的所有其他模块并以统一的方式导出它们大不了多少。为了简化这类代码,有一种一体化的import-and-export简写:

// world-foods.js - good stuff from all over

// import "sri-lanka" and re-export some of its exports
export {Tea, Cinnamon} from "sri-lanka";

// import "equatorial-guinea" and re-export some of its exports
export {Coffee, Cocoa} from "equatorial-guinea";

// import "singapore" and export ALL of its exports
export * from "singapore";

每个export-from语句都类似于import-from语句后跟export语句。与真正的导入不同,它不会将重新导出的绑定添加到您的作用域。因此,如果你打算在world-food.js中编写一些利用Tea的代码,请不要使用这种简写。你会发现它并不存在。

如果singapore输出的任何名称与其他输出名称发生冲突,那将是一个错误,因此要小心使用export *

语法已经讲完了!现在说说有趣的部分。

import实际上是做什么的?

你会相信…什么都不没做吗?

哦,你没那么容易上当。你能相信标准并没有说import到底做了哪些事情吗?这是件好事吗?

ES6将模块加载的细节完全留给了实现。模块执行方式是详细指定的。粗略地说,当你告诉JS引擎运行一个模块时,它必须表现得像以下四个步骤正在发生:

  1. 解析(Parsing):
    实现读取模块的源代码并检查语法错误。

  2. 加载(Loading):
    实现加载所有导入的模块(递归地)。这部分还没有标准化。

  3. 链接(Linking):
    对于每个新加载的模块,实现创建一个模块作用域,并用该模块中声明的所有绑定填充它,包括从其他模块导入的内容。
    如果你试图import {cake} from "paleo",但是paleo模块实际上没有导出任何名为cake的东西,你会得到一个错误。这太糟糕了,因为你离真正运行一些JS代码已经很近了。

  4. 运行时(Runtime):
    最后,实现运行每个新加载模块的代码体中的语句。此时,导入处理已经完成,所以当执行到有import声明的代码行时……什么也没有发生!

看到了吗?我告诉过你答案是"import什么都没做",关于编程语言,我没有撒谎。

现在我们来看看这个系统中有趣的部分。有一个很酷的技巧。因为系统不指定加载是如何工作的,因为你可以提前通过查看源代码import声明算出所有的依赖关系,一种加载的实现方式是在编译时完成所有的工作,你所有的模块打包成一个文件,并把它放在网络上传输!像webpack这样的工具可以做到这一点。

这是一件大事,因为通过网络加载脚本需要花费时间,而且每次获取一个脚本时,您可能会发现它包含需要加载几十个以上的导入声明。一个简单的加载器将需要大量的网络往返通讯。但是有了webpack,你现在不仅可以使用带有模块的ES6,还可以在不影响运行时性能的情况下获得所有的软件工程好处。

ES6中模块加载的详细规范最初是计划并构建的。它没有出现在最终标准中的一个原因是,对于如何实现这个捆绑特性没有达成共识。我希望有人能解决这个问题,因为我们将看到,模块加载确实应该标准化。捆绑销售太好了,不能放弃。

静态 vs 动态,或者:规则以及如何打破规则

作为一种动态语言,Javascript让自己拥有了一个令人惊讶的静态模块系统。

  • 在一个模块中,所有类型的导入和导出都只允许在顶层。没有条件导入或导出,并且不能在函数内使用导入。
  • 所有导出的标识符必须在源代码中按名称显式导出。你无法通过编程方式遍历数组并以数据驱动的方式导出一组名称。
  • 模块对象被冻结(无法修改)。没有办法将一个新特性hack到一个模块对象中,polyfill风格。
  • 在任何模块代码运行之前,模块的所有依赖项都必须被加载、解析和链接。没有语法可以实现按需惰性加载的导入
  • 导入发生错误没有错误恢复。一个应用程序可能包含数百个模块,如果有任何模块无法加载或链接,就无法运行。不能把import包裹在try/catch块中。(这里的好处是,因为系统是静态的,所以webpack可以在编译时检测到这些错误。)
  • 没有钩子允许模块在依赖项加载之前运行一些代码。这意味着模块无法控制它们的依赖项是如何加载的。

只要你的需求是静态的,系统就相当不错。但你难免有时候需要做一点定制,对吧?

这就是为什么无论你使用什么模块加载系统,都会有一个编程API来配合ES6的静态import/export语法。例如,webpack包含一个API,你可以用它来“分割代码”,按需惰性加载一些模块包。同样的API可以帮助您打破上面列出的大多数规则。

ES6模块语法是非常静态的,这很好——它以强大的编译时工具的形式得到了回报。但是这种静态语法被设计为与丰富的动态、程序化加载器API一起工作。


推荐阅读
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • 记录一些 Latex 的技巧
    Latex一些技巧:1.如何创建不浮动的的figure和table\makeatletter\newcommand{\figcaption}{\def\captyp ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
author-avatar
皮皮美2_160
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有