javascript的模块化

2024-09-08 06:04
文章标签 java script 模块化

本文主要是介绍javascript的模块化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 无模块化

script标签引入js文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:

  <script src="jquery.js"></script><script src="jquery_scroller.js"></script><script src="main.js"></script><script src="other1.js"></script><script src="other2.js"></script><script src="other3.js"></script>

 即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。缺点很明显:

  • 污染全局作用域
  • 维护成本高
  • 依赖关系不明显

2. CommonJS规范

在node中,默认支持的模块化规范叫做CommonJS,它使得 JavaScript 代码可以更好地组织、复用和维护。

关于模块:

  • 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
  • 在模块中使用global定义全局变量,不需要导出,在别的文件中可以访问到。
  • 每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外接口。
  • 通过require加载模块,读取并执行一个js文件,然后返回该模块的exports对象。
  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

 一点说明:  

exports 是对 module.exports 的引用。比如我们可以认为在一个模块的顶部有这句代码: exports = module.exports所以,我们不能直接给exports赋值,比如number、function等。

var exports = module.exports
类似于
var obj1 = obj
这里exports只是module.exports的一个引用,所以这里直接给exports直接赋值,并不会影响module.exports,只是指向了新对象而已。
始终要记住:底层实现里最后返回的是module.exports对象

重新赋值,解除引用:

此外,这里要注意:一旦给exports重新赋值,便会失去和module.exports的关联,指向新对象,且后期无法使用。

一点优点:解决了依赖、全局变量污染的问题

一点缺点: CommonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS不适合浏览器端模块加载,更合理的方案是使用异步加载,比如AMD规范。

        

1.module对象

node内部提供一个Module构建函数。所有的模块其实都是Module的实例。

每个模块内部都有一个Module对象,代表当前模块。它有以下属性:

  • module.id,模块的识别符,通常是带有绝对路径的模块文件名;
  • module.filename,模块的文件名,带有绝对路径;
  • module.loaded,返回应boolean值,表示模块是否已经完成加载;
  • module.parent,返回一个对象,表示调用该模块的模块;
  • module.children,返回一个数组,表示该模块内用到的其他模块;
  • module.exports,表示模块对外输出的值。

2. CommonJS 模块的导出

// math.js
const sum = (a, b) => a + b;
const multiply = (a, b) => a * b;
module.exports = { sum, multiply };//暴露这两个函数const a = 10
const b = 20
const obj = { name: "孙悟空" }module.exports.a = a
module.exports.b = b
// const {a, b} = require("./m.js")// math_functions.js
function add(a, b) {return a + b;
}function subtract(a, b) {return a - b;
}const PI = 3.14159;module.exports = { add, subtract, PI };const getName = () => {return 'Jim';
};const getLocation = () => {return 'Munich';
};const dateOfBirth = '12.01.1982';exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;class User {constructor(name, age, email) {this.name = name;this.age = age;this.email = email;}getUserStats() {return `Name: ${this.name}Age: ${this.age}Email: ${this.email}`;}
}module.exports = User;

3.其他module.exportsexports的常见问题

  • module.exportsexportsNode.js中有什么区别?

module.exports和exports都用于从模块中导出值。exports本质上是对module.exports的引用,所以您可以任选其一使用。然而,通常建议使用module.exports以避免潜在问题。

  • 我可以在同一个模块文件中同时使用module.exportsexports吗?

是的,您可以在同一个模块中同时使用两者。但在这样做时要小心,最好保持一致性,坚持使用一种惯例。
 

  • 在使用module.exportsexports时有哪些常见问题?

一个常见的是直接重新赋值exports,这可能导致意想不到的结果。最好使用module.exports以获得更好的一致性,并避免潜在的问题。

  • 我可以在浏览器中使用module.exportsexports吗,还是它们只适用于Node.js

module.exportsexports是特定于Node.js的,在浏览器的JavaScript中不可用。在浏览器中,通常使用其他机制,如ES6模块进行导出和导入。

4. CommonJS 模块的导入

2.1使用 require 函数导入文件模块(用户自定义)

  • 使用require(“模块的路径”)函数来引入模块
  • 模块名要以./ 或 …/开头
  • 扩展名可以省略(除了扩展名是.cjs)
  • 在 JavaScript 中,引入模块时可以省略文件扩展名。当引入的模块是 JavaScript 文件(.js)、JSON 文件(.json)和node(.node)时,可以不写扩展名,Node.js 会根据需要自动解析文件类型并加载对应的模块。
  • * 如果没有.js 后缀的同名文件它会寻找 .json后缀的。(如果两个后缀名都有,则优先导入后缀名为.js的)
  • .js > .json >.node

