函数作用域
一、函数作用域
我们已经知道, 在任意代码片段外部添加包装函数, 可以将内部的变量和函数定义“隐藏” 起来, 外部作用域无法访问包装函数内部的任何内容
看下面代码:
var a = 2;
function foo() { // <-- 添加这一行
var a = 3;
console.log( a ); // 3
} // <-- 以及这一行
foo(); // <-- 以及这一行
console.log( a ); // 2
它并不理想,首先,必须声明一个具名函数 foo(), 意味着 foo 这个名称本身“污染” 了所在作用域(在这个例子中是全局作用域)。 其次, 必须显式地通过函数名(foo()) 调用这个函数才能运行其中的代码
如果函数不需要函数名
(或者至少函数名可以不污染所在作用域), 并且能够自动运行
,这将会更加理想
。
JavaScript 提供了能够同时解决这两个问题的方案
var a = 2;
(function foo(){ // <-- 添加这一行
var a = 3;
console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2
首先, 包装函数的声明以 (function...
而不仅是以function… 开始。这函数会被当作函数表达式
而不是
一个标准的函数声明
来处理。
区分
函数声明和表达式最简单的方法
是看 function 关键字出现在声明中的位置
(不仅仅是一行代码, 而是整个声明中的位置)。 【如果 function 是声明中的第一个词, 那么就是一个函数声明, 否则就是一个函数表达式】
函数声明和函数表达式
之间最重要的区别
是它们的名称标识符将会绑定在何处
比较前面两个代码片段:第一个片段中 foo 被绑定在所在作用域中
, 可直接通过foo() 调用它
。 第二个片段中 foo 被绑定在函数表达式自身的函数中
而不是所在作用域中
换句话说, (function foo(){ … }) 作为函数表达式意味着 foo 只能在 … 所代表的位置中被访问, 外部作用域则不行。 foo 变量名被隐藏在自身中意味着不会非必要地污染外部作用域
二、匿名和具名
函数表达式你最熟悉的场景可能就是回调参数了,例如:👇
setTimeout( function() {
console.log("I waited 1 second!");
}, 1000 );
这叫作匿名函数表达式
, 因为 function()… 没有名称标识符。 函数表达式可以匿名
的,而函数声明则不可以省略函数名
——在 JavaScript 的语法中这是非法的
匿名函数表达式有如下缺点:
- 没有意义的名称,调试困难
- 当函数需要引用自身时,麻烦
- 代码可读性/可理解性差
给函数表达式指定一个函数名可以有效解决以上问题。 始终给函数表达式命名(具名函数表达式)是一个最佳实践
:
三、立即执行函数表达式
var a = 2;
(function foo() {
var a = 3;
console.log( a ); // 3
})();
console.log( a ); // 2
由于函数被包含在一对 ( ) 括号内部, 因此成为了一个表达式, 通过在末尾加上另外一个( ) 可以立即执行这个函数, 比如 (function foo(){ … })()。 第一个 ( ) 将函数变成表达式, 第二个 ( ) 执行了这个函数
这叫IIFE👉立即执行函数表达式
IIFE 最常见的用法是使用一个匿名函数表达式
。 虽然使用具名函数的 IIFE
并不常见, 但它具有上述匿名函数表达式的所有优势, 因此也是一个值得推广
的实践(通过匿名函数表达式缺点得出)
相较于传统的 IIFE 形式, 很多人都更喜欢另一个改进的形式: (function(){ .. }())
这两种形式在功能上是一致的,随意选择
IIFE另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去
(第二段代码👇)
我们将 window 对象的引用传递进去, 并将参数命名为 global