知识点
1.函数中的作用域
函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
function foo(a){
var b = 2;
// 一些代码
function bar(){
// ...
}
// 更多的代码
var c = 3;
}
bar(); // 失败
console.log(a,b,c); // 三个全都失败
复制代码
2.隐藏内部实现
可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。
为什么“隐藏”变量和函数是一个有用的技术?
最小特权原则:在软件设计中应该最小限度地暴露必要内容,而将其它内容都“隐藏”起来,比如某个模块或对象的API设计。
function doSomething(a){
b = a + doSomethingElse(a * 2){
console.log(b * 3);
}
}
function doSomethingElse(a){
return a - 1;
}
var b;
doSomething(2); // 15
// 最小特权原则改进
function doSomething(a){
function doSomethingElse(a){
return a - 1;
}
var b;
b = a + doSomethingElse(a * 2){
console.log(b * 3);
}
}
doSomething(2); // 15
复制代码
3.规避冲突
“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突。
function foo(){
function bar(a){
// var i = 3;
i = 3; // 修改for循环所属作用域中的i
console.log(a+i);
}
for(var i = 0; i < 10; i++){
bar(i * 2); // 糟糕,无限循环了!
}
}
复制代码
可以使用var i = 3修改上面的代码,得到正确的结果。在这种情况下使用作用域来“隐藏”内部声明是最佳选择。
3.1 全局命名空间
变量冲突的一个典型例子存在于全局作用域中。当程序中加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。
这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象,被用作命名空间,所有需要暴露给外界的功能都是该对象的属性。
var MyReallyCoolLibrary = {
awesome: "stuff",
doSomething: function(){
// ...
}
doAnotherThing: function(){
// ...
}
}
复制代码
3.2 模块管理
另外一种避免冲突的方法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来使用。
4.函数作用域
在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。
var a = 2;
function foo(){ // 添加这一行
var a = 3;
console.log(a);
} // 以及这一行
foo(); // 以及这一行
console.log(a);
复制代码
这样会导致的问题是,首先必须声明一个具名函数foo(),意味着foo这个名称会“污染”所在作用域(在这个例子中是全局作用域)。其次,必须显示地通过函数名(foo())来调用才能运行。
JavaScript提供了能够同时解决函数不需要函数名,并且能够自动运行的方案。
var a = 2;
(function foo(){
var a = 3;
console.log(a); // 3
})()
console.log(a); // 2
复制代码
函数声明和函数表达式的区别是function是否是声明的的第一个词,如果是就是函数声明。
(function foo(){..})作为函数表达式意味着foo只能在..所代表的位置中被访问。外部作用域则不行。foo变量被隐藏在自身中意味着不会非必要地污染外部作用域。
5.立即执行函数表达式(IIFE)
始终给函数表达式命名是一个最佳实践。
setTimeout(function timeoutHandler(){ // 快看,我有名字了!
console.log("I waited 1 second");
},1000)
复制代码
(function(){...})(),第一个()将函数变成表达式,第二个()执行了这个函数。
IIFE的进阶用法是把它们当做函数调用并传递参数进去。
var a = 2;
(function IIFE(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window)
console.log(a); // 2
复制代码
IIFE的另一种用途是倒置代码的运行顺序。
var a = 2;
(function IIFE(){
def(window);
})(function def(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})
复制代码
函数表达式def定义在片段的第二部分,然后当做参数(这个参数也叫做def)被传递进IIFE函数定义的第一部分中。最后,参数def(也就是传递进去的函数)被调用,并将window传入当做global参数的值。
6.块作用域
6.1 let
let 关键字可以将变量绑定到所在的任意作用域中(通常是{..}内部)。let为其声明的变量隐式地劫持了所在的块作用域。
var foo = true;
if(foo){
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
console.log(bar); // ReferenceError
复制代码
var foo = true;
if(foo){
{ // 显式的块
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
}
console.log(bar); // ReferenceError
复制代码
let 进行的声明不会在块作用域中进行提升。
{
console.log(bar); // ReferenceError
let bar = 2;
}
复制代码
6.2 const
var foo = true;
if(foo){
var a = 2;
const b = 3; // 包含在if中的块作用域常量
a = 3; // 正常
b = 4; // 错误!
}
console.log(a); // 2
console.log(b); // ReferenceError
复制代码
总结
函数是JavaScript中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,这是有意为之的良好的软件设计原则。
但函数不是唯一的作用域单元。块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指{...}内部)。
巴拉巴拉
关于标签
一朋友在我对自己产生严重质疑的时候对我说,不要轻易给自己加标签,当然也不要轻易给别人下定义。聊这个事情的时候,并没有醍醐灌顶,倒是最近经历的一些事,让我对他说的话,有了一些不一样的感悟。比如,你某件事没有做好,你的上级可能会觉得你没做好的原因是不够认真,经验不足,能力不够等,一旦他把其中的某个词加在你身上,你的第一反应可能他是对的,我就是这样的,甚至还会自我加注更多类似的。我想说这样是不对的,因为你可能无意中就多了一个困扰自己的负面标签。正确的做法应该是,他也许是对的,我还有需要提升,改正的地方,尽量避免正面的去同意他的话,而是从积极的角度说可以改正,提升的地方,很多事,是没有绝对的对错和好坏的,一个原则是,尽量不要给自己加注负面的具体的标签,因为会给人更深的印象,让人永远记住黑历史。