在探究为什么要用webpack这个问题时,不断地接触到JS模块化的概念,无论是CommJS或者ESModel,他们的基础都是闭包,所以从闭包开始,彻底搞懂JS的模块化。
闭包
变量提升
ES6版本以后一共有三个作用域,全局作用域,函数作用域,块作用域。
JS的变量和函数都会有提升的效果,在JS进行预编译的时候就会被定义
let 在声明时会进入 script 作用域 但是因为暂存性死区的影响 所以效果是 let 不存在变量提升。实际再预编译时都会提前声明,因为js在被引擎编译时,会提前将所有变量提前
暂存性死区是指声明变量前引用该变量
console.log('start')
var func1 = function(){
console.log('i am in func1')
}
let func2 = function(){
console.log('i am in func2')
}
function func3(){
console.log('i am in func3')
}
console.log('end')
在第一行打上断点后,全局变量如下图所示
因为var 的原因 会有变量提升,而 var func1 = function(){ console.log('i am in func1')} 变量提升后 变为
var func1;
而 function 也有提升(且function 的名字和 var 变量重名时,会覆盖var 变量,可以说var 将变量 声明后,function 占用了变量的内存指向,当再次执行到 var 语句时,该变量声明的 函数 会替代 function 声明的函数)
console.log('start')
let func2 = function(){
console.log('i am in func2')
}
function func1(){
console.log('i am in func3')
}
var func1 = function(a){
console.log('i am in func1',a)
}
console.log('end')
变量提升发生在JS在浏览器编译的阶段,而let声明的变量不存在变量提升的问题,只有执行到时,才开始声明变量并执行。
立即执行函数
立即执行函数不等于匿名函数,立即执行函数一定是匿名函数。
const func = function (){console.log('123')}
后面的函数就是匿名函数,因为没有名字,但是很显然,匿名函数是没办法主动调用的,或者靠回调函数,或者靠变量。
立即执行函数有两种写法
(function(){}());
(function(){})();
立即执行函数内部的变量外部是都访问不到的。因为立即执行函数在执行开始进入自己的作用域,执行完毕就销毁作用域。(垃圾回收会回收所有变量)
闭包
形: 闭包就是函数的执行导致了函数的定义
function func1(){
retrun function(){
}
}
闭包有个和立即执行函数相同的作用,保证变量不被污染。
我有两个文件,用闭包实现了避免变量污染
let main = (function(newName){
let my = {name:'ton',age: 20};
let count = 1;
return function(newName){
my.age++;
if(newName){
my.name = newName
}
console.log(`${my.name} is ${my.age} years old.`,count++)
return "I am in main"
}
}()) //立即执行函数写法1
let another = (function(){
let my = {name:'ton',age: 23};
return function(){
my.age += 2;
console.log(`my another age is ${my.age}.`)
return "I am in another"
}
})() //立即执行函数写法2
console.log('waht happen',main())
console.log('waht happen',another())
console.log('waht happen',main('town'))
因为script的渲染是顺序的,所以变量的读取和script引入文件的顺序有关,这里先引入的main.js
输出结果:
main.js:9 ton is 21 years old. 1
anotherJS.js:9 waht happen I am in main
anotherJS.js:5 my another age is 25.
anotherJS.js:10 waht happen I am in another
main.js:9 town is 22 years old. 2
anotherJS.js:11 waht happen I am in main
可见虽然都有age 但彼此并不干扰。但受立即执行函数的影响,age变量外部是无法访问到的,但又因为闭包的原因,有函数引用了该变量,变量并不会回收。
模块化
模块化的发展是曲折的,AMD、CMD、UMD 和常用的 CJS (Common JS )和 ESM (Es module)
从闭包的例子也能得知,想要在main的访问anotherJS的函数,那anotherJS在html页面中的引用顺序一定要早于main,对于很大的项目来讲,这样的依赖关系并不好维护。
Common JS 是2009年node.js提出的时候引出的模块工具,其实是一条准则和规范。
但Common JS 是没办法再浏览器中使用的(需要借助第三方包)
而Es module是2015年ES6引入的模块化规范,现在node14以后的版本也可以用ES Module