从闭包到模块化

119 阅读3分钟

在探究为什么要用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