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 Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd