Skip to content

函数作用域和块级作用域

函数中的作用域

DANGER

函数作用域的含义是指,属于这个都可以在也可使用)

隐藏内部实现

简介

函数的传统认知就是声明有一个函数,然后在内部添加代码。但是反过来,而是在这个代码片段的周围创建了一个作用域气泡,即把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。这体现了

javascript
// 传统写法
function doSomething(a) {
  b = a + doSomethingElse(a * 2);
  console.log(b * 3);
}
function doSomethingElse(a) {
  return a - 1;
}
var b;
doSomething(2); //15
// 变量b和doSomethingElse(..)都可以在外部作用域访问,但它们应该是doSomething内的私有内容
// 改写
function doSomething(a) {
  function doSomethingElse(a) {
   return a - 1;
  }
  var b;
  b = a + doSomethingElse(a * 2);
  console.log(b * 3);
}
doSomething(2); //15
// 这种写法b和doSomethingElse(..)都无法从外部被访问,被doSomething私有化了

规避冲突

,可以之间的,避免冲突导致变量的值被
javascript
function foo() {
  function bar(a) {
    i = 3; //修改了for循环内所属作用域中的i的值,使得i一直为3,死循环
    console.log(a + i);
  }
  for(var i=0; i<10; i++) {
    bar(i*2); // 无限循环
  }
}
foo();
1. 全局命名空间

简介

在引用一些库时会发现,这些库在全局作用域中声明一个名字足够独特的变量,通常是。这个对象被用作库的,给外界暴露的功能为这个对象的属性。

javascript
var myLibrary = {
  name:'xxx',
  eating: function() {},
  working: function() {}
}
2. 模块管理

简介

使用模块管理器,任何库无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显式地导入到另外一个特定的作用域中。

函数作用域

简介

但是这种技术可以解决一些问题,但它会导致一些额外问题。首先必须声明一个具名函数,这就意味着具名函数“污染”了所在作用域,而且必须显示的调用才能运行。

匿名和具名

javascript
setTimeout(function (){
  console.log("I waited 1 second!");
},1000);

具有名称标识符的function functionName()..就是具名函数表达式。其次没有名称标识符的function()..就是匿名函数表达式(但是它也有几个缺点)。

  1. 匿名函数在栈追踪不会显示出有意义的函数名调试困难
  2. 如果函数没有函数名,当函数需要引用自身时只能使用过期的argument.callee引用,比如在递归中,另一个例子,是在事件触发后事件监听器需要解绑自身。
  3. 匿名函数省略了对代码可读性/可理解性很重要的函数名

立即执行函数表达式

立即执行函数(Immediately Invoked Function Expression),被规定了一个术语叫做IIFEIIFE最常见的用法是使用一个匿名函数表达式。立即执行函数表达式的形式为,即(function(){..})()。第二个括号能够当作函数调用将参数传递进去。

javascript
// 立即执行函数表达式其他形式。
(function(){..})()
(function(){..}())
// 如果你不关心函数返回值或者你的代码变得难以阅读
// 你可以在函数前面加一个一元运算符

!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

IIEF另外一种用途是倒置代码的运行顺序,可以将需要运行的函数放在第二位,在IIEF执行之后当作参数传递进去,这种模式叫做UMD(Universal Module Definition)

javascript
// 传递参数案例
var a = 2;
(function IIEF( global ) {
  var a = 3;
  console.log( a ); //3
  console.log( global.a ); //2
})( window );
console.log( a ); //2
// UMD 案例
(function IIEF(def) {
  def(window);
})(function def( global ){
  var a = 3;
  console.log(a); //3
  console.log(global.a); //2
})

块作用域

块级作用域就是由大括号界定的范围详见MDN

非严格模式var函数声明时,是没有块级作用域的。使用letconstfuntion,是有块级作用域的

javascript
var x = 1;
{
 var x = 2;
}
// 严格模式下
console.log(x) // 1
// 非严格模式下
consoloe.log(x) // 2

1. with

用with从对象中创建出的作用域仅在with声明中而非外部作用域中有效。

2. try/catch

ES3规范中规定try/catch的catch分句会创建一个块作用域,其中声明的变量有效。

javascript
try {
  undefined(); // 执行一个非法操作
} catch (err) {
  console.log(err) // 正常运行
}
console.log(err); // err not found

3. let

let关键词可以将变量绑定到所在的任意作用域中(通常是{..}内部)。let为其声明的地了所在块作用域。所以在声明有效的情况下,在声明中的任意位置都可以使用{..}来为let创建一个用于绑定的块,但是使用let进行的声明中进行

javascript
{
  console.log( bar ); // ReferenceError!
  let bar = 2;
}
  • 垃圾收集

块级作用域和闭包及内存垃圾的回收机制有关。

  • let循环

for循环头部的let不仅将i到了中,事实上它将其到了中,确保使用上一个的值进行进行

javascript
for(var i=0; i<10; i++){
  console.log(i);
}
console.log(i); // 10
for(let i=0; i<10; i++){
  console.log(i);
}
console.log(i); // RefferenceError
// let重新绑定可以理解如下形式
{
  let j;
  for(j=0; j<10 ;j++){
    let i = j; //每个迭代重新绑定!
    console.log(i);
  }
}

4. const

除了let之外,ES6还引入了const,同样可以用来创建作用域变量,但的(常量)。之后无法修改。

小结

函数是JavaScript中最常见的作用域单元。本质上,声明一个起来,体现了最小特权原则。块作用域指,或是