好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ES6+ 模块化(二)

ES6+ 模块化扩展

1. 前言

要深入前端学习时绕不开的是 Node 的学习,而 Node 中 自带 了模块化系统,Node 中的模块化是基于 Commo njs 规范实现的。而 ES6 中的模块化与之还有很多的不同的地方。现阶段 Node 还依然使用的是 Commo njs 规范,而前端正在逐渐使用 ES6 module 规范。两个规定统一是 一个 漫长的过程,两者都存在历史遗留问题和兼容问题需要浏览器和 Node 核心的 支持 。有必要搞清楚两个规范的区别和注意事项,有助于我们深入地学习前端。

上一节我们学习 ES6 Module 的环境搭建和基本 用法 ,本节将继续学习模块化的相关知识,本节主要是学习 Commo njs 规范,还有对比 ES6 module 规范的一些区别和注意事项。

2. Commo njs 规范

在维基百科中是这样定义 Commo njs 的:

Commo njs 是 一个 项目,其目标是为 JavaScript 在网页浏览器之外创建模块约定。创建这个项目的主要原因是当时缺乏普遍可接受形式的 JavaScript 脚本模块单元,模块在与运行 JavaScript 脚本的常规网页浏览器所提供的不同的环境下可以重复使用

JavaScript 语言在很长一段时间是没有模块化的概念的,直到 Node.js 的诞生后,让 JavaScript 有能力编写服务端语言,对操作系统、网络、 文件 系统等等的复杂业务场景,使用模块化就是不可或缺。这样也把模块化的概念带到了前端,而这时的客户端的 功能 也很复杂,急需一种可以拆分 代码 模块方便管理 代码 的一种模式。最终在社区的推动下 ES6 给出了 JavaScript 模块化的规范。

在 Node 模块中,Commo njs 规定每个 文件 就是 一个 模块,有它自己的作用域。在 一个 文件 里面定义的变量、 函数 、类,都是私有的,对其他 文件 不可见。

