【深入JS模块化】JS模块化解析

前言

没有模块化带来的问题

早期没有模块化带来了很多的问题:比如命名冲突的问题
当然,我们有办法可以解决上面的问题:立即函数调用表达式
但是,我们其实带来了新的问题:

  • 第一,我必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用;
  • 第二,代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写;
  • 第三,在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况;

所以,我们会发现,虽然实现了模块化,但是我们的实现过于简单,并且是没有规范的。
我们需要制定一定的规范来约束每个人都按照这个规范去编写模块化的代码;
这个规范中应该包括核心功能:模块本身可以导出暴露的属性,模块又可以导入自己需要的属性;JavaScript社区为了解决上面的问题,涌现出一系列好用的规范。

CommonJS

CommonJS模块化规范通常是在node的运行环境里面,每一个独立的JS文件就是一个模块,exports和module.exports可以负责对模块中的内容进行导出;require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
至于module.exports和exports的关系,大致就是这样的关系:
module.exports = {},exports = module.exports导出的是取决于module.exports而不是exports,但是一般情况下他们都是相同的,但是如果给exports单独赋值了一个对象,会导致exports = module.exports不成立,是不会达到导出的目的的。
实际上,在一个JS文件中,通过module.exports导出一个对象时,然后其他地方通过require获取这个文件导出的对象,我们可以理解为实际上,通过module.exports指向了这个对象,并且对外暴露了这个对象,使得我们可以通过require访问到,在其他地方,我们通过require访问的时候,会根据require里面传入的文件路径,然后去寻找这个文件暴露的对象,然后指向这个对象,也就是说,module.exports和require指向的都是同一个对象,那么我们也可以在require导入的文件里面去修改这个对象,当然可以是可以,我们并不支持这样做,这样会导致在当多个文件引入同一个文件时候,出bug后,使得错误的寻找变得毫无头绪。
在这里插入图片描述

1、require的细节

情况一:

X是一个Node核心模块,比如path、http直接返回核心模块,并且停止查找

情况二:X是以 ./ 或 …/ 或 /(根目录)开头的

  • 第一步:将X当做一个文件在对应的目录下查找;
    1.如果有后缀名,按照后缀名的格式查找对应的文件
    2.如果没有后缀名,会按照如下顺序: 1> 直接查找文件X 2> 查找X.js文件 3> 查找X.json文件 4> 查找X.node文件
  • 第二步:没有找到对应的文件,将X作为一个目录 查找目录下面的index文件 1> 查找X/index.js文件 2> 查找X/index.json文件 3> 查找X/index.node文件
  • 如果没有找到,那么报错:not found

情况三:直接是一个X(没有路径),并且X不是一个核心模块

这样的话,会先在当前文件夹里面寻找node_modules,如果没有找到,会在上一层文件夹里面寻找,如果寻找到了node_modules文件夹,便会去内部寻找要引入的模块。

2、模块的加载过程

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

模块被多次引入时,会缓存,最终只加载(运行)一次
为什么只会加载运行一次呢? 这是因为每个模块对象module都有一个属性:loaded。 为false表示还没有加载,为true表示已经加载

如果有循环引入,那么加载顺序是什么?Node采用的是深度优先算法
如下:
在这里插入图片描述
顺序为main -> aaa -> ccc -> ddd -> eee ->bbb

3、CommonJS规范缺点

CommonJS加载模块是同步的:

同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;
这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;

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

浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行;
那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;
所以在浏览器中,我们通常不使用CommonJS规范:当然在webpack中使用CommonJS是另外一回事;因为它会将我们的代码转成浏览器可以直接执行的代码;

ESModule

1、如何使用

在使用ESModule前,我们需要先设置type=“module”

  <script src="./main.js" type="module"></script>

需要注意的是,在浏览器运行ESModule模块化代码的时候,必须通过开启本地服务器的方式打开,而不能通过右键open in browser 打开。如果你通过本地加载Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为Javascript 模块安全性需要。所以需要通过一个服务器来测试,我这里使用的VSCode,VSCode中有一个插件:Live Server
ES Module和CommonJS的模块化有一些不同之处:一方面它使用了import和export关键字;另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;

ES Module模块采用export和import关键字来实现模块化:
export负责将模块内的内容导出;
import负责从其他模块导入内容;

ESModule通过export关键字导出内容,export 后跟上一个{},里面放入导出的内容,要注意的是,这并不是对象,这只是一种导出的写法而已,当我们需要导入的时候,使用import…from…来实现。当我们需要将在导出或者导入时候以另外一个变量名接收时,只需要在导出的变量后面加as (新变量名),*表示全部内容,还可以使用default关键字表示默认导出。例如export default foo;
index.html中:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script src="./main.js" type="module"></script>
</body>

