博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
学渣的模块化之路——50行代码带你手写一个common.js规范
阅读量:6434 次
发布时间:2019-06-23

本文共 4245 字,大约阅读时间需要 14 分钟。

一、简述

  • 一个js文件就是一个模块
  • 会自动把写的代码块套一层闭包
  • 浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量.(module,export,require,global)

既然没有,我们就手写一个吧,这里先普及下备用的基础知识,请往下看

二、源码中用到的备用知识

fs.accessSync方法

  • 判断文件是否存在,不存在则报错
  • let fs = require("fs");fs.accessSync("./x.js") //当前目录中没有该文件则会报错
    ec04a917b42e52ee72d4dd29993ccd33c3f9cdfb
  • path.extname方法
  • 判断文件的扩展名
  • let path = require("path");console.log(path.extname("./c.js"))
    82c7c430148c2655ab3adabd5c461d4173e9f8f9
  • vm.runInThisContext()方法

    • 会创建一个独立的沙箱环境,以执行对参数code的编译,运行并返回结果。vm.runInThisContext()方法运行的代码没有权限访问本地作用域,但是可以访问Global全局对象。

var vm = require('vm');var localVar = 'initial value';//在runInThisContext创建的沙箱环境中执行var vmResult = vm.runInThisContext('localVar = "vm";');console.log('vmResult: ', vmResult);    //vmResult:  vmconsole.log('localVar: ', localVar);    //localVar:  initial value//在eval中执行var evalResult = eval('localVar = "eval";');console.log('evalResult: ', evalResult);	//evalResult:  evalconsole.log('localVar: ', localVar);	//localVar:  eval
  • 在上面示例中,分在vm.runInThisContext()方法创建的沙箱环境中和eval()中执行了一段JavaScript代码。 vm.runInThisContext创建的沙箱环境无法访问本地作用域,因此localVar没有被改变。而eval可以访问本地作用域,因此localVar被改变。
  • 有了这些基础知识的储备,已经迫不及待的开始手写源代码了

三、模块化实现

  • 我们想的是构建一个module实例,类似这样{ loaded: false,filename: 绝对路径, exports: 引入模块的结果}
  • loaded检测如果require多次,会进行缓存,
  • filename我们需要把输入的路径变成绝对路径
  • export存放我们加载模块后的结果,至于怎么实现,我们会对应load方法就行实现
  • 最后把export返回,就实现了加载模块
  • 有了这么点想法,我们就一步一步开始实现吧

1、准备工作做好,声明构造函数Module

let path = require('path');let fs = require('fs');let vm = require('vm');// 声明构造函数Modulefunction Module(filename){    this.loaded = false; //用于检测是否被缓存过    this.filename = filename; //文件的绝对路径    this.exports = {} //模块对应的导出结果}//存放模块的扩展名Module._extensions = ['.js','.json'];//检测是否有缓存Module._cache = {};//拼凑成闭包的数组Module.wrapper = ['(function(exports,require,module){','\r\n})'];
已经把准备声明的变量和模块声明好,没有使用的先不用理会,下面用到的时候就明白了

2、实现一个require方法,实现加载模块

