commonJS和ES6模块化

commonJS和ES6模块化

面试题

  • commonJS的特性
  • commonjs和es6的模块化区别
  • commonjs 和 esm 是怎么解决循环引用问题的
  • 模块化了解过吗?延伸出说一下CommonJS和Es Modulde的底层原理?

commonJS

common规范的应用

  • node 是CommonJS在服务器端一个具有代表性的实现
  • Browserify 是CommonJS在浏览器中的一种实现
  • webpack打包工具对CommonJS的支持和转换,前端应用在编译之前使用CommonJS进行开发

commonjs的特点

  • commonjs中每个js文件都是一个单独的模块
  • 使用require()方法加载其他模块时,会执行被加载模块中的代码
  • module.exports 指向暴露的对象,exports是简写情况,默认情况下,exportsmodule.exports指向同一个对象 exports==module.exports,如果不同module.exports为准
  • CommonJS 同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖。采用深度优先算法
  • require() 可以在任意的位置,动态加载(运行时加载)模块,不会提升到最开头

commonJs实现原理

需要关注的问题

  • 如何解决变量污染的问题

script标签直接引入的方式,没有模块化,script内部的变量是可以相互渲染的

  • module.exports,exports,require 三者是如何工作的?又有什么关系?

模块执行的原理

  1. 在编译过程中,commonJs对js的代码块进行了收尾包装
    每个模块文件上存在requiremoduleexports方法,但是这三个变量在文件中是没有定义的。 Commonjs 会将我们写代码包装起来,形成包装函数,requiremoduleexports本质是通过形参的方式传递到包装函数中的。
    require: 引入模块的方法
    module: 记录当前模块信息
    exports:当前模块导出的属性
  2. 在模块加载的时候,会传入requiremoduleexports等参数
const sayName = require('./hello.js')
module.exports = function say(){
    return {
        name:sayName(),
        author:'ranan'
    }
}
//包装后
(function(exports,require,module,__filename,__dirname){
	const sayName = require('./hello.js')
module.exports = function say(){
    return {
        name:sayName(),
        author:'ranan'
    	}
	}
})

require模块加载原理

  • CommonJS 模块同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖。采用深度优先算法
  • require() 可以在任意的位置,动态加载(运行时加载)模块,不会提升到最开头

module :在 Node 中每一个 js 文件都是一个 module ,module 上保存了 exports 等信息之外,还有一个 loaded 表示该模块是否被加载,没有加载过就先缓存后执行,已经加载过,就直接去缓存不用执行来避免循环引用问题

  • 为 false 表示还没有加载;
  • 为 true 表示已经加载
/*
根据文件标识符,先查找有没有缓存,有缓存直接返回缓存的内容
没有缓存,创建module对象,将其缓存到module上,然后加载文件,将 loaded 属性设置为 true ,然后返回 module.exports 对象。
*/
 // id 为路径标识符
function require(id) {
   /* 查找  Module 上有没有已经加载的 js  对象*/
   const  cachedModule = Module._cache[id]
   
   /* 如果已经加载了那么直接取走缓存的 exports 对象  */
  if(cachedModule){
    return cachedModule.exports
  }
 
  /* 创建当前模块的 module  */
  const module = { exports: {} ,loaded: false , ...}

  /* 将 module 缓存到  Module 的缓存属性中,路径标识符作为 id */
  //只会在第一次加载时运行一次,后面都会从缓存中读取,  
  Module._cache[id] = module
  /* 加载文件 */
  runInThisContext(wrapper('module.exports = "123"'))(module.exports, require, module, __filename, __dirname)
  /* 加载完成 *//
  module.loaded = true 
  /* 返回值 */
  return module.exports
}

Es Module

Es Module

  • Es Module 的静态导入导出的优势,实现了 tree shaking
  • Es Module 还可以 import() 懒加载方式实现代码分割。

ES6 module的特性

  1. ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层 import , export 不能放在块级作用域或条件语句中。

这种静态语法,在编译过程中确定了导入和导出的关系,所以更方便去查找依赖,更方便去 tree shaking

  1. ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块
  2. 使用import 导入的变量是只读的,可以理解默认为 const 装饰,无法被赋值
  3. 使用 import导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递。

import() 函数 动态引入

import() 返回一个 Promise 对象, 返回的 Promisethen 成功回调中,可以获取模块的加载成功信息。

特点

  • import() 动态加载一些内容,可以放在条件语句或者函数执行上下文中
if(isRequire){
    const result  = import('./b')
}
  • import() 可以实现懒加载
[
   {
        path: 'home',
        name: '首页',
        component: ()=> import('./home') ,
   },
]

import() 可以很轻松的实现代码分割。避免一次性加载大量 js 文件,造成首次加载白屏时间过长的情况。

commonJs和es module的区别

-commonJses module
导入方式require()动态加载模块,可以在任意的位置,不会被提升到最前面导入方式分为静态导入和动态导入
静态导入:不能放在块级作用域和条件中,会提升到最前面
动态导入import()类似require(),但他是异步加载的
构建模块依赖的时期require同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖。在编译阶段就建立起了模块之间的依赖关系
ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块
输出的值输出值的拷贝值,一旦输出了某个值,如果模块内部发生变化,不会影响外部的值输出的是值的引用,JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里去取值。所以内部发生变化会影响外部的值。

版权声明:本文为qq_41370833原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。