关于javascript:彻底掌握CommonJS

62次阅读

共计 3618 个字符,预计需要花费 10 分钟才能阅读完成。

模块化简介

为什么模块化

随着前端代码越来越简单,咱们迫切希望解决以下几个问题

  • 全局变量净化(多人单干)
  • 抽出公共代码(封装)
  • 缩小申请次数(缩小 script 标签)

一个好的模块化计划,必须要能解决 依赖 问题以及 加载程序 问题

模块式历史

IIFE:应用自执行函数来编写模块化,特点:在一个独自的函数作用域中执行代码,防止变量抵触
如何解决依赖,能够让一个依赖裸露到 windows 上,而后当做参数传入到另外一个须要此依赖的匿名函数里
如何解决加载程序呢,这个有两个计划,要么用 defer,等全副加载完再运行,要么程序的摆放 script 标签,

(function(){
  return {data:[]
  }
})()

AMD:应用 requireJS 来编写模块化,特点:提前执行,前置依赖

define('./index.js',function(code){// code 就是 index.js 返回的内容})

CMD:应用 seaJS 来编写模块化,特点:提早执行,就近依赖

define(function(require, exports, module) {var indexCode = require('./index.js');
});

CommonJS:nodejs 中自带的模块化。

var fs = require('fs');

UMD:兼容 AMD,CommonJS 模块化语法。
UMD 的实现很简略:

  1. 先判断是否反对 Node.js 模块格局(exports 是否存在),存在则应用 Node.js 模块格局。
  2. 再判断是否反对 AMD(define 是否存在),存在则应用 AMD 形式加载模块。
  3. 前两个都不存在,则将模块公开到全局(window 或 global

ES Modules:ES6 引入的模块化,反对 import 来引入另一个 js。

import a from 'a';

commonjs

用法

Node 利用由模块组成,采纳 CommonJS 模块标准。每个文件就是一个模块,有本人的作用域。在一个文件外面定义的变量、函数、类,都是公有的,对其余文件不可见

裸露模块:module.exports=value 或者 exports.xxx = value

引入模块 require(xxx)

特点

  • 所有代码都运行在模块作用域,不会净化全局作用域。
  • 模块能够屡次加载,然而只会在第一次加载时运行一次,而后运行后果就被缓存了,当前再加载,就间接读取缓存后果。要想让模块再次运行,必须革除缓存。
  • 模块加载的程序,依照其在代码中呈现的程序。

commonjs 的繁难实现

commonjs 本质就是一个立刻执行函数,包裹住被执行的文件,让文件外面的内容通过 module 的 exports 属性裸露进去

let path = require('path');
let fs = require('fs');
let vm = require('vm')

function Module(id){
    this.id = id;
    this.exports = {};}
Module.wrapper = ["(function(exports,module,req,__firname,__dirname){",   // 为什么在文件里能够用 module require 的起因
    "})"
]
Module._extensions = {'.js'(module){ // 如何解决模块
        let fileContent = fs.readFileSync(module.id);
        // 给读取进去的文件内容 增加自执行函数
        let script = Module.wrapper[0] + fileContent + Module.wrapper[1];
        let fn = vm.runInThisContext(script);
        fn.call(module.exports,module.exports,module,req)
    },
    '.json'(module){
        // 在 json 中只须要将 后果赋予给 exports 对象上即可
        let fileContent = fs.readFileSync(module.id);
        module.exports = JSON.parse(fileContent)
    }
}
// 解析文件的绝对路径 能够尝试增加后缀
Module.resolveFilename = function(filePath){... 解析绝对路径}
Module.prototype.load = function(){ // 是真正加载模块的办法
    //this 指代的就是以后的模块
    let extension = path.extname(this.id);
    Module._extensions[extension](this); 
    return this.exports; // 特地留神这里返回的是 module.exports
}
Module._cache = {};
Module.load = function(filePath){ // ./a
    let absPath = this.resolveFilename(filePath);
    // 如果缓存有这个模块,间接把这个模块的 module.exoports;
    if(Module._cache[absPath]) return Module._cache[absPath].exports;
    let module = new Module(absPath); // module.id module.exports;
    Module._cache[absPath] = module; // 把文件和模块对应上
    return module.load();}
function req(filePath){return Module.load(filePath)
}

从实现还是能够看出一些货色的
1、模块外面的 this 就是 module.exports
2、require 返回的是 module 的 exports 属性(一个对象),这个属性在执行立刻执行函数(模块里代码) 的时候会被赋值,所以咱们能够有这么几种写法 module.exports = value(这里不要了解为扭转返回值地址,指向一个新对象,而应该了解为扭转 module 的属性,具体起因看谬误的写法)
module.exports.a = value(不扭转原返回对象,只是加个属性)
exports.a = value(因为 exports 也作为参数传到立刻执行函数里了,exports 是齐全等于 module.exports 的)
然而这种写法是错的
exports = value 因为这是间接扭转传入函数的参数对象(不是增加属性),里面的对象不会受影响(函数参数值传递)
3、缓存指的是在同一个 js 里 require 屡次只采纳第一次后果

commonjs 与 ems(es6 module)区别

两个差别

  • CommonJS 模块输入的是一个值的拷贝,一旦输入一个值,模块外部的变动就影响不到这个值(援用类型还是能改的),ES6 模块输入的是值的援用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输入接口, 有 read-only 个性

浏览器中执行 commonjs

首先,commonjs 是同步加载的,浏览器外面加载会比较慢,js 会阻塞 dom, 导致白屏
所以浏览器外面能够用 esm,script 标签外面加 type=module,这个会默认开启 defer,在 dom 加载完且 script 下载完当前才会执行

如果非要在浏览器外面加载 commonjs 模块呢,因为浏览器没有 node 环境,也就是没有 module,require,exprots 这些变量的实现,所以如果能有工具增加了这些实现,是能够让浏览器运行 commonjs 模块标准的代码的

browserify 就是这么一个工具
原理很简略,获取到每个模块的依赖关系,生成一个以 id 为键的模块依赖字典,模块 3 依赖模块 1,模块 1 依赖模块 2,而后包装每个模块(传入依赖字典以及本人实现的 export 和 require 函数),生成用于执行的 js

{
  1:[function(require,module,exports){var t = require("./mo2.js");
    exports.write = function(){document.write("test1");
      t.write2();}
  },
  {"./mo2.js":2}
],
  2:[function(require,module,exports){exports.write2 = function(){document.write("=2=");
    }
  },
  {}],
  3:[function(require,module,exports){var mo = require("./mo.js");
    mo.write();},
  {"./mo.js":1}
]}

参考

1. 浏览器加载 CommonJS 模块的原理与实现
2.commonjs 与 ems 的差别
3.commonjs 与 ems 差别
4. 前端模块化详解
5.UMD
6.script 标签 defer async type=module
7.browserify 运行原理剖析

正文完
 0