1.函数中的作用域
1. 无论标识符声明在作用域的何处,这个标识符所代表的变量或函数都将附属于所处作用域的气泡。
2.函数作用域的含义:属于这个函数的全部变量可以在整个函数的范围内使用及复用。
function foo(a){
var b=2;
//一些代码
function bar(){
//...
}
var c=3;
}
2.隐藏内部实现
1.可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来"隐藏"它们。
2.促成这种基于作用域的隐藏方法的原因之一:最小暴露原则。
这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都"隐藏起来"。
3.规避冲突:
①隐藏作用域中的变量与函数所带来的一个好处:可以避免同名标识符之间的冲突。
function foo(){
function bar(){
i=3;
console.log(a+i);
}
for(var i=0;i<10;i++){
bar(i*2);
//i永远为3,程序将永远循环
}
}
/* 同名冲突的解决方法:
* 1.将bar函数中的i声明为本地变量 即 var i=3;
* 2.采用一个完全不同的标识符名称
*/
②全局命名空间:变量冲突的一个典型例子
当程序加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,很容易引发冲突。
③模块变量
从众多模块管理器中挑选一个来使用。使用这些工具,任何库无需将标识符加入到全局作用域,而是通过依赖管理器的机制将库的标识符显示地导入到另外一个特定的作用域中。
3.函数作用域
var a=2;
function foo(){
var a=3;
console.log(a);
}
foo();//3
console.log(a);//2
1.在任意代码块外部添加包装函数可以解决一些问题,但是不理想。
它会导致一些额外的问题:
a)必须声明一个具名函数foo(),意味着foo这个名称本身“污染”了所在作用域。
b)必须显示地通过函数名调用这个函数才能运行其中的代码
2.更加理想的是:不需要名字的函数,并且能够自动运行
下面代码中,函数会被看做函数表达式而不是一个标准的函数声明来处理。
var a=2;
(function foo(){
var a=3;
console.log(a);
})();
console.log(a);
3.函数表达式与函数声明的区别:
它们的名称标识符将会被绑定在何处。
(第一个片段中,foo会被绑定在所在作用域中。第二个片段中,foo被绑定在函数表达式自身的函数中而不是所在作用域)
4.匿名和具名
setTimeout(function(){
console.log("I waited 1 second");
},1000);
a)匿名函数表达式
缺点:调试困难、引用自身只能使用过期的arguments.callee引用,省略了代码可读性很重要的函数名。
b)行内函数表达式:函数表达式指定函数名
setTimeout(function timeoutHandler(){
console.log("I waited 1 second");
},1000);
5.立即执行函数表达式
a)IIFE:代表立即执行函数表达(Immediately Invoked Function Expression)
函数包括在一个()括号内部,在末尾加上另一个()可以立刻执行这个函数。
b)IIFE两种用法:
(function foo(){..})()
(function(){..}())
c)IIFE的进阶用法:把它们当做函数调用,并传递参数进去
var a=2;
(function IIFE(global){
var a=3;
console.log(a);
console.log(global.a);
})(window);
console.log(a);
d)IIFE的另一种应用场景:解决undefined标识符的默认值被错误覆盖导致的异常
undefined=true;
(function IIFE(undefined){
var a;
if(a == undefined){
console.log("Undefined is safe here");
}
})();
e)倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行之后当做参数传递进去。
这种模式在UMD(Universal Module Definition)项目被广泛使用。
(function IIFE(def){
def(window);
})(function def(global){
var a=3;
console.log(a);
console.log(global.a);
});
4.块作用域
块作用域的用处:变量的声明应该距离使用的地方越近越好,并最大限度地本地化。
但是,当使用var声明变量时,它写在哪里都是一样,因此它们最终都会属于外部作用域。
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏。
var foo=true;
if(foo){
var bar=foo*2;
bar=something(bar);
console.log(bar);
}
1.with
with也是块作用域的一个例子。
2.try/catch
try/catch的catch分句也会创建一个块作用域,其中声明的变量仅在catch内部有效。
3.let
let关键字可以将变量绑定到所在的任意作用域中,换句话说,let为其声明的变量隐式地劫持了所在的块作用域。
用let将变量附加在一个已经存在的块作用域的行为是隐式的。如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性移动这些块或者包含在其他块中,就会导致代码混乱。
但是,用let进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不存在。
var foo=true;
if(foo){
let bar=foo*2;
bar=something(bar);
console.log(bar);
}
console.log(bar);
为代码显式创建块可以部分解决问题:
var foo=true;
if(foo){
{//显式的块
let bar=foo*2;
bar=something(bar);
console.log(bar);
}
}
console.log(bar);
4.垃圾收集
5.let循环
for循环头部的let不仅将i绑定到for循环的块中,事实上它将重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
for(let i=0;i<10;i++){
console.log(i);
}
console.log(i);
6.const
ES6引入了const,同样可以用来创建块作用域变量,但是其值是固定的。之后试图修改值的操作都会引起错误。