《你不知道的JavaScript》 函数作用域和块级作用域

一、函数作用域

可用在代码外添加包装函数,将内部的变量和函数定义隐藏。

var a = 2;

function foo() {  // <- - 添加这一行

    var a = 3;
    console.log( a );  //3

}   // <- - 以及这一行
foo();  // <- - 以及这一行

console.log( a ); //2

这种技术必须声明一个具名函数foo(),foo本身“污染”了所在作用域。其次,必须显式地通过函数名( foo( ) )调用这个函数才能运行其中的代码。

var a = 2;

(function foo() {  // <- - 添加这一行

    var a = 3;
    console.log( a );  //3

} )();  // <- - 以及这一行

console.log( a ); //2

包装函数的声明以 (function 而不仅以function开始,函数会被当做 函数表达式而不是标准的函数声明。如果function是声明中的第一个词,则是一个函数声明,否则就是函数表达式。

函数声明和函数表达式最重要的区别是它们的名称标识符会绑定在哪。

第一个片段的foo被绑定在所在作用域,可以通过foo()来调用。第二个片段的foo被绑定在函数表达式自身的函数而不是所在的作用域。(function() {...})作为函数表达式意味着foo只能在...代表的位置中被访问,外部作用域不行。foo变量名被隐藏在自身意味着不会非必要地污染外部作用域。

二、匿名与具名

匿名函数有一些缺点:

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

可以给函数表达式指定函数名。

三、立即执行函数表达式(IIFE)

var a = 2;
(function foo() {

    var a = 3;
    console.log( a );

})();

由于函数被包含在一个括号内,因此成了一个表达式,通过在末尾加上()可以立即执行这个函数。第一个()将函数变成表达式,第二个()执行这个函数。

更多人使用改进形式(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

我们将window对象的引用传递进去,但将参数命名为global,因此在代码风格上对全局对象的引用变得比引用一个没有“全局”字样的变量更清晰。

这个模式的另一个应用场景是解决undefined标识符的默认值被错误覆盖导致的异常。将一个参数命名为undefined,但在对应的位置不传入任何值,这样就可以保证在代码块中undefined标识符的值真的是undefined。

IIFE还有一种变化的用途是倒置代码的执行顺序,将需要运行的函数放在第二位,在IIFE执行后将参数传递进去。

var a = 2;
(function IIFE( def ) {
    def(window);
})(function def( global ) {
    var a = 3;
    console.log( a );   // 3
    console.log( global.a);   //2
});

函数表达式def定义在片段的第二部分,然后当做参数(这个参数也叫def)被传递到IIFE函数定义的第一部分。最后参数def(传递进去的函数)被调用,并将window传入 当做global参数的值。

四、块作用域

在JavaScript中只有函数作用域,没有块级作用域。

for(var i = 0; i < 10; i++) {

}
console.log( i );   // 10//在for循环的头部定义了变量i,通常只是想在for循环内部的上下文使用i,而忽略了i会被绑定到外部作用域(函数或全局)。 
var foo = true;

if (foo) {
    var bar = foo * 2;
}

console.log( bar );  // 2//bar变量虽然在if声明中的上下文使用,但它们最终都属于外部作用域。

4.1 with

with也是块级作用域的一种形式,用with从对象中创建出的作用域仅在with声明中有效。

4.2 try/catch

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

try {
    undefined();
}
catch(err) {
    var a = 0;
    console.log( err );  //可以执行
}

console.log( a );   // 0;
console.log( err );  // err not found

4.3 let

ES6的let可以将变量绑定到所在的任意作用域(通常是{...})。

var foo = true;

if (foo) {
   let bar = foo * 2;
   console.log( bar );   // 2
}

console.log( bar ); //referenceError

为块级作用域显式得创建块

var foo = true;

if (foo) {
  {   // < -- 显式的块
      let bar = foo * 2;
      console.log( bar );   // 2
   }
}

console.log( bar ); //referenceError

使用let进行的声明不会在块级作用域中提升。声明的代码被运行前,声明并不“存在”。

{
    console.log(  bar );  //ReferenceError
    let bar = 2;
} 

4.4 const

ES6引入const同样能创建块级作用域,但其值是常量。

var foo = true;
if (foo) {
    var a = 2;
    const b = 3; //  包含在if中的块级作用域常量

    a = 3;
    b = 4;  //  错误
}

console.log( a );  // 3
console.log( b );  // ReferenceError
时间: 2024-08-06 03:41:44

《你不知道的JavaScript》 函数作用域和块级作用域的相关文章

函数表达式-模仿块级作用域

模仿块级作用域 Javascript中没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的 1 function outputNumbers(count){ 2 for (var i = 0; i < count; i++){ 3 console.log(i); 4 } 5 console.log(i); 6 } 在for循环中定义的局部变量i可以再for循环外访问,因为变量i是定义在outputNumbers()的活动对象中的,因此从它有定义开始,就可以在函

JS函数表达式 -- 模仿块级作用域

JavaScript没有块级作用域的概念.这意味着在语句中定义的变量,实际上是在包含函数中而非语句中创建的. 1 function outputNumbers(count){ 2 for(var i=0; i<count; i++){ 3 alert("In: " + i); 4 } 5 var i; 6 alert("Out: " + i); 7 } 8 outputNumbers(2); //In: 0 In: 1 Out:2 在这个函数中定义了一个for

0140 JavaScript作用域:概述、全局作用域、函数作用域、块级作用域

1.1 作用域概述 通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域.作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突. JavaScript(es6前)中的作用域有两种: 全局作用域 局部作用域(函数作用域) 1.2 全局作用域 作用于所有代码执行的环境(整个 script 标签内部),或者一个独立的 js 文件. 1.3 局部作用域 作用于函数内的代码环境,就是局部作用域. 因为跟函数有关系,所以也称为函数作

JS作用域与块级作用域

作用域永远都是任何一门编程语言中的重中之重,因为它控制着变量与参数的可见性与生命周期.讲到这里,首先理解两个概念:块级作用域与函数作用域. 什么是块级作用域呢? 任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域. 函数作用域就好理解了(*^__^*) ,定义在函数中的参数和变量在函数外部是不可见的. 大多数类C语言都拥有块级作用域,JS却没有.请看下文demo: //C语言 #include <stdio.h> void mai

函数作用域和块级作用域--你不知道的JavaScript

et和const在{}内声明都会变为外部不能访问的值,但是const声明的是常量,也不能修改 函数是 JavaScript 中最常见的作用域单元.本质上,声明在一个函数内部的变量或函数会在所处的作用域中"隐藏"起来,这是有意为之的良好软件的设计原则.但函数不是唯一的作用域单元.块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指 { .. } 内部).从 ES3 开始, try/catch 结构在 catch 分句中具有块作用域.在 ES6 中引入了 let

php javascript C 变量环境 块级作用域

<?php $w = 'w'; $wb = '123'.$w; $w = 'ww'; echo $wb; if(TRUE){ $wd = '123wd'; } echo $wd; if(FALSE){ $we = '123wd'; } var_dump($we);

javascript模仿块级作用域

作用域有词法作用域和块级作用域之分,javascript属于词法作用域,而在java.C++中却是块级作用域.在javascript中,只有函数能够创建作用域,作用域是以function作为边界的. 先看一段代码: function outPut(){ for(i=0;i<5;i++) { console.log(i);//0,1,2,3,4 第一个i } console.log(i);//5 第二个i }; 解释:在java等语言中,for循环这块代码中的i变量将会被销毁,第二个i打印出来就是

javascript之模拟块级作用域

在java.C++等语言中,变量i在会在for循环的语句块中定义,循环一旦结束,变量i就会被销毁.可是在javaScript中,从定义开始,就可以在函数内部随处访问.比如 function output(){ for(var i=0; i<10; i++){ } alert(i); //10 var i; //重新声明 alert(i); //10 } javaScript会对后续i的声明视而不见,如果后续声明中有变量初始化还是会执行.可以使用匿名函数来模仿块级作用域,或者使用ES6的let命令

bala001 浏览器中的JavaScript执行机制:09 | 块级作用域:var缺陷以及为什么要引入let和const?

前言:该篇说明:|请见 说明 —— 浏览器工作原理与实践 目录 在前面<07 | 变量提升:JavaScript 代码是按照顺序执行的吗?>这篇文章中,我们已经讲解了 JavaScript 中变量提升的相关内容,正是由于 JavaScript 存在变量提升这种特性,从而导致了很多于直觉不符的代码,这也是 JavaScript 的一个重要设计缺陷. 虽然 ECMAScript6(以下简称 ES6 )已经通过引入块级作用域并配合 let.const 关键字,来避开了这种设计缺陷,但是由于 Java