function req(path) { //自己实现require方法,实现加载模块    // 根据输入的路径 变出一个绝对路径    let filename = Module._resolveFilename(path);    // 通过这个文件名创建一个模块    let module = new Module(filename);    module.load(); //让这个模块进行加载 根据不同的后缀加载不同的内容    return module.exports}

3、有了这个架构想法,先实现 Module._resolveFilename方法

//如果没写扩展名,我们给它默认添加扩展名Module._resolveFilename = function (p) {    p = path.join(__dirname,p);    if(!/\.\w+$/.test(p)){        //如果没写扩展名,尝试添加扩展名        for(let i=0;i

4、此时的filename是存在这个文件的绝对路径,接下来对不同后缀名加载不同的内容

Module.prototype.load = function () {  // 加载模块本身 js按照js加载 json按照json加载  let extname = path.extname(this.filename); //判断后缀名  Module._extensions[extname](this); //把module实例传过去}

5、先实现加载.json后缀的方法,直接文件读取,并赋值给module.export

Module._extensions['.json'] = function (module) {    let content = fs.readFileSync(module.filename,'utf8');    module.exports = JSON.parse(content);};

写了这么多,我们先来测试下.json结尾的文件,有没有被写入到module.exports中

bc60eff42bd6653d607619e0dc0233e42fe10a9b

6、实现以.js结尾的后缀名的方法

// 后缀名为js的加载方法Module._extensions['.js'] = function (module) {    //取出加载模块的内容    let content = fs.readFileSync(module.filename,'utf8');    // 形成闭包    let script = Module.wrap(content);    //让js代码执行    let fn = vm.runInThisContext(script);    fn.call(module,module.exports,req,module)};
7、我们来看下Module.wrap(content)如何进行代码解析的

Module.wrap = function(content){    return Module.wrapper[0] + content +Module.wrapper[1];};
是不是很简单,只是简单的js字符串拼接成闭包的函数,然后让其进行执行。到目前为止,基本的功能已经实现了,我们先来测试下。但是我们还没实现缓存,接下来继续

83d9863b00afaf0c6a98d332690dbc695356296b

8、最后我们在处理下缓存,就完美结束啦

//检测是否有缓存Module._cache = {};//改造下req方法function req(path) { //自己实现require方法,实现加载模块    // 根据输入的路径 变出一个绝对路径    let filename = Module._resolveFilename(path);    if(Module._cache[filename]){ //有缓存直接取缓存中的        return Module._cache[filename].exports;    }    // 通过这个文件名创建一个模块    let module = new Module(filename);    module.load(); //让这个模块进行加载 根据不同的后缀加载不同的内容    Module._cache[filename] = module; //第一次加载先缓存    return module.exports}
这样一来就完美实现了,共不到50行代码,开不开心,愉不愉快。最后贴上源码供大家参考,别忘记喜欢哦
9、源码贴上(仅供参考)
let path = require('path');let fs = require('fs');let vm = require('vm');// 声明构造函数Modulefunction Module(filename){    this.loaded = false; //用于检测是否被缓存过    this.filename = filename; //文件的绝对路径    this.exports = {} //模块对应的导出结果}//存放模块的扩展名Module._extensions = ['.js','.json'];//检测是否有缓存Module._cache = {};//拼凑成闭包的数组Module.wrapper = ['(function(exports,require,module){','\r\n})'];//如果没写扩展名,我们给它默认添加扩展名Module._resolveFilename = function (p) {    p = path.join(__dirname,p);    if(!/\.\w+$/.test(p)){        //如果没写扩展名,尝试添加扩展名        for(let i=0;i

原文发布时间为:2018年06月25日

原文作者:言sir

本文来源:   如需转载请联系原作者
你可能感兴趣的文章
细谈Ehcache页面缓存的使用
查看>>
每天一个linux命令(3):pwd命令
查看>>
GridView如何设置View的初始样式
查看>>
从PHP5.2.x迁移到PHP5.3.x
查看>>
我的友情链接
查看>>
Placeholder in IE8 and older
查看>>
Maven(四):定制库到Mave本地资源库 (Kaptcha)
查看>>
【轉】JAVA中isEmpty和null以及""的区别
查看>>
自我评价
查看>>
Linux命令之cd
查看>>
MyEclipse移动开发教程:移动Web模拟器指南(一)
查看>>
Hinton神经网络公开课编程练习3 Optimization and generalization
查看>>
Java序列化简单例子
查看>>
第一篇博文——沉淀
查看>>
Direct2D (2) : 基本图形命令测试
查看>>
linux开通vsftpd服务过程
查看>>
我的友情链接
查看>>
常用 API 函数(3): 文件处理函数
查看>>
代码需要不断进化和改变
查看>>
SqlServer转换为Mysql的一款工具推荐(mss2sql)
查看>>