JS基础(一)函数作用域

欢迎阅读,本篇主要介绍JS中的函数作用域.

前言

几乎所有编程语言最基本的功能之一,就是能够存储变量当中的值,并能在之后对这个值进行访问和修改。但是将变量引入程序后会引出几个有意思的问题:这些变量存储在哪里?更重要的是,如何在需要时找到它们?
这些问题说明需要一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为 作用域

什么是函数作用域?

1
2
3
4
5
6
7
8
9
10
function foo(a) {
var b = 2;
...
function bar () {
...
}
var c = 3;
}
console.log(a,b,c); // 报错
bar(); // 报错

foo(…)的作用域气泡中包含了标识符 a、b、c 和 bar,无论标识符的声明出现在作用域的何处,这个标识符所代表的变量或函数都将附属于所处作用域的气泡。在上述代码中,bar(…)拥有自己的作用域气泡。全局作用域也有自己的作用域气泡,它只包含了一个标识符:foo。
由于标识符 a、b、c、bar 都附属于foo(…)的作用域气泡,因此无法从foo()外部对它们进行访问,会报错。
但这些标识符在foo(…)内部可以被访问,同样在bar(…)内部也可以被访问(假设bar(…)内部没有同名的标识符,如果有,则采用就近原则,使用bar(…)内部的标识符)

函数作用域的含义:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。

“隐藏”内部实现

我们对于函数的传统认知就是先声明一个函数,再向里面添加代码,但反过来想:从所写代码中挑选出一个任意的片段,然后使用函数声明对它进行包装,实际上就是把这些代码“隐藏”起来了。实际的结果就是在这个代码片段周围创建了一个作用域气泡,把变量和函数包裹在一个函数作用域中,用这个作用域“隐藏”它们。

函数作用域的好处

  1. 基于最小特权原则(又叫最小授权/最小暴露原则),在软件设计中,应该最小限度的暴露必要内容,而将其他内容都“隐藏”起来。引申到作用域,如果我们将所有的变量和函数都放在全局作用域中,当然也可以在内部嵌套的作用域中访问到它们,但破坏了前面说到的“最小特权原则”。
  2. 规避冲突。避免同名标识符之间的冲突,两个标识符之间可能具有同样的名字但用途却不一样,无意间可能造成命名冲突,冲突会导致变量的值被覆盖。

函数声明和函数表达式

虽然我们已经知道,在任意代码片段外部添加包装函数,可以讲内部的变量和函数“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。但这种技术仍会导致一些额外的问题。首先,我们必须声明一个具名函数foo(),意味着foo这个名称本身“污染”了所在作用域,其次,我们必须显式的调用foo()函数才能运行其中的代码。
如果函数不需要函数名,并且能够自动运行就好了。JS为我们提供了解决方案:

1
2
3
4
5
6
7
// 函数表达式
var a = 2;
(function foo(a) {
var a = 3;
console.log(a); // 3
})();
console.log(a); // 2

首先,包装函数的声明以 (function... 而不仅是以 function... 开始,尽管看上去这并不是一个显眼的细节,但却是一个很重要的区别,函数会被当作函数表达式而不是一个标准的函数声明来处理。

1
2
3
4
// 函数声明
function foo(a) {
var b = 2;
}

区分函数声明和表达式最简单的方法是看function关键字出现在声明中的位置,如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。另外,(foo(){…})作为函数表达式意味着foo只能在…所代表的位置中被访问,外部作用域则不行。foo变量名被隐藏在自身中意味着不会非必要的污染外部作用域。

匿名和具名

1
2
3
4
// 匿名函数表达式
setTimeout(function() {
console.log('wait 1 second!');
}, 1000);

function()…没有标识符,被称为匿名函数表达式。需要说明的是:函数表达式可以是匿名的,但函数声明则不可以省略函数名,因为这在JS语法中是非法的。

立即执行函数表达式

1
2
3
4
5
6
7
// 立即执行函数表达式
var a = 2;
(function foo() {
var a = 3;
console.log(a); // 3
})();
console.log(a); // 2

由于函数被包含在()内,因此成了一个函数表达式,在末尾加上另一个()可立即执行这个函数。这种模式很常见,术语叫:IIFE,立即执行函数表达式(Immediately Invoked Function Expression);相较于传统形式,很多人更喜欢另一个改进形式:(function(){…}())。这两种在功能上完全一致,选择哪种全凭个人喜好。

1
2
3
4
5
6
7
8
 // 进阶用法
var a = 2;
(function IIFE(global) {
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window);
console.log(a); // 2