走向Node与Webpack 之路 - CommonJS 模块化

走向Node与Webpack 之路 - CommonJS 模块化

1. 参考资料

JavaScript 标准参考教程(alpha)

CommonJS规范(推荐 - 阮一峰写的)

官方网站 (看半天,不知道干啥!)

CommonJS 是什么 ? (有些晕)


2. 基本环境

  • node v4.5.0
  • npm v4.2.0

这里基本环境安装配置就不说了,自己查找资料。

使用webstrom ,使用 node 记得enable ,写相关命令的时候,才会提醒显示: 
如图:我的是已经打开的

这里写图片描述


3. 认识

无论是node应用模块,还是webpack 配置 ,均是采用CommonJS模块化规范。

Example :

example.js

/**
 * Created by yuan on 2/21/2017.
 */
var x = 1;
var addX = function (value) {
    return value + x;
}

module.exports.x = x; #对外提供值
module.exports.addX = addX; #对外提供函数

example-test.js

/**
 * Created by yuan on 2/21/2017.
 */
var example = require('./example.js') #加载模块
console.log(example.x) #调用value
console.log(example.addX(12)); #调用函数

执行 : node xxx.js 执行 js 文件

D:\webworkspace\webpack-demo\commond>node example1-test.js
1
13

规范:

  • 每个js 文件就是一个模块,有自己的作用域 ;
  • 每个js 文件定义的函数,变量,类都是私有的;
  • 每个模块的运行,不会污染全局作用域 ;
  • 每个模块可以多次加载,但只会在第一次加载时运行一次,然后结果就缓存了;重新运行模块,需要清除缓存;
  • 所有模块的加载顺序不定,按照被使用的顺序加载;
  • require : 加载模块
  • module.exports : 对外提供接口

3.module

所有的模块都是一个module对象,代表当前模块

Example : 
引入jquery , 打印当前module ;

  • npm install jquery -g : 安装jquery
