1. 问题描述
想要进行深拷贝,最好的选择莫过于使用 lodash.cloneDeep
方法了。那么你知道 lodash.cloneDeep
可以深拷贝模块吗?
请看下面的代码:
js
// main.js
export const a1 = {
name: "a1",
}
export const a2 = {
name: "a2",
}
export const a3 = {
name: "a3",
}
// 入口文件
// index.js
import * as mainModule from './main.js';
import { cloneDeep } from 'lodash-es'
console.log(mainModule);
console.log(cloneDeep(mainModule))
有两个文件,第一个main.js
,第二个为入口文件index.js
。如果用 webpack@5
打包,然后在浏览器内执行,能拷贝成功吗?
2. 答案及解析
如果你说不能,恭喜你答对了。下图为打包之后在浏览器里的执行结果:

记住:这种图很重要!!!
如果打断点调试一下cloneDeep
源码,很快就会找到原因:
js
// 获取tag
const tag = getTag(value)
if (tag == objectTag /* '[object Object]' */ ||
tag == argsTag /* '[object Arguments]' */ ||
(isFunc && !object)) {
} else {
if (!cloneableTags[tag]) {
// 如果不符合可拷贝tag,返回 {}
return object ? value : {};
}
result = initCloneByTag(value, tag, isDeep);
}
这里getTag
的逻辑也很简单,如果当前对象中存在 Symbol(Symbol.toStringTag)
,取该值;如果不存在,则取 Object.prototype.toString.call(obj)
。
所以这里的tag
值为"[object Module]"
。如果查看 cloneableTags
就会发现,
ts
[ object Arguments ] : true
[ object ArrayBuffer ] : true
[ object Array ] : true
[ object Boolean ] : true
[ object DataView ] : true
[ object Date ] : true
[ object Error ] : false
[ object Float32Array ] : true
[ object Float64Array ] : true
[ object Function ] : false
[ object Int8Array ] : true
[ object Int16Array ] : true
[ object Int32Array ] : true
[ object Map ] : true
[ object Number ] : true
[ object Object ] : true
[ object RegExp ] : true
[ object Set ] : true
[ object String ] : true
[ object Symbol ] : true
[ object Uint8Array ] : true
[ object Uint8ClampedArray ] : true
[ object Uint16Array ] : true
[ object Uint32Array ] : true
[ object WeakMap ] : false
很明显,"[object Module]"
是不能深拷贝的,所以直接返回了 {}
3. 再多些疑问
3.1 webpack@3
打包之后的资源可以执行成功吗?
如果你使用 webpack@3
使用同样的配置去打包这段代码,然后再在浏览器中执行打包好的代码,你会发现神奇的事发生了:

是的,你没看错,webpack@3
打包成功了。从上图能看到,这里的模块,并没有 @@toStringTag
属性,此时返回的 tag
为 "[ object Object ]"
,所以成功执行了深度拷贝。
3.2 webpack3
之后发生了什么?
下面是 v3
和 v4或者v5
里打包运行时对模块的处理,可以看出,后者定义值为 Module
的 Symbol.toStringTag
属性。


那为什么要这么做呢?下面是我的个人理解:
ES6
中的模块机制是未来标准。标识模块 ,在webpack
内部处理机制中作为一种暗示。这样做有很多好处:
- 对齐标准。也可以区分
ES Modules
和Commonjs
。 - 基于
ES6
标准,webpack
可以做tree-shaking
等优化。 - 标准中提供了元数据,
import.meta
...等等,也可以在模块处理中发挥作用。 - ...