作为前端,import和require是最常用的包引入形式,我们知道语法有所不同,一个属于esModule,一个属于commonjs,但除了语法层,其实还有不同之处,就是某些情况下,commonjs一直是值的复制,而esModule有时候是值的引用,即使是基础数据类型(String,Number,Boolean),本文从webpack打包后源码分析两种引入方式的区别,以及原因。
我们主要有两个文件,index引入counter的一个变量count,之后count再进行变量变更,看看不同形式的引入,值是否是引用的
commonjs形式导出(export.count=count)
//counter.js
var count = 0
exports.count = count
setTimeout(function () {
console.log('increase count to', ++count, 'in counter.js after 500ms')
}, 500)
导入方式
import
//index.js
import {count} from './counter'
console.log('第一次执行count,获取原始值',count);
setTimeout(function () {
console.log('read count after 1000ms is', count)
}, 1000)
require
//index.js
let count = require('./counter')
console.log('第一次执行count,获取原始值',count);
setTimeout(function () {
console.log('read count after 1000ms is', count)
}, 1000)
执行webpack打包
npx webpack --mode development
结果,基础类型变量不会同步状态(require,import方式均是)

require结果
count导出后,exports.count是一个变量赋值,后面的count++就不是引用的exports.count了
这跟我们平时赋值是一样的
let count = __webpack_require__ ("./src/counter.js")
之后counter里对这个值的变更自然不会影响引用的变量。
import结果
import形式用的是叫harmony import的方式,会把所有的值都挂载引用模块导出的 _counter__WEBPACK_IMPORTED_MODULE_0__
上(modulex.exports)
/* harmony import */
var _counter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/counter.js");
let count = require ('./counter')
console.log('第一次执行count,获取原始值',_counter__WEBPACK_IMPORTED_MODULE_0__[\"count\"]);
setTimeout(function () {
console.log('read count after 1000ms is', _counter__WEBPACK_IMPORTED_MODULE_0__[\"count\"])
}, 1000)

es6形式导出 (export {count})
//couter2.js
var count = 0
export {count}
setTimeout(function () {
console.log('increase count to', ++count, 'in counter.js after 500ms')
}, 500)
require结果(状态不能同步)
//index引入方式
let {count} = require('./counter2')
console.log('第一次执行count,获取原始值',count);
setTimeout(function () {
console.log('read count after 1000ms is', count)
}, 1000)

在require形式下,还是用的webpack_require模式
let {count} = __webpack_require__("./src/counter2.js\")
import结果(状态可以同步)

原因:
我们拿到的不是一个变量,而是整个模块的
_counter2__WEBPACK_IMPORTED_MODULE_0__
(实际就是module.exports),当我们要获取count时,拿的就是_counter2__WEBPACK_IMPORTED_MODULE_0__['count']
var _counter2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/counter2.js\");
console.log('第一次执行count,获取原始值',_counter2__WEBPACK_IMPORTED_MODULE_0__[\"count\"]);
setTimeout(function () {
console.log('read count after 1000ms is', _counter2__WEBPACK_IMPORTED_MODULE_0__[\"count\"])
},1000)
而在子模块,已经通过defineProperty来代理这个值,由于作用域链的原因,会找到父作用域的count,所以变化能追踪到。
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
__webpack_require__.d(__webpack_exports__, "count", function(){
return count;
});
import形式的引用只是一个getter
既然import的方式,通过defineProperty拿到这个值,那么我们能否更改呢?肯定是不行的,因为这样引用的文件会怎么更改源文件,我们是无法控制的。
import {count} from './counter2'
console.log('第一次执行count,获取原始值',count);
setTimeout(function () {
console.log('read count after 1000ms is', count)
count++
}, 1000)
运行后,会发现这个模块只有getter,因此,对于count,我们是不能更改这个值的。
这样就不怕这个值被更改了。

es6 export default形式
//counter3.js
var count = 0
export default count
setTimeout(function () {
console.log('increase count to', ++count, 'in counter.js after 500ms')
}, 500)
import结果
状态不会同步过来

这种形式类似于commonjs的形式,只是挂载在一个默认的default变量上
__webpack_exports__["default"] = (count)
setTimeout(function () {
console.log('increase count to', ++count, 'in counter.js after 500ms')
}, 500)
//输出模块的地方
var count = 0
/* harmony default export */
__webpack_exports__["default"] = (count);
setTimeout(function () {
console.log('increase count to', ++count, 'in counter.js after 500ms')
}, 500)
require结果
require同理,只是把输出挂载了default这个key上而已
//引入方式
let count = require('./counter3').default
console.log('第一次执行count,获取原始值',count);
setTimeout(function () {
console.log('read count after 1000ms is', count)
}, 1000)