加紧学习,抓住中心,宁精勿杂,宁专勿多。 —— 周恩来
<script type="application/javascript">
// module1 code
// module2 code
script>
所有的嵌入到网页内的 JavaScript 对象都会使用全局的 window
对象来存放未使用 var
定义的变量。这就会导致一个问题,那就是,最后调用的函数或变量取决于我们引入的先后顺序。
模块化时代
。随着单页应用与富客户端的流行,不断增长的代码库也急需合理的代码分割与依赖管理的解决方案,这也就是我们在软件工程领域所熟悉的模块化(Modularity)
。
直接声明依赖(Directly Defined Dependences)、命名空间(Namespace Pattern)、模块模式(Module Pattern)、依赖分离定义(Detached Dependency Definitions)、沙盒(Sandbox)、依赖注入(Dependency Injection)、CommonJS、AMD、UMD、标签化模块(Labeled Modules)、YModules、ES 2015 Modules。这些都是模块化时代的产物。
问题来了,过度
碎片化的模块同样会带来性能的损耗与包体尺寸的增大,这包括了模块加载、模块解析、因为 Webpack 等打包工具包裹模块时封装的过多IIFE
函数导致的 JavaScript 引擎优化失败等。
简而言之,模块化就是将一个大的功能拆分为多个块,每一个块都是
独立
的,你不需要去担心污染
全局变量,命名冲突
什么的。
封闭
的作用域和依赖管理IO流
APIv8引擎
上的javascript运行时,作为服务端的,不能没有模块化的功能,于是就创造CommonJS规范,现在的node用的是CommonJS2。CommonJS2和CommonJS1的区别也在下面。属于动态同步加载
。// CommonJS2也可以通过这种方式导出
module.exports = {
a: 1
}
// CommonJS1只能通过这种方式
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
RequireJS
提出的,主要是依赖前置
。CMD是SeaJS
提出的,主要是就近依赖(只要用到才会导入),两者用法接近。属于异步加载
。// file lib/greeting.js
define(function() {
var helloInLang = {
en: 'Hello world!',
es: '¡Hola mundo!',
ru: 'Привет мир!'
};
return {
sayHello: function (lang) {
return helloInLang[lang];
}
};
});
// file hello.js
define(['./lib/greeting'], function(greeting) {
var phrase = greeting.sayHello('en');
document.write(phrase);
});
(function(define) {
define(function () {
var helloInLang = 'hello';
return {
sayHello: function (lang) {
return helloInLang[lang];
}
};
});
}(
typeof module === 'object' && module.exports && typeof define !== 'function' ?
function (factory) { module.exports = factory(); } :
define
));
绝对路径
缓存
,把文件名作为key,module作为value闭包(runInThisContext)
同步
操作js
,json
,…冲突
//arguments就是参数列表
console.log(arguments)
{ '0': {},
'1':
{ [Function: require]
resolve: { [Function: resolve] paths: [Function: paths] },
main:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
loaded: false,
children: [],
paths: [Array] },
extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
cache: { '/Users/chenxufeng/Desktop/笔记/node/arguments.js': [Object] } },
'2':
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
loaded: false,
children: [],
paths:
[ '/Users/chenxufeng/Desktop/笔记/node/node_modules',
'/Users/chenxufeng/Desktop/笔记/node_modules',
'/Users/chenxufeng/Desktop/node_modules',
'/Users/chenxufeng/node_modules',
'/Users/node_modules',
'/node_modules' ] },
'3': '/Users/chenxufeng/Desktop/笔记/node/arguments.js',
'4': '/Users/chenxufeng/Desktop/笔记/node' }
//exports内存中指向的就是module.exports指向的那块空间
//require一个方法
//Module模块类
//__filename该文件绝对路径
//__dirname该文件父文件夹的绝对路径
(function(exports,require,Module,__filename,__dirname){
module.exports = exports = this = {}
//文件中的所有代码
//不能改变exports指向,因为返回的是module.exports,所以是个{}
return module.exports
})
所以我们require的时候其实就相当于执行了这么一个闭包,然后返回的就是我们的module.exports
下面我通过步骤讲解
require
整个的一个实现
//require方法
function req(moduleId){
//解析绝对路径的方法,返回一个绝对路径
let p = Module._resolveFileName(moduleId)
//查看是否有缓存
if(Module._catcheModule[p]){
//有缓存直接返回对应模块的exports
return Module._catcheModule[p].exports
}
//没有缓存就生成一个
let module = new Module(p)
//把他放入缓存中
Module._catcheModule[p] = module
//加载模块
module.exports = module.load(p)
return module.exports
}
上面有很多方法都还没有,不急,我们慢慢实现
Module类
,并添加_resolveFileName
和_catcheModule
//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法(这个我们到时候再实现)
this.load = function(filepath){
//判断文件是json还是 node还是js
let ext = path.extname(filepath)
//返回一个exports
return Module._extensions[ext](this)
}
}
//以绝对路径为key存储一个module
Module._catcheModule = {}
// 解析绝对路径的方法,返回一个绝对路径
Module._resolveFileName = function(moduleId){
//获取moduleId的绝对路径
let p = path.resolve(moduleId)
try{
//同步地测试 path 指定的文件或目录的用户权限
fs.accessSync(p)
return p
}catch(e){
console.log(e)
}
}
此时会有一个问题,如果我们没有传文件后缀,就会读取不到
加载策略
,并且在_resolveFileName
中再加点东西//所有的加载策略
Module._extensions = {
'.js': function(module){
//每个文件的加载逻辑不一样,这个我们后面再写
},
'.json': function(module){
},
'.node': 'xxx',
}
Module._resolveFileName = function(moduleId){
//对象中所有的key做成一个数组[]
let arr = Object.keys(Module._extensions)
for(let i=0;ilet file = p+arr[i]
//因为整个模块读取是个同步过程,所以得用sync,这里判断有没有这个文件存在
try{
fs.accessSync(file)
return p
}catch(e){
console.log(e)
}
}
}
此时,我们能够找到文件的绝对路径,并把他丢给Module实例上的
load
方法
//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。
let vm = require('vm')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法
this.load = function(filepath){
//判断文件后缀是json还是 node还是js
let ext = path.extname(filepath)
return Module._extensions[ext](this)
}
}
//js文件加载的包装类
Module._wrapper = ['(function(exports,require,module,__dirname,__filename){','\n})']
//所有的加载策略
Module._extensions = {
//这里的module参数是就是Module的实例
'.js': function(module){
let fn = Module._wrapper[0] + fs.readFileSync(module.id,'utf8') + Module._wrapper[1]
//执行包装后的方法 把js文件中的导出引入module的exports中
//模块中的this === module.exports === {} exports也只是module.exports的别名
//runInThisContext:虚拟机会产生一个干净的作用域来跑其中的代码,类似于沙箱sandbox
vm.runInThisContext(fn).call(module.exports,module.exports,req,module)
return module.exports
},
'.json': function(module){
//同步读取文件中的内容并把它转为JSON对象
return JSON.parse(fs.readFileSync(module.id,'utf8'))
},
'.node': 'xxx',
}
此时我们的代码已经全部完成
Code Runner
,可在vscode右键直接运行js文件,在node环境中。//node原生的模块,用来读写文件(fileSystem)
let fs = require('fs')
//node原生的模块,用来解析文件路径
let path = require('path')
//提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。
let vm = require('vm')
//Module类,就相当于我们的模块(因为node环境不支持es6的class,这里用function)
function Module(p){
//当前模块的标识
this.id = p
//没个模块都有一个exports属性
this.exports = {}
//这个模块默认没有加载完
this.loaded = false
//模块加载方法
this.load = function(filepath){
//判断文件是json还是 node还是js
let ext = path.extname(filepath)
return Module._extensions[ext](this)
}
}
//js文件加载的包装类
Module._wrapper = ['(function(exports,require,module,__dirname,__filename){','\n})']
//所有的加载策略
Module._extensions = {
'.js': function(module){
let fn = Module._wrapper[0] + fs.readFileSync(module.id,'utf8') + Module._wrapper[1]
//执行包装后的方法 把js文件中的导出引入module的exports中
//模块中的this === module.exports === {} exports也只是module.exports的别名
vm.runInThisContext(fn).call(module.exports,module.exports,req,module)
return module.exports
},
'.json': function(module){
return JSON.parse(fs.readFileSync(module.id,'utf8'))
},
'.node': 'xxx',
}
//以绝对路径为key存储一个module
Module._catcheModule = {}
// 解析绝对路径的方法,返回一个绝对路径
Module._resolveFileName = function(moduleId){
let p = path.resolve(moduleId)
try{
fs.accessSync(p)
return p
}catch(e){
console.log(e)
}
//对象中所有的key做成一个数组[]
let arr = Object.keys(Module._extensions)
for(let i=0;ilet file = p+arr[i]
//因为整个模块读取是个同步过程,所以得用sync,这里判断有没有这个文件存在
try{
fs.accessSync(file)
return file
}catch(e){
console.log(e)
}
}
}
//require方法
function req(moduleId){
let p = Module._resolveFileName(moduleId)
if(Module._catcheModule[p]){
//模块已存在
return Module._catcheModule[p].exports
}
//没有缓存就生成一个
let module = new Module(p)
Module._catcheModule[p] = module
//加载模块
module.exports = module.load(p)
return module.exports
}