第三章 函数作用域和块作用域
函数作用域是指,属于这个函数的全部变量都可以在整个函数的范围内使用。函数上层作用域无法访问函数作用域内声明的变量,从而隐藏了函数中的变量,不会让函数中的变量对外层作用域污染。
隐藏作用域中变量和函数的另外一个好处是可以避免同名标识符之间的冲突。
例如:
1.
function test(){
function bar(a){
i = 3;
console.log(a+i); //3
}
console.log(i); //undefined
for(var i=0; i<1; i++){
bar(i * 2);
}
console.log(i);
}
test();
2.
function test(){
function bar(a){
var i = 3;
console.log(a+i); //3
}
console.log(i); //undefined
for(var i=0; i<1; i++){
bar(i * 2);
}
console.log(i); //1
}
test();
上面两个实例的唯一区别在于,例2的bar函数中对i变量进行了声明,那么bar中这个i就是bar的局部变量,它的作用域只在bar的范围内。而例1的bar中并未对i进行声明,只是赋值3,那么当程序运行时,当前bar作用域中无法找到i的声明就会到上层作用域中去找,在for循环中找到了i的声明,用于javascript中{}中var声明的变量在{}也可以访问,所以这个i的作用域实际上是foo函数作用域。所以例1中,由于未在bar中声明i变量,污染了上层for中定义的i变量,使得上层i变量的值修改成3,于是最后打印出来i的值为3。同理,例2中,由于在bar中声明了i局部变量,所以运行时,不会跑到bar的上层for中去寻找i的声明,在bar中就有i的声明,从而不会对上层for中i变量造成污染,所以i最后打印值为1。
全局命名空间
变量冲突的典型例子存在于全局作用域中。当程序中家在了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴露在顶级的词法作用域中。
例如:
var myClass = {
awesome : "stuff";
doSomething : function(){
// ...
},
doAnotherThing : function(){
//...
}
};
另外一种避免冲突的方法就是使用模块管理。
函数表达式
让函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行的方法是使用函数表达式。
例:
var a = 2;
(function test(){ //<--添加这一行
var a = 3;
console.log(a); //3
})(); //<--添加这一行
console.log(a); //2
(function test(){ ... })作为函数表达式意味着test只能在 ... 所代表的位置中被访问,外部作用域则不行。test变量名被隐藏在自身意味着不会非必要地污染外部作用域。
匿名和具名
SetTimeout( function(){
console.log(" Hello World !");
}, 1000);
这是匿名函数表达式,因为function() .. 没有名称标识符。函数表达式可以是匿名的,而函数声明则不可以省略函数名。
匿名函数的缺点:
1.匿名函数在栈追踪中不会显示出有意义的函数名,使得调试困难。
2.如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用。比如递归中调用自身。
3.匿名函数可读性比较差。
行内函数表达式非常强大且有用。给函数表达式指定一个函数名可以有效解决上述问题。
setTimeout( function timeoutHandler(){
console.log(" Hello World !");
}, 1000);
立即执行函数表达式
var a = 2;
(function test(){
var a = 3;
console.log(a); //3
})();
console.log(a); //2
用一对()括号把函数包含起来,就成了一个表达式,通过在末尾加上另外一个()可以立即执行这个函数。
第一个()将函数变成表达式,第二个()执行了这个函数。
立即执行函数表达式(IIFE)有两种形式,任选其一都可以。
(function IIFE(){ ... })() 和 (function IIFE(){ ... }()) 两种。
IIFE的一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。
例如:
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,这个参数名可以为任何你觉得合适的名字。
IIFE的另外一个应用场景是解决 undefined 标识符的默认值被错误覆盖导致的异常。
将一个参数命名为 undefined, 但是在对应的位置不传入任何值,这样就可以保证在代码块中 undefined 标识符的值真的是 undefined:
undefined = true; // 给其他代码挖了个大坑!绝对不要这样做!
(function IIFE(undefined){
var a;
if ( a === undefined) {
console.log( " Undefined is safe here!" );
}
})();
IIFE还有一种变化用户的是倒置代码的运行顺序,将需要运行的函数放在第二位, 在IIFE执行之后当作参数传递进去。这种模式在UMD项目中被广泛使用。
var a = 2;
(function IIFE(def){
def(window);
})(function def(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
});
块作用域
javascript中本来没有块作用域的概念,比如for循环,if等。在其中定义的变量都会被绑定到外部作用域,而不是for和if内部。为了让块作用域中的变量值能够在块中使用,不绑定到外部作用域中,我们在声明变量的时候不能用var,而是改用let来声明块中变量,这样这个变量的作用域才会被绑定到当前块中。
用with,try/catch的catch分句会创建 一个块作用域。
前边我们提到了提升,提升是指声明会被视为存在于其所出现的作用域的整个范围。
使用var的声明会被提升,而使用let的声明是不会被提升的。
例:
{
console.log(bar); //ReferenceError!
let bar = 2;
}
垃圾收集
function process(data){}
//在这个快中定义的内容可以销毁了!
{
let someReallyBigData = { ... };
process(someReallyBigData);
}
var btn = document.getElementById("myButton");
块作用域可以让引擎清楚的知道没有必要继续保存someReallyBigData了,可以回收了。这里的变量声明只能使用let,不能使用var。如果使用var,就会把someReallyBigData绑定到外部作用域,从而无法在块结束之后顺利把someReallyBigData的数据销毁。
posted on 2019-02-08 17:28 atomgame的记事本 阅读(115) 评论(0) 收藏 举报