Skip to content

作用域与IIFE

元信息

  • 目标:理解模块化诞生前夜的语言背景——js 只有全局/函数/块级作用域而无文件作用域,多脚本共页必然名字冲突;掌握用 IIFE 圈出私有空间、化解变量与函数名冲突的手法
  • 关键概念:全局作用域、函数作用域、块级作用域、变量名冲突、IIFE、闭包
  • 关联阶段:cp3
  • 常见误区:以为每个 js 文件有独立作用域(无文件作用域,引入即共享全局);以为 IIFE 是新语法(只是"定义后立即调用"的函数表达式惯用法);以为改名就能躲开冲突(库一多指数爆炸,收进 IIFE 局部才是解)

Javascript的作用域

  • 全局作用域(global scope)
  • 函数作用域(function scope)
  • 块级作用域(block scope)

由于早期脚本语言的设计定位,传统 js 脚本没有文件作用域(file scope)——即"每个文件独享一个作用域"并不成立:页面引入的多个 <script> 共享同一个全局作用域。文件作用域(近似于模块作用域或命名空间)要到 ESM 才出现。

import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;

问题:变量名冲突

import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;

counter变量用于保存状态,但如果放置于全局空间,即当前文件引入了另一个js文件,而该js文件中的脚本也使用了counter变量即变量名冲突,将形成冲突,怎么办?

解决方法:闭包

import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;

  • 上述代码先是定义了一个匿名函数,并且被立即执行。执行后的结果是另一个匿名函数对象的引用
  • 利用了js中的闭包特性,来解决变量的生命周期(全局可用)和作用域(局部可见)的问题,从而实现了变量的本地化
  • 函数式编程的特性:函数可以被动态地定义,允许作为输入参数、输出参数。

问题:函数名称冲突

$()函数是个十分常用的名字,曾经有多个流行的js库都定义了$(),如Jquery、Prototype等。因此,当某页面需要同时引用这两个库或者当你的自编库也定义了$()函数。那么,如何避免冲突?

javascript
<script src="./jquery.js"></script>

  <script src="./prototype.js"></script>

  <script>
  $('#info').text('该字段不能为空!');  // `$()`函数出现歧义
</script>

解决方法:立即执行函数

立即执行函数(IIFE, Immediately Invoked Function Expression)即"定义后马上调用"的函数表达式惯用法,它借助闭包实现私有变量,用于解决函数名称冲突的问题:

  • 通过声明一个匿名函数,将$名字仅限于该匿名函数内部
  • 然后立即执行该函数,在执行时,将传入的外部函数名称动态映射为内部函数名称
javascript
(function ($) {   
  $('#info').text('该字段不能为空!'); 
})(window.jQuery);
import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;

IIFE帮助开发者间接实现了一个局部化的作用域,从而:

  • 模块化: 外部函数和内部函数可以同名;不同的库只需要保持唯一的库名;一般来说,库名远远少于函数名且易于管理。
  • 依赖注入:外部库以参数传入、映射为形参名,接口不变而来源可替换
  • 性能提升:全局变量的局部化,减少对全局变量作用域的查找

参考