</html>

main.js中:

// 1、
// import { name as name1, age } from "./info.js"
// console.log(name1, age);

// 2、
// import * as foo from "./info.js"
// console.log(foo.name, foo.age);

// 3、default
import foo from "./info.js"
console.log(foo.name, foo.age);

info.js中:

const name = "cy"
const age = 18;
const height = 1.88
    // export {
    //     name,
    //     age
    // }
const foo = {
    name,
    age
}
export {
    height
}
export default foo

2、import的细节

通过import加载一个模块,是不可以在其放到逻辑代码中的,比如:
为什么会出现这个情况呢?

这是因为ES Module在被JS引擎解析时,就必须知道它的依赖关系;
由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况;

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

如果根据不同的条件,动态来选择加载模块的路径; 这个时候我们需要使用 import() 函数来动态加载

import.meta是一个给JavaScript模块暴露特定上下文的元数据属性的对象。

它包含了这个模块的信息,比如说这个模块的URL; 在ES11(ES2020)中新增的特性

// import { name, age, foo } from './foo.js'
// import函数返回的结果是一个Promise
import("./foo.js").then(res => {
  console.log("res:", res.name)
})

console.log("后续的代码都是不会运行的~")

// ES11新增的特性
// meta属性本身也是一个对象: { url: "当前模块所在的路径" }
console.log(import.meta)

3、ES Module的解析流程

ES Module的解析过程可以划分为三个阶段:
阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record);
阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中

阶段一:构建阶段
在这里插入图片描述
阶段二和阶段三:实例化阶段 – 求值阶段
在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
一个非侵入式、不会破坏原来静态页面结构、可被浏览器正确显示的、格式良好的前端HTML模板引擎。彻底实现前后端分离,让后端专注业务的处理。 传统MVC开发模式,V层使用服务器端渲染。美工设计好静态HTML文件,交给后端工程师,需要转换成Jsp、Freemarker、Velocity等动态模板文件。这种模式有几个缺点 1、动态模板文件不能被浏览器解释、必须要运行在服务器中才能显示出效果 2、动态效果和静态效果分别存在不同文件,美工和后端工程师需要分别维护各自页面文件,其中一方需要修改页面,都需要通知另一方进行修改 3、页面数据不能分块加载、获取跨域数据比较麻烦 domTemplate.js 模板引擎是通过在标签中添加自定义属性,实现动态模板功能,当没有引入domTemplate脚本, 则自定义标签属性不会被浏览器解析,不会破坏原有静态效果,当引入domTemplate脚本,模板引擎回去解析这些标签属性, 并加载数据进行动态渲染。 下图:对比服务器页面渲染和使用domTemplate前端引擎开发流程 服务器端模板解析 domTemplate前端解析 用法 导入jquery.js或者zepto.js和domTemplate.js $(function () {  $.domTemplate.init(options); //可以通过selector指定根节点,默认根节点是body,表示从body开始,渲染整个页面  }); 或者解析某一个html片段。 $('selector').domTemplate(options); //渲染数据是通过h-model 自动去获取数据,也可以通过data指定全局数据 if条件标签 <div> <p h-if="{user.id==50}" h-text="用户ID等于50">xxx</p> <p>其他内容</p> <div> switch条件标签 <p h-switch="{user.id}"> <input type="text" h-case="20" h-val="{user.email}"/> <input type="text" h-case="60" h-val="拉拉"/> <input type="text" h-case="*" h-val="丽丽"/>  </p> each遍历标签 <p>遍历List例子</p> <ul> <li h-each= "user,userStat : {users}" h-text="{userStat.index 1}-{user.email}"> 李小璐</li> </ul> 自定义标签 $.domTemplate.registerTag('tagName',function(ctx,name,exp){ }); //tagName 是自定义标签名称,用时要加上前缀,如定义'test'标签,用时h-test="" 标签:domTemplate 分享 window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "2", "bdMiniList": [], "bdPic": "", "bdStyle": "1", "bdSize": "24" }, "share": {} }; with (document)0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' ~(-new Date() / 36e5)];\r\n \r\n \r\n \r\n \r\n \u8f6f\u4ef6\u9996\u9875\r\n \u8f6f\u4ef6\u4e0b\u8f7d\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\nwindow.changyan.api.config({\r\nappid: 'cysXjLKDf', conf: 'prod_33c27aefa42004c9b2c12a759c851039' });

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

既白biu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值