0

0

JavaScript高级语法之模块化(建议收藏)

WBOY

WBOY

发布时间:2022-01-26 17:46:27

|

1584人浏览过

|

来源于掘金

转载

本篇文章给大家带来了关于javascript中模块化的相关知识,希望对大家有帮助。

JavaScript高级语法之模块化(建议收藏)

众所周知,js在前端开发中的地位。学好它,真的很重要。

下面这篇文章,介绍一下模块化。

什么是模块化?

到底什么是模块化、模块化开发呢?

立即学习Java免费学习笔记(深入)”;

  • 事实上模块化开发最终的目的是将程序划分成一个个小的结构。

  • 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构。

  • 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用。

  • 也可以通过某种方式,导入另外结构中的变量、函数、对象等。

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程。

模块化的历史

在网页开发的早期,Brendan Eich开发JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的:

  • 这个时候我们只需要讲JavaScript代码写到

  • 并没有必要放到多个文件中来编写。

但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了:

  • ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过JavaScript进行前端页面的渲染。

  • SPA的出现,前端页面变得更加复杂:包括前端路由、状态管理等等一系列复杂的需求需要通过JavaScript来实现。

  • 包括Node的实现,JavaScript编写复杂的后端程序,没有模块化是致命的硬伤。

所以,模块化已经是JavaScript一个非常迫切的需求。所以ES6(2015)才推出了自己的模块化方案。

在此之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS等。

没有模块化带来的问题

比如命名冲突的问题。

通过立即函数调用表达式(IIFE)来解决上面的问题。因为函数有自己的作用域,不会造成不同文件命名冲突。

    // a.js
    var moduleA = (function() {
      var name = "llm"
      var age = 22
      var isFlag = true
      return {
        name: name,
        isFlag: isFlag
      }
    })()
    // b.js
    var moduleB = (function() {
      var name = "zh"
      var isFlag = false
      return {
        name: name,
        isFlag: isFlag
      }
    })()
    // 使用
    moduleA.name
    moduleB.name

但是,我们其实带来了新的问题:

  • 我必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用。

  • 代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写。

  • 在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况。

所以,我们会发现,虽然实现了模块化,但是我们的实现过于简单,并且是没有规范的。

我们需要制定一定的规范来约束每个人都按照这个规范去编写模块化的代码。这个规范中应该包括核心功能:模块本身可以导出暴露的属性,模块又可以导入自己需要的属性。JavaScript社区为了解决上面的问题,涌现出一系列好用的规范,接下来我们就学习具有代表性的一些规范。

CommonJS规范和Node

我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS。

  • Node是CommonJS在服务器端一个具有代表性的实现。

  • Browserify是CommonJS在浏览器中的一种实现。

  • webpack打包工具具备对CommonJS的支持和转换。

所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发。

  • 在Node中每一个js文件都是一个单独的模块。

  • 这个模块中包括CommonJS规范的核心变量:exports、module.exports、require。

  • 我们可以使用这些变量来方便的进行模块化开发。

前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现:

  • exports和module.exports可以负责对模块中的内容进行导出。

  • require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容。

Node.js模块化

Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发:

  • 在Node中每一个js文件都是一个单独的模块。

  • 这个模块中包括CommonJS规范的核心变量:exports、module.exports、require。

  • exports和module.exports可以负责对模块中的内容进行导出。

  • require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容。

下面我们将来介绍exports、module.exports、require的使用。

  • exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出。

  • 我们也可以通过module.exports直接导出一个对象。

  • 我们通过require()函数导入一个文件。并且该文件导出的变量。

下面来详细介绍一个module.exports。

CommonJS中是没有module.exports的概念的。

但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module。

所以在Node中真正用于导出的其实根本不是exports,而是module.exports。

因为module才是导出的真正实现者。

并且内部将exports赋值给module.exports。

该方式的导入导出有以下特点:

Node中的文件都运行在一个函数中。可以通过打印console.log(arguments.callee + "")来验证。

17.png

