目录
目标
本文站在js逆向的角度总结知识,所以不讲解Webpack打包技术,只分析模块加载机制。
概述
Webpack
Webpack是一个基于模块化的构建工具,它不仅支持JavaScript,还能将CSS、图片、字体等资源统一视作模块,通过Loader转换各种类型的文件,通过Plugin插件机制扩展构建过程,最终生成适合上线部署的静态资源文件。语法结构的特点是:通过自执行函数(IIFE)将所有模块封装起来。这样可以避免全局变量污染,并且确保模块化的封装和依赖的管理。
IIFE(Immediately Invoked Function Expression)
翻译过来叫做立即执行函数表达式(正式名称) ,也常常叫它自执行函数。意思是将函数变成表达式,从而达成立刻执行该函数的目的。
IIFE语法分析
无参数的IIFE
javascript
//形式一
(function () {
console.log("hello world!");
})();
//形式二
!function () {
console.log("hello python!");
}();
//形式三
(
()=>{console.log("箭头函数!")}
)()
语法分析
以形式一为案例讲解。它的结构是**(function(){......})()**,其中:
- function(){......}是普通的js函数,只是声明并没有执行;
- 该函数外层括号将其变成了函数表达式;
- 最右边的括号表示执行垓表达式,同时也可以用它来传递参数。
有参数的IIFE
javascript
//形式一
(function (name,age,sex) {
console.log(`name:${name};age:${age};sex:${sex}`);
})("张三",14,"男");
//形式二
!function (name,age,sex) {
console.log(`name:${name};age:${age};sex:${sex}`);
}("张三",14,"男");
//形式三
(
(name,age,sex)=>{console.log(`name:${name};age:${age};sex:${sex}`);}
)("张三",14,"男")
Webpack语法分析
基本结构
数组传参
javascript
(
function fun(e) {
//加载器(调用模块的方法)
function tx(t) {
return e[t].call()
}
//执行tx方法,
tx(1)
}
)(
//模块
[
function () {
console.log("Java")
},
function () {
console.log("Python")
},
function () {
console.log("Html")
},
]
)
对象传参
javascript
(
function fun(e) {
//加载器(调用模块的方法)
function tx(t) {
return e[t].call()
}
//执行tx方法,
tx("py")
}
)(
//模块
{
java: function () {
console.log("Java")
},
py: function () {
console.log("Python")
},
html: function () {
console.log("Html")
},
}
)
代码分析
以数组形式为案例,结构是:(function fun(e){function tx(t){return e[t].call()}tx(1)})([fun1,fun2,fun3......]),其中:
- 外层是一个自执行函数 。结构是:(function(){......})()
- 这个自执行函数的实际参数是数组或对象,我们叫它模块或者插件 。比如:[fun1,fun2,fun3......]
- 内层函数是加载器 ,用于执行模块。比如:function tx(t){......}
- 执行模块的方式是传递数组下标或对象的属性名称。比如:tx(1)
- 外层函数实际上就是一个闭包,目的就是为了保证各个模块之间互不干扰。想了解闭包的同学,可以进入我的主页查看关于js闭包的文章。
缓存加载过的模块
ES5的格式
javascript
(
function (e) {
var c = {}
function fun(t) {
//是否是第一次调用
if (c[t]) {
return c[t].exports
}
//如果是第一次调用,需要保存到缓存对象c中
var o = c[t] = {
i: t,
l: !1,
exports: {}
};
e[t].call(o.exports, o, o.exports, fun)
return o.exports.exports
}
fun(0)
}
)(
[
function () {
console.log(1)
},
function () {
console.log(2)
},
function () {
console.log(3)
},
]
)
ES6的格式
module1.mjs
javascript
export function run() {
console.log(1);
}
module2.mjs
javascript
export function run() {
console.log(2);
}
module3.mjs
javascript
export function run() {
console.log(3);
}
调用文件
javascript
import { run as run1 } from './module1.mjs';
import { run as run2 } from './module2.mjs';
import { run as run3 } from './module3.mjs';
// 模拟调用模块
const modules = [run1, run2, run3];
// 调用第0个模块
modules[1]();
思考:为什么ES6模块不需要手动缓存?
答:因为ES6模块天然自带缓存机制。也就是说同一个模块只会被加载一次,第二次import的时候直接复用已经加载的模块实例。