D:\webworkspace\webpack-demo\commond>npm install jquery -g
C:\Users\yuan\AppData\Roaming\npm
`-- jquery@3.1.1
  • 配置 package.json dependencies 依赖
 "dependencies": {
    "jquery": "^3.1.1",
  }
  • example.js
/**
 * Created by yuan on 2/21/2017.
 */
var jquery= require('jquery');
exports.$ = jquery;
console.log(module);
  • 执行
D:\webworkspace\webpack-demo\commond>node example2.js
Module {
  id: '.', #模块识别,通常是模块的绝对路径文件名
  exports: { '$': [Function] },#模块对外输出的接口
  parent: null, # 返回对象,表示调用该模块的模块
  filename: 'D:\\webworkspace\\webpack-demo\\commond\\example2.js', # 绝对路径的模块名
  loaded: false, # 表示是否加载完成
  children: # 表示该模块依赖的其他模块
   [ Module {
       id: 'D:\\webworkspace\\webpack-demo\\node_modules\\jquery\\dist\\jquery.js',
       exports: [Function],
       parent: [Circular],
       filename: 'D:\\webworkspace\\webpack-demo\\node_modules\\jquery\\dist\\jquery.js',
       loaded: true,
       children: [],
       paths: [Object] } ],
  paths:
   [ 'D:\\webworkspace\\webpack-demo\\commond\\node_modules',
     'D:\\webworkspace\\webpack-demo\\node_modules',
     'D:\\webworkspace\\node_modules',
     'D:\\node_modules' ] }

(1)认识module

  • id : 模块识别,通常是模块的绝对路径文件名
  • exports : 模块对外输出的接口
  • parent : 返回对象,表示调用该模块的模块
  • filename : 绝对路径的模块名
  • loaded : 表示是否加载完成
  • children : 表示该模块依赖的其他模块

(2)module.exports

module.exports 当前模块对外输出的接口,对外提供变量;在每个模块中默认实现了

var exports=module.exports;

Example 1 :

/**
 * 返回x平方与y平方和
 * @param x
 * @param y
 * @returns {number}
 */
function add(x, y) {
    return x * x + y * y;
}
// exports.add = add; // 对外提供add函数
// module.exports.add = add; //对外提供add函数 

结果:

上面两种写法,实现的效果是一样的;建议使用 module.exports 来做,我觉得代码清晰。

Example 2 :

//ok
exports.hello = function () {
    console.log('hello node!')
};

//error1 : 该exports 赋值 就和 module.exports 没有了联系!
exports = function (x) {
    console.log(x);
};
//error2 :
module.exports = 'hello commonJS';

结果:

注意: 不可以对exports 或 module.exports 直接赋值,不然exports 就和 module 本身的exports 没有关系了。


4. require

require 加载模块文件: 读入并执行javascript 文件 ,返回该module.exports对象

(1)简单的加载

Example :

example.js

var sayHi = function (name) {
    console.log(name + ' say hi !');
    console.log(require.main===module); #false
};

module.exports.sayhi = sayHi;

example-test.js

var say = require('./example.js'); #加载上面的exmple.js
console.log(say.sayhi('labelnet'));
console.log(require.main === module); #true

执行

D:\webworkspace\webpack-demo\commond>node example4-test.js
labelnet say hi !
false
undefined
true

总结: 

require.main === module 可以判断模块是直接执行的还是被调用执行的,如果直接执行返回true , 被调用的返回false;

(2)require 加载规则

进行加载时,默认后缀名时 .js , 也就是说 require('./example4.js') 和 require('./example4') 效果实现一样的。

参数格式:

参数格式决定了不同路径寻找模块文件

  • “/” 开头 , 绝对路径加载
 require('/yuan/dist/example.js')
  • “.” 开头 , 相对路径加载,比如 同一目录加载
require('./example4')
require('./example4.js')
  • “文件名称”, 加载默认提供的核心模块,package.json 以 配置的模块,当然node_modules以安装的工程
require('jquery')

(3)缓存

每个模块被加载时,只执行一次,其余的都去缓存中获取;

requrie.cache

Example :

require('./example');
require('./example').message = 'hello js';
console.log(require('./example').message);

上面代码,加载了三次example, 第二次添加message 变量,第三次打印message变量; 
结果:

D:\webworkspace\webpack-demo\commond>node example5-test.js
hello js

说明 第三次的结果是从缓存中获取的;

删除缓存

删除缓存,必须知道缓存模块的绝对路径,才可以删除; 
基本格式 :

delete require.cache[模块绝对路径];

Example :

require('./example');
require('./example').message = 'hello js';
// console.log(module); //可以获取到模块的绝对路径
delete require.cache['D:\\webworkspace\\webpack-demo\\commond\\example.js']; //删除指定模块缓存,缓存是根据绝对路径识别的!!

console.log(require('./example').message);

结果:

D:\webworkspace\webpack-demo\commond>node example5-test.js
undefined

删除全部缓存

Object.keys(require.cache).forEach(function (key) {
    delete require.cache[key];
});

5. 加载机制

(1)require 加载机制

对外输出的值,是这个值得拷贝(复制),如果这个值内部值改变,那么对外输出的值并不改变;

Example : 
先打印外部值,改变内部值,再次打印外部值;

example.js

var value = 3;
function add() {
    value++;
    console.log('内部 value +1 : ' + value)
}

module.exports = {
    value: value,
    add: add,
};

example-test.js

/**
 * Created by yuan on 2/21/2017.
 */
var test= require('./exapmle6');

console.log('外部 value 执行1 : '+test.value);
console.log(test.add());
console.log('外部 value 执行2 : '+test.value);

结果:

D:\webworkspace\webpack-demo\commond>node example6-test.js
外部 value 执行1 : 3
内部 value +1 : 4
undefined
外部 value 执行2 : 3

(2)require 内部处理流程 (学习来源)

require 不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load

Module._load = function(request, parent, isMain) {
  // 1. 检查 Module._cache,是否缓存之中有指定模块
  // 2. 如果缓存之中没有,就创建一个新的Module实例
  // 3. 将它保存到缓存
  // 4. 使用 module.load() 加载指定的模块文件,
  //    读取文件内容之后,使用 module.compile() 执行文件代码
  // 5. 如果加载/解析过程报错,就从缓存删除该模块
  // 6. 返回该模块的 module.exports
};

上面的第4步,采用 module.compile() 执行指定模块的脚本,逻辑如下。

Module.prototype._compile = function(content, filename) {
  // 1. 生成一个require函数,指向module.require
  // 2. 加载其他辅助方法到require
  // 3. 将文件内容放到一个函数之中,该函数可调用 require
  // 4. 执行该函数
};

上面的第1步和第2步,require函数及其辅助方法主要如下。

  • require(): 加载外部模块
  • require.resolve():将模块名解析到一个绝对路径
  • require.main:指向主模块
  • require.cache:指向所有缓存的模块
  • require.extensions:根据文件的后缀名,调用不同的执行函数

一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值 。

 

转自:https://blog.csdn.net/LABLENET/article/details/56287947

 

posted @ 2018-07-07 15:06  我们家的小常客  阅读(595)  评论(0编辑  收藏  举报