函数作用域和块作用域
函数作用域
函数作用域:属于这个函数的全部变量都可以在整个函数的范围的范围内使用及复用。
最小暴露原则:最小限度地暴露必要内容,而将其他内容隐藏起来,例如某个模块或对象的API。
在任意代码片段外部包装函数,可以隐藏内部的变量和函数定义,外部作用域无法访问。但是导致一些问题:1.必须声明一个具名函数foo( ),foo这个名称本身‘污染’了所在的作用域;2.必须显式地通过这个函数名(foo())调用这个函数才能运行其中的代码。
问题:1.函数名污染所在作用域;2.自动运行不需要调用
var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2
以上面的例子分析。
- 函数的声明以(function开始,而不是function,函数会被当成函数表达来处理,而不是标准的函数声明。
- 函数声明和函数表达式之间最重要的区别是:它们的名称标识符将会绑定在何处。(如果function是声明中的第一次词,那就是一个函数声明,否则就是一个函数表达式)
- 在上面的例子中,foo 被绑定在函数表达式自身的函数中而不是所在的作用域中。
- (function foo(){ … })作为函数表达式意味着foo只能在 … 所代表的位置中访问,外部作用域则不行。foo变量隐藏在自身中,不会污染外部作用域。
匿名和具名函数表达式
setTimeout(function(){
console.log('123');
},1000);
该函数表达式为匿名函数表达式,因为没有名称标识符。
(函数表达式可以是匿名的,而函数声明则不可以省略函数名)
缺点:
- 匿名函数在栈追踪中不会显示有意义的函数名,使得调试很困难.
- 没有函数名,函数需要引用自身的时候只能使用已经过期的arguments.callee引用,例如递归。
- 匿名函数降低了函数可读性。
立即执行函数表达式
var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2
还是在上面这个例子中:对foo函数处理。
第一个( )将函数变成表达式,第二个( )执行了这个函数。
IIFE:立即执行函数。常见用法是使用一个匿名函数表达式。函数名对于IIFE来说非必须。
另一种形式:(function( ){ … }( ))。两种形式功能完全一致。
IFFE的另一个用法:当做函数调用并传递参数进去。
var a = 2;
(function IIFE(global){
var a = 3;
console.log(a);//3
console.log(global.a);//2
})(window);
console.log(a);//2
将window对象的引用传递进去,将参数命名为global。传入的可以是外部作用域的任何东西,此时外部作用域就是window,所有传window和this的效果是一样的。
块作用域
函数作用域是是最常见的作用域单元。
for(var i=0; i<10; i++){
console.log(i);
}
上面是一个经典的例子,在for循环的头部直接定义了变量i,通常是因为只想在for循环内部的上下文中使用i,但是会忽略掉i会被绑定在外部作用域中。
变量的声明应该距离使用的地方越近越好,并且最大限度地本地化。
var foo = true;
if(foo){
var bar = foo * 2;
bar = something(bar);
console.log(bar);
}
上面的例子中,bar变量的声明仅在if声明的上下文使用,但是,使用var声明变量时,写在哪里都是一样的,它最终还是属于外部作用域。这个只是形式上的块作用域。
let
let关键字可以将变量绑定到所在的任意作用域中(通常是{…}内部)。换句话说,let为其声明的变量隐式地劫持了所在的块作用域。
for(let i=0; i<10; i++){
console.log(i);
}
for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保上一个循环迭代结束时的值重新进行赋值。
提升
变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。
只有声明本身会被提升,而赋值或其它运行逻辑会留在原地。
foo();
function foo(){
console.log(a)//undefined
var a = 2;
}
上面的例子中,foo函数的声明被提升了。
每个作用域都会有提升操作。foo函数内部的变量a也被提升。
foo();//TypeError
var foo = function(){
//...
}
函数表达式不会被提升。
函数优先
函数声明和变量都会被提升,但是函数会首先被提升,然后才是变量。
foo();//1
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}
一个普通块内部的函数声明通常会被提升到所在作用域的顶部。
foo();//b
var a = true;
if(a){
function foo(){console.log('a');}
} else {
function foo(){console.log('b');}
}
上面例子中foo函数的两个声明都被提升到当前作用域顶部,else中的覆盖了if中的foo函数声明。