CommonJS 是为在网页浏览器之外的JavaScript提供模块化规范的项目。在模块化出现之前,前端引用其他的js文件资源都是通过<script>
标签的方式,这种方式有很多的弊端,例如必须有一个html文档,而 Node.js 根本没有html文档;需要根据资源之间的依赖关系衡量引用资源的先后顺序;资源之间要共享变量必须定义为全局变量,造成全局变量污染严重。
随着JavaScript的应用场景越来越广,项目代码越来越多,模块化的需求也就越来越大。闭包是早期实现模块化的一个方式。例如下面代码,
javascript
// 闭包
aModule = (function (){
const hobby = '滑雪'
return {
getHobby(){
return `爱好:${hobby}`
}
}
})()
bModule = (function (){
console.log(aModule.getHobby())
const lunch = '汉堡包'
return {
getLunch() {
return `午餐:${lunch}`
}
}
})()`
模块内部可以维护自己的私有变量,但是又可以对外暴露接口供其他模块使用。缺点是模块的创建时机依赖于代码执行顺序,aModule 无法调用 bModule 里面的方法
CommonJS 模块化规范可以很好的解决全局变量污染问题以及更适用于资源相互依赖的场景。Node.js 应用 CommonJS 模块规范,将CommonJS 模块规范推而广之。
CommonJS 模块规范要点
① 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
② 每个模块内部有两个变量可以使用,require
和 module
。
③ require
用来加载某个模块;module
代表当前模块,是一个对象,保存了当前模块的信息。
④ exports
是 module
上的一个属性,保存了当前模块要导出的接口或者变量,使用 require
加载的某个模块获取到的值就是那个模块使用 exports
导出的值,如果不设置就是一个空对象。在模块中可以通过给 exports
变量赋值来给模块挂载属性
a.js
javascript
exports.name='张三'
exports.age=18
b.js
javascript
const a = require('./a')
console.log(a)
输出:
⑤ 另一种给 exports
赋值的方式如下,会直接丢弃之前所有的属性
javascript
exports.name = '张三'
exports.age = 18
module.exports = {
address:'北京市'
}
输出:
webpack 可以将CommonJS模块的代码转换为浏览器支持的代码,所以我们看一下webpack是怎么转换的。
① npm init -y
创建项目
② 安装依赖
webpack 的最新版本对于命令会有更强的校验,我觉得不方便,所以安装的是@4
cnpm install webpack@4 webpack-cli@4 --save-dev
③ 在 package.json 中设置启动命令 "dev": "webpack --devtool none --mode development"
④ 在a.js 和 b.js 中随便导出一些东西,并且在 index.js 中使用 require
引入
index.js
javascript
require('./a.js')
require('./b.js')
console.log('index.js')
⑤ 运行 npm run dev
,查看 webpack打包结果,大致结构如下
javascript
(function (modules) {
var installedModules = {}
function __webpack_require__(moduleId) {
// moduleId 就是文件名
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(__webpack_require__.s = 0);
})({
"./src/a.js":
(function (module, exports) {
exports.name = '张三'
exports.age = 18
// 相当于重新给变量指向了一个新的引用地址,所以之前挂载的属性会消失
module.exports = {
address: '北京市'
}
}),
"./src/b.js":
(function (module, exports) {
exports.address = '北京市'
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
__webpack_require__("./src/a.js")
__webpack_require__("./src/b.js")
console.log('index.js')
}),
0: (function (module, exports, __webpack_require__) {
module.exports = __webpack_require__("./src/index.js");
})
});
文件名和模块内容会以键值对的形式存储在一个对象里面,每个模块中的代码都被放在一个函数里面,以形成独立的作用域
call() 方法和 apply()
call()
方法和 apply()
方法是 JavaScript 中用于调用函数的两种方式。它们的主要区别在于参数的传递方式。
call()
方法是函数对象的一个方法,通过该方法可以调用一个函数,并将指定的对象作为函数的上下文(也称为 this 值)来执行函数。call()
方法接受的参数是一个对象和一系列的参数。第一个参数是要设置为函数上下文的对象,后面的参数是传递给函数的参数。
示例使用 call()
方法调用函数:
javascript
function greet(name) {
console.log('Hello, ' + name);
}
greet.call(null, 'John'); // 输出: Hello, John
在上面的示例中,call()
方法将 null 作为函数 greet 的上下文对象,并将字符串 'John' 作为参数传递给函数。
apply()
方法也是函数对象的一个方法,它与 call()
方法类似,用于调用一个函数并设置函数的上下文。不同之处在于它接受的参数是一个对象和一个数组,其中数组中的元素将作为参数传递给函数。
示例使用 apply()
方法调用函数:
javascript
function greet(name) {
console.log('Hello, ' + name);
}
greet.apply(null, ['John']); // 输出: Hello, John
在上面的示例中,apply()
方法将 null 作为函数 greet 的上下文对象,并将包含字符串 'John' 的数组作为参数传递给函数。
总结:
call()
方法使用一系列的参数来调用函数,并设置函数的上下文对象。
apply()
方法使用一个数组来调用函数,并设置函数的上下文对象。
两者的作用是相同的,只是传递参数的方式不同。你可以根据具体的需求选择使用哪种方法。