三、函数作用域和块作用域:
一)、函数中的作用域:
JS具有基于函数的作用域。
二)、隐藏内部实现:
由于函数具有单独的作用域,因此可以将一段代码包装在一个函数中,达到隐藏内部实现的作用。这基于 最小暴露原则。这些被隐藏的内容不只有变量,函数也应该被适当隐藏起来。
// 代码1:
function doSomething(a) {
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething(2); // 15
// 代码2:
function doSomething(a) {
function doSomethingElse(a) {
return a - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
doSomething(2); // 15
对上面的代码进行分析可以看出,doSomethingElse函数与b变量只在doSomething函数中被调用和引用。因此像代码1一样将这些变量和函数暴露在全局作用域中是没有必要且不合适的(甚至是“危险的”)。
规避冲突:
适当的隐藏作用域中的变量和函数可以帮助避免同一作用域中同名标识符之间的名称冲突。
全局名称空间:
程序加载第三方库时,第三方库通常采用在全局作用域中声明一个名称足够独特的变量(通常是一个对象),来用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴露在顶级的词法作用域中。
例如:
var MyReallyCoolLibrary = {
awesome: "stuff",
doSomething: function() {
// ...
},
doAnotherThing: function() {
// ...
}
};
模块管理:
利用作用域的规则强制所有标识符都不能注入到共享作用域中,而是保持在私有、无冲突的作用域中。
三)、函数作用域:
// 代码1
function foo() {
var a = 3;
console.log(a);
}
前面提到可以将变量或函数包装在一个外层函数(例如:foo)中,达到对外部作用域隐藏的效果。但是这也带来了问题:
- foo这个函数也“污染”了外部作用域。
- 必须显式地通过foo这个函数名才可以访问被隐藏起来的内部实现。
JS为我们提供了解决方案:
// 代码2
(function foo() {
var a = 3;
console.log(a);
})();
比较这两段代码的不同:代码2的foo函数被包装在()中,这样函数会被视作函数表达式而不是一个标准的函数声明。
- 如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
1、匿名和具名:
函数表达式是可以匿名的,而函数声明则不可以省略函数名。
Cons:
- 匿名函数由于没有函数名,增加了调试难度。
- 由于没有函数名,函数引用自身时只能使用已经过期的 argumens.callee引用.
- 降低代码可读性。
2、立即执行函数表达式:
上文的代码2还有一个点,函数的最后加上了一个小括号,就是函数调用的意思。这样这个函数表达式就可以立即调用。这被称作IIFE(Immediately Invoked Function Expression),立即执行函数表达式。
- 在上面的代码中,也可以将最后一对()直接写在花括号后面。
- IIFE中也可以传递参数。
四)、块作用域:
Q:为什么要有块作用域?
A:变量的声明应该距离使用的地方越近越好,并最大限度地本地化。
一般的编程语言中,if、for等语句都拥有自己的块作用域。但是,在JS中,使用var声明的变量并不支持块作用域,它写在哪里都是一样的,都属于外部作用域。
ES6中引入了新的let、const关键字。使用它们声明的变量不会在块作用域中进行提升。声明的代码被运行之前,声明并不“存在”。
- const常量的值是固定的,之后任何视图修改值的操作都会引起错误。
// 代码1
var foo = true;
if (foo) {
var a = 3;
}
console.log(a); // 3
for (var i = 0; i < 5; i++) {
// ...
}
console.log(i); // 5
// 代码2
var foo = true;
if (foo) {
let a = 3;
}
console.log(a); // ReferenceError
for (let i = 0; i < 5; i++) {
// ...
}
console.log(i); // ReferenceError
垃圾收集:
使用{}创建块作用域可以帮助引擎进行垃圾收集工作(收回在内存中占用的空间,取决于引擎的具体实现)。
其他创建块作用域的方法:
- with
- try/catch