CMJ和ESM

505 阅读3分钟

CommonJS

关键词

  • 社区标准
  • 使用函数实现
  • 仅node环境支持
  • 动态依赖(需要代码运行后才能确定依赖)
  • 动态依赖是同步执行的

原理

// require函数的伪代码
function require(){
    if(该模块有缓存吗){
        return 缓存结果;
    }
    function _run(exports,require,module,__filename,_dirname){
        // 模块代码会放到这里
    }
    var module = {
        export:{}
    }
    _run.call({
        module.exports,// 全局环境中this的值=module.exports={}
        module.exports,// 这五个是run的参数
        require,
        module,模块路径,
        模块所在目录
    }):
    // 把module.exports加入到缓存;
    return module.exports;
}

this === exports === module.exports === {}

在js中打印一下看看值

console.log(arguments.length); 
// 5
console.log(exports);
// {}
console.log(require);
/*
[Function: require] {
  resolve: [Function: resolve] { paths: [Function: paths] },
  main: Module {
    id: '.',
    path: 'D:\\code\\level1\\工程化\\CommonJS',
    exports: {},
    parent: null,
    filename: 'D:\\code\\level1\\工程化\\CommonJS\\yuanli.js',
    loaded: false,
    children: [],
    paths: [
      'D:\\code\\level1\\工程化\\CommonJS\\node_modules',
      'D:\\code\\level1\\工程化\\node_modules',
      'D:\\code\\level1\\node_modules',
      'D:\\code\\node_modules',
      'D:\\code\\node_modules',
      'D:\\node_modules'
    ]
  },
  extensions: [Object: null prototype] {
    '.js': [Function (anonymous)],
    '.json': [Function (anonymous)],
    '.node': [Function (anonymous)]
  },
  cache: [Object: null prototype] {
    'D:\\code\\level1\\工程化\\CommonJS\\yuanli.js': Module {
      id: '.',
      path: 'D:\\code\\level1\\工程化\\CommonJS',
      exports: {},
      parent: null,
      filename: 'D:\\code\\level1\\工程化\\CommonJS\\yuanli.js',
      loaded: false,
      children: [],
      paths: [Array]
    }
  }
}
*/
console.log(module);
/*
Module {
  id: '.',
  path: 'D:\\code\\level1\\工程化\\CommonJS',
  exports: {},
  parent: null,
  filename: 'D:\\code\\level1\\工程化\\CommonJS\\yuanli.js',
  loaded: false,
  children: [],
  paths: [
    'D:\\code\\level1\\工程化\\CommonJS\\node_modules',
    'D:\\code\\level1\\工程化\\node_modules',
    'D:\\code\\level1\\node_modules',
    'D:\\code\\node_modules',
    'D:\\code\\node_modules',
    'D:\\node_modules'
  ]
}
*/
console.log(__filename);
// D:\code\duyi\level1\工程化\CommonJS\yuanli.js
console.log(__dirname);
// D:\code\duyi\level1\工程化\CommonJS

ES Module

关键词

  • 官方标准
  • 使用新语法实现,import export等
  • 所有环境都支持
  • 同时支持静态依赖和动态依赖
  • 静态依赖:在代码运行前就要确定依赖关系,必须放在文件顶部。
  • 动态依赖是异步的,哪里需要就在那里require。
  • 符号绑定

关于静态依赖

关于符号绑定

// a.js
export var a = 1;
export function changeA(){
    a = 2;
}
// index.js
import {a as num,changeA} from './a'
console.log(num);
changeA(); // 修改的应该是a.js中的a,理论上不应该影响num
console.log(num);

// 看代码会输出 0 0
// 结果输出的却是:0 1

说明:a和num用的是同一块内存空间。

面试题

  1. CMJ 和 ESM 区别
  • CMJ是社区标准,ESM是官方标准
  • CMJ使用的是API实现的模块化,ESM是使用的新语法实现的模块化
  • CMJ仅在node环境中支持,ESM在各种环境都支持
  • CMJ是动态的依赖,同步执行。ESM既支持动态,也支持静态。动态依赖是异步执行的。
  • ESM导出时有符号绑定,CMJ只是普通函数调用和赋值
  1. export 和 export default的区别是什么?
  • export 为普通导出,又叫做具名导出。它导出的数据必须带有命名,比如变量定义、函数定义这种带有命名的语句。在导出的模块对象中,命名即为模块对象的属性名。在一个模板中可以有多个具名导出。
  • export default 为默认导出,在模块对象中名称固定为 default,因此无需命名,通常导出一个表达式或字面量。在一个模块中只能有一个默认导出。
  1. 下面的模块导出了什么结果?
// this === exports === module.exports === {}
exports.a = 'a'; // {a:'a'}
module.exports.b = 'b'; // {a:'a',b:'b'}
this.c = 'c'; // {a:'a',b:'b',c:'c'}
module.exports = { // 这里给exports对象重新赋值了 {d:'d'}
    d:'d'
}

{ d: 'd' }

  1. 下面代码导出了什么?
exports.a = 1;
module.exports.b = {
    b:2
}

{ a: 1,b: {b: 2}}

  1. 下面代码导出什么结果?
// counter.js
var count = 1;
eaport {count}
export function increase(){
    count++;
}
// 导出: {count:1,increase:fn}

// main.js
import {count,increase} from './counter';// 同一块内存
import * as counter from './counter';// 同一块内存
const {count:c} = counter;// 单独开辟一块
increase();
console.log(count); // 2
console.log(counter.count);// 2
console.log(c); // 1