从webpack源码看require,import的区别

2,886 阅读3分钟

作为前端,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方式均是)


image.png

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)

引用方式类似以下:

image.png

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)

image.png

在require形式下,还是用的webpack_require模式

let {count} = __webpack_require__("./src/counter2.js\")

import结果(状态可以同步)


image.png


原因:
我们拿到的不是一个变量,而是整个模块的_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,我们是不能更改这个值的。
这样就不怕这个值被更改了。

image.png

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结果


状态不会同步过来

image.png


这种形式类似于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)