2.2使用 require 函数导入核心模块(Node.js 内置的模块)

  • 直接写核心模块的名字即可
  • 也可以在核心模块前添加 node:

// main.js
const math = require('./math.js');
console.log(math.sum(2, 3)); // 输出 5
console.log(math.multiply(2, 3)); // 输出 6
 

2.3文件夹作为模块


当我们使用一个文件夹作为模块时,文件夹中必须有一个模块作为主文件。如果文件夹中含有package.json文件且文件中设置了main属性,则main属性指定的文件会成为主文件,导入模块时就会导入该文件(主文件)。如果没有package.json,则node会按照index.js、index.node的顺序寻找主文件。

//01module.js
// reauire是用来加载模块的,它会把引进来的东西作为返回值返回
// const result = require('./外部')
const result = require('./文件')//它会执行引入模块内的代码但是它的返回值就是模块内导出(暴露/exports/module.exports)的东西,如果模块未导出(暴露)任何东西,那么它就是空
console.log(result)

被导入的文件夹

index.js


console.log('我是入口')
require('./a')
require('./b')
require('./c')
require('./d')
// console.log('ff')`

a.js

console.log('我是a')

b.js

console.log('我是b')

c.js

console.log('我是c')

d.js

console.log('我是d')

注意此时打印result内容为空,这是因为虽然引入了,但是被引入发的模块没有暴露东西(在模块内部任何变量或其他对象都是私有的,需要手动暴露)
用module.exports暴露后

//index.js
console.log('我是入口')
require('./a')
require('./b')
require('./c')
require('./d')
// console.log('ff')`
const a = 10
const b = 20
module.exports.a = a
module.exports.b = b

再次执行,result对象内为我们暴露的东西

2.4模块的原理

(function(exports, require,module,_filename,__dirname)
let a= 10
let b=20
});

用module.exports导出和用exports导出有什么异同?
答:他俩都是对象,但是exports是module.exports的引用,所以exports只能添加属性(exports直接赋值会改变exports的引用,而不会改变module.exports的引用,从而起不到导出的效果),不能直接赋值,而module.exports可以直接赋值。

3. AMD规范

AMD规范则是非同步加载模块允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

AMD标准中,定义了下面三个API:

  1. require([module], callback)
  2. define(id, [depends], callback)
  3. require.config()

即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。

先到require.js官网下载最新版本,然后引入到页面,如下:

<script data-main="./alert" src="./require.js"></script>

data-main属性不能省略。

 

以上分别是定义模块,引用模块,运行在浏览器弹出提示信息。

      引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。

     在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。

一点优点:适合在浏览器环境中异步加载模块、并行加载多个模块

一点缺点:不能按需加载、开发成本大

4. CMD

AMD 推崇依赖前置、提前执行CMD推崇依赖就近、延迟执行。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

很明显,CMD是按需加载,就近原则。

5. ES6模块化

在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。 

 

es6在导出的时候有一个默认导出,export default,使用它导出后,在import的时候,不需要加上{},模块名字可以随意起。该名字实际上就是个对象,包含导出模块里面的函数或者变量。

但是一个模块只能有一个export default

6. CommonJs和ES6区别

(1) CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • S6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

    (2) CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  • 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。

  • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

 

(3)

  • exports只能使用语法来向外暴露内部变量:如exportts.xxx =xxx;
  • module.exports既可以通过语法,也可以直接赋值一个对象

小节:

1、exports为modules.exports的一个引用
2、最后node底层模块导出的是module.exports
3、底层代码var module = {exports:{...}} 
4、exports.name等价于module.exports.name,但node为了方便书写,使用module.exports导出单个成员,本质为将该子对象重新赋值  
5、所以只要给exports赋值,便丢失了module.exports的引用关系,后期便不可用   

这篇关于javascript的模块化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1147316

相关文章

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

java Stream操作转换方法

《javaStream操作转换方法》文章总结了Java8中流(Stream)API的多种常用方法,包括创建流、过滤、遍历、分组、排序、去重、查找、匹配、转换、归约、打印日志、最大最小值、统计、连接、... 目录流创建1、list 转 map2、filter()过滤3、foreach遍历4、groupingB

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后