问题来源
在学习node的commonjs模块化的过程中,对于模块导出,node提供两种解决方式
exports.xxx = xxxx;
module.exports = xxxx;
但是在模块编译的过程中,外层包裹的函数里面是传递了exports的参数,但是为什么使用以下的方式就无法导出模块,以下是require的模拟代码:
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {}
exports = '1111'; // 无法导出,不生效
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
console.log(require()); // [Function: someFunc]
初步分析
在编译的流程中,首先是先找到模块的位置,然后加载模块,中间会对模块进行首尾包裹,方便在后面执行的时候可以直接获取到module.exports的对象。 而在具体的实现中,是通过构造module对象,然后将module和exports传递到函数内,而exports本身也是一种对象引用,传递到里面之后,对这个引用 进行赋值没有生效,所以直接对js传递进来的引用没有办法直接改变对象本身,这就是涉及到求值策略的机制了。以下是基于C++的求值策略相关知识。
参数传递机制(求值策略)
按值传递
把参数的实际值复制给函数的形参,修改函数内的形参,不会影响到实参。C++默认使用此传值策略。
按指针传递
该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数
按引用传递
该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
C++ 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的参数。而JavaScript则是只有按值传递的方式。如果 是基本类型,则复制基本类型的值给形参,如果是引用类型,则是复制引用值到形参中。
这里的引用类型是后面C++推出的新的复合类型,相当于指针功能的子集。而引用类型算是做了安全限制的指针,比如强制要求必须初始化,不能更改
指向等。因此在大部分场景,C++的求值策略一般推荐使用引用传递的方式。如在node源码中,buffer的compare内置方法就使用了:
node解决方案
js语言本身是使用C++的V8引擎实现的。js对基本类型采用的是传值,但是对于引用类型的,使用的是C++的按引用传递(引用是更安全的指针),传递的是引用类型的地址。只是相对于按指针传递来说,这个引用只能被修改,不能被覆盖,因此更为安全。现在大部分的C++应用也是推荐使用按引用传递的。
各种传值策略的特点

最佳实践
使用module.exports来统一导出模块,首先易读性强,另外也能避免歧义。