导入导出是值的引用,如果导出的是一个基本数据类型值,那么导出文件改变该值,然后导入文件该变量的值也不会变。

    // a.js
    const obj = require("./b.js")
    console.log(obj)
    setTimeout(() => {
      obj.name = "llm"
    }, 1000)
    // b.js
    const info = {
      name: "zh",
      age: 22,
      foo: function() {
        console.log("foo函数~")
      }
    }
    setTimeout(() => {
      console.log(info.name) // llm
    }, 2000)
    module.exports = info

他是通过require 函数来导入的,只有在执行js代码才会知道模块的依赖关系。

代码是同步执行的。

模块多次引入,只会加载一次。每个module内部会存在一个loaded来确定是否被加载过。

代码循环引入的时候,深度优先来加载模块。然后再广度优先。

下面来详细介绍一个require的导入细节

我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。

那么,require的查找规则是怎么样的呢?

详细查找规则,请访问这里

这里我总结比较常见的查找规则:导入格式如下:require(X)

18.png

模块的加载细节

模块在被第一次引入时,模块中的js代码会被运行一次

模块被多次引入时,会缓存,最终只加载(运行)一次

为什么只会加载运行一次呢?

这是因为每个模块对象module都有一个属性:loaded。为false表示还没有加载,为true表示已经加载。

如果有循环引入,那么加载顺序是什么?

19.png

如上图,Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb

CommonJS规范缺点

CommonJS加载模块是同步的:

Shakespeare
Shakespeare

一款人工智能文案软件,能够创建几乎任何类型的文案。

下载

同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行。

这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快。

如果将它应用于浏览器呢?

浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行。

那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作。所以在浏览器中,我们通常不使用CommonJS规范。当然在webpack中使用CommonJS是另外一回事。因为它会将我们的代码转成浏览器可以直接执行的代码。

AMD规范

在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD。但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换。AMD和CMD已经使用非常少了,所以这里我们进行简单的演练。

AMD主要是应用于浏览器的一种模块化规范:

AMD是Asynchronous Module Definition(异步模块定义)的缩写。它采用的是异步加载模块。

我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用。

AMD实现的比较常用的库是require.js和curl.js。

require.js的使用

定义HTML的script标签引入require.js和定义入口文件。data-main属性的作用是在加载完src的文件后会加载执行该文件

// index.html
 
    //main.js
    require.config({
      baseUrl: '', // 默认是main.js的文件夹路径
      paths: {
        foo: "./foo"
      }
    })
    require(["foo"], function(foo) {
      console.log("main:", foo)
    })
    // foo.js
    define(function() {
      const name = "zh"
      const age = 22
      function sum(num1, num2) {
        return num1 + num2
      }
      return {
        name,
        age,
        sum
      }
    })

CMD规范

CMD规范也是应用于浏览器的一种模块化规范:

CMD 是Common Module Definition(通用模块定义)的缩写。它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来。

AMD实现的比较常用的库是SeaJS。

SeaJS的使用

引入sea.js和使用主入口文件。

    // index.html
      
      
    //main.js
    define(function(require, exports, module) {
      const foo = require("./foo")
      console.log("main:", foo)
    })
    // foo.js
   define(function(require, exports, module) {
      const name = "zh"
      const age = 22
      function sum(num1, num2) {
        return num1 + num2
      }
      // exports.name = name
      // exports.age = age
      module.exports = {
        name,
        age,
        sum
      }
    });

ES Module

ES Module和CommonJS的模块化有一些不同之处:

  • 一方面它使用了import和export关键字来实现模块化。

  • 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式。

  • export负责将模块内的内容导出。

  • import负责从其他模块导入内容。

  • 采用ES Module将自动采用严格模式:use strict。

基本使用

    // index.html
    
    // foo.js
    let obj = {
      name: "zh",
      age: 22
    }
    
    export default sum
    // main.js
    import foo from './foo.js'
    console.log(foo)

html文件加载入口文件的时候,需要指定type为module。

在打开html文件时,需要开启本地服务,而不能直接打开运行在浏览器上。

这个在MDN上面有给出解释:

你需要注意本地测试 — 如果你通过本地加载Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为Javascript 模块安全性需要。

你需要通过一个服务器来测试。

exports关键字

export关键字将一个模块中的变量、函数、类等导出。

我们希望将其他中内容全部导出,它可以有如下的方式:

方式一:在语句声明的前面直接加上export关键字。

    export const name = "zh"
    export const age = 22

方式二:将所有需要导出的标识符,放到export后面的 {} 中。注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的。所以: export {name: name},是错误的写法。

    const name = "zh"
    const age = 22
    function foo() {
      console.log("foo function")
    }
    export {
      name,
      age,
      foo
    }

方式三:导出时给标识符起一个别名。(基本没用,一般在导入文件中起别名)。然后在导入文件中就只能使用别名来获取。

    export {
      name as fName,
      age as fAge,
      foo as fFoo
    }

import关键字

import关键字负责从另外一个模块中导入内容。

导入内容的方式也有多种:

方式一:import {标识符列表} from '模块'。注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容。

    import { name, age } from "./foo.js"

方式二:导入时给标识符起别名。

    import { name as fName, age as fAge } from './foo.js'

方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上。然后通过起别名来使用。

    import * as foo from './foo.js'

export和import结合使用

表示导入导出。

    import { add, sub } from './math.js'
    import {otherProperty} from './other.js'
    export {
      add,
      sub,
      otherProperty
    }

等价于

    // 导入的所有文件会统一被导出
    export { add, sub } from './math.js'
    export {otherProperty} from './other.js'

等价于

    export * from './math.js'
    export * from './other.js'

为什么要这样做呢?

在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中。 这样方便指定统一的接口规范,也方便阅读。这个时候,我们就可以使用export和import结合使用。

default用法

前面我们学习的导出功能都是有名字的导出(named exports):

在导出export时指定了名字。

在导入import时需要知道具体的名字。

还有一种导出叫做默认导出(default export)

    // foo.js
    const name = "zh"
    cconst age = 22
    export {
      name,
      // 或者这样的默认导出
      // age as default
    }
    export default age
    // 导入语句: 导入的默认的导出
    import foo, {name} from './foo.js'
    console.log(foo, name) // 22 zh

默认导出export时可以不需要指定名字。

在导入时不需要使用 {},并且可以自己来指定名字。

它也方便我们和现有的CommonJS等规范相互操作。

注意:在一个模块中,只能有一个默认导出(default export)。

import函数

通过import加载一个模块,是不可以在其放到逻辑代码中的,比如:

    if(true) {
        import foo from './foo.js'
    }

为什么会出现这个情况呢?

这是因为ES Module在被JS引擎解析时,就必须知道它的依赖关系。

由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况。

但是某些情况下,我们确确实实希望动态的来加载某一个模块:

如果根据不同的条件,动态来选择加载模块的路径。

这个时候我们需要使用 import() 函数来动态加载。import函数返回的结果是一个Promise。

    import("./foo.js").then(res => {
      console.log("res:", res.name)
    })

es11新增了一个属性。meta属性本身也是一个对象: { url: "当前模块所在的路径" }

    console.log(import.meta)

ES Module的解析流程

ES Module是如何被浏览器解析并且让模块之间可以相互引用的呢?

ES Module的解析过程可以划分为三个阶段:

阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record)。

阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。

阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中。

20.png

阶段一:

21.png

阶段二和三:

22.png

所以,从上面可以看出在导出文件中,修改变量的值会影响到导入文件中的值。而且导入文件被限制修改导出文件的值。

相关推荐:javascript学习教程

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

6

2026.01.15

公务员递补名单公布时间 公务员递补要求
公务员递补名单公布时间 公务员递补要求

公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

28

2026.01.15

公务员调剂条件 2026调剂公告时间
公务员调剂条件 2026调剂公告时间

(一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

37

2026.01.15

国考成绩查询入口 国考分数公布时间2026
国考成绩查询入口 国考分数公布时间2026

笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

6

2026.01.15

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

63

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

34

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

74

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

20

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

31

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号