Commo njs 规定每个模块内部, module 变量代表当前模块。这个变量是 一个 对象,它的 exports 属性 (即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性 。

2.1 导出模块

使用 module.exports 把需要暴露的 内容 导出,没有导出的在外面是访问不了的。

  // a.js 
module . exports . name  =   'imooc'  ; 
module . exports .  fn   =   function  (  )  {  } 
 const  age  =   ; 
 

上面的 代码 中在 a.js 文件 中相当于 一个 私有的作用域, module.exports 把 name 和 fn 两个变量导出,但是 age 没有导出,所以在外部是访问不了的。

为了方便 module.exports 也可以省略 module 直接使用 exports 进行导出操作:

 exports . a  =   'hello' 
 

使用 module.exports 时还可以整体导出,整体导出时不能简写 exports 。

 module . exports  =   {  name :   'imooc'  ,  fn :  function  (  )  {  }   } 
 

2.2 导入模块

使用 require 用于导入其他模块暴露出来的 内容 。导出的 内容 是 一个 对象。

  const  a  =   require  (  './a'  )  ; 
console .  log  ( a )  ; 	 // { name: 'imooc', fn: [Function (anonymous)] } 
 

2.3 Commo njs 模块的特点

所有 代码 都运行在模块作用域,不会污染全局作用域。 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。 模块加载的顺序,按照其在 代码 中出现的顺序。

3. 不同规范之 间的 加载

3.1 import 加载 Commo njs 模块

使用 import 命令加载 Commo njs 模块,Node 会 自动 将 module.exports 属性 当作模块的 默 认 输出 ,即等同于 export default

  // a.js 
module . exports  =   { 
  foo :   'hello'  , 
  bar :   'world' 
 } 

 // 在import引入时等同于 
 export   default   { 
  foo :   'hello'  , 
  bar :   'world' 
 } 
 

Commo njs 模块是运行时确定 输出 接口,所以采用 import 命令加载 Commo njs 模块时,只能使用整体输入(*)。

  import   { readfile }   from   'fs'   //当'fs'为Commo njs 模块时 错误  
 // 整体输入 
 import   *   as  express  from   'express' 
 const  app  =  express .  default  (  )  ; 
 

3.2 require 加载 ES6 模块

require 命令加载 ES6 模块时,所有的 输出 接口都会成为输入对象的 属性 。

  // es.js 
 let  foo  =   { bar  :   'my-default'  }  ; 
exxport  default  foo ; 
foo  =   null  ; 

 // cjs.js 
 const  es_namespace  =   require  (  './es'  ) 
console .  log  ( es_namespace .  default  )  ;  // {bar:'my-default'} 
 

4. 面试题

模块化在面试中经常会被问到,掌握其深层原理是回答这类问题的关键。下面是面试中参考的两道题,这里和大家 分享 一下,提供的答案仅供参考。

commo njs 规范与 es module 规范的区别?

两个规范的区别可以从以下几个方面来回答:

模块的导出和导入:commo njs 使用的是 module.exports 和 require;es module 使用的是 export 和 import; 模块的引入方式:commo njs 是动态引用;esmodule 是静态分析,export 和 import 只能出现在 代码 的顶层,在编译时就可以确定引用; 模块的引用类型:commo njs 对基本类型 传递值 ,esmodule 对基本类型是传递引用; Commo njs 的 this 是当前模块,ES6 Module 的 this 是 undefined; 对 webpack 来说,想要 支持 tree shaking,包必须采用 es module 规范。

JS 在加载时分为两个阶段:编译和执行,而 ES6 模块是在 编译时进行加载(也可以叫:静态加载),这使得静态分析成为可能。es module 自动 采用严格模式,不管你有没有在模块头部 加上 "use strict"; 。

题目:commo njs 规范的循环引用

这是一道经典的 commo njs 的面试题,分析下列这段 代码 ,并解释原理。

  //main.js 
 var  a  =   require  (  './a'  ) 
console .  log  ( a ) 

 // a.js 
module . exports . a  =  
 var  b  =   require  (  './b'  ) 
console .  log  ( b ) 
module . exports . a  =  

 // b.js 
module . exports . b  =  
 var  a  =   require  (  './a'  ) 
console .  log  ( a ) 
module . exports . b  =  
 

回答本题的核心就是要知道 require 后的模块是会被缓存的,还需要注意的是先加入缓存,然后再执行。这样在按照 代码 同步的执行顺序去分析 代码 就会很清晰。具体分析如下:

使用 node main.js 执行 main.js 文件 内容 ; 执行 require('./a') 会将 a 模块加入缓存,然 后执行 a 模块中的 内容 ,执行权交到了 a 模块中,执行 a; 执行第一行将缓存的 a 值赋值为 1,然 后执行 第二行 require('./b') 把 b 模块加入缓存,并把执行权交到 b 模块中; b 模块中把 b 的值赋值为 11,在 require('./a') 时,是从缓存中取的值,这里就会在控制台打印 {a: 1} ,最后把缓存中的 b 值 修改 为 22,执行权交给上一级; 代码 执行权回到 a 模块中,这时 b 从缓存中取的值是 22,控制台中打印 { b: 22 } ,最后把缓存中的 a 值 修改 为 2,执行权交给上一级; 代码 执行回到 main 模块中,这时缓存中的 a 是 2,控制台中打印 { a: 2 } ,然后 代码 执行完毕。

5. 小结

本节主要学习了 Commo njs 的使用、在 Commo njs 和 ES Module 混用的一些问题,最后通过两道面试题学习了两个规范的区别和 Commo njs 在使用时会存在循环引用的问题,并分析了其执行的顺序和缓存的特点。

查看更多关于ES6+ 模块化(二)的详细内容...

  阅读:42次

上一篇

下一篇

第1节:ES6+ 简介    第2节:ES6 环境配置    第3节:ES6+ let    第4节:ES6+ const    第5节:ES6+ 展开语法    第6节:ES6+ 剩余参数    第7节:ES6+ 解构赋值    第8节:ES6+ 模版字符串    第9节:ES6+ 箭头函数    第10节:ES6+ 数值扩展    第11节:ES6+ isFinite()&isNaN()    第12节:ES6+ Number 对象的方法    第13节:ES6+ Math 对象的扩展    第14节:ES6+ includes()    第15节:ES6+ 字符串的扩展    第16节:ES6+ startsWith()    第17节:ES6+ endsWith()    第18节:ES6+ repeat()    第19节:ES6+ padStart()    第20节:ES6+ padEnd()    第21节:ES6+ trim()    第22节:ES6+ Array.from()    第23节:ES6+ of()    第24节:ES6+ find()和findIndex()    第25节:ES6+ copyWithin()    第26节:ES6+ fill()    第27节:ES6+ isArray()    第28节:ES6+ 对象的扩展    第29节:ES6+ flat()    第30节:ES6+ 可选链操作符    第31节:ES6+ Object.is()    第32节:ES6+ Object.assign()    第33节:ES6+ Object.keys()    第34节:ES6+ Object.values()    第35节:ES6+ Object.entries()    第36节:ES6+ 数据结构扩展    第37节:ES6+ Set    第38节:ES6+ WeakSet    第39节:ES6+ Map    第40节:ES6+ WeakMap    第41节:ES6+ Symbol    第42节:ES6+ for...of    第43节:ES6+ 迭代协议    第44节:ES6+ 实现一个简版的 Promise    第45节:ES6+ Promise 基础    第46节:ES6+ Promise 进阶    第47节:ES6+ Generator 基础    第48节:ES6+ Generator 函数应用    第49节:ES6+ async/await    第50节:ES6+ Class 前置知识    第51节:ES6+ Class    第52节:ES6+ Proxy    第53节:ES6+ Reflect(一)    第54节:ES6+ Reflect(二)    第55节:ES6+ 模块化(一)    第56节:ES6+ 模块化(二)    第57节:ES6实战1-实现Vue3 reactive 源码    第58节:ES6实战2-实现 Vue3 effect 源码    第59节:ES6 实战2-封装请求    第60节:ES6+ 实战3-代码整洁之道    第61节:ES6 Map原理分析    第62节:ES6module语法加载importexport    第63节:ES6的循环与可迭代对象示例详解