函数作用域和块作用域

简介: 函数作用域和块作用域

说明

《你不知道的JavaScript》学习笔记。



函数中的作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。


举例说明:

function foo(a) {
    var b = 2;
    // 一些代码
    function bar() {
        // ...
    }
    // 更多的代码
    var c = 3;
}


上述代码分析:


   1、foo(..) 的作用域气泡中包含了标识符 a、b、c 和 bar


   2、全局作用域也有自己的作用域气泡,它只包含了一个标识符:foo。


   3、无法从 foo(..) 的外部对标识符 a、b、c 和 bar进行访问。


   4、标识符(a、b、c、foo 和 bar)在 foo(..) 的内部都是可以被访问的。


   5、标识符(a、b、c、foo 和 bar)在 bar(..) 内部也可以被访问(假设 bar(..) 内部没有同名的标识符声明)。



隐藏内部实现

1、先看一个例子:

function doSomething(a) {
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}
function doSomethingElse(a) {
    return a - 1;
}
var b;
doSomething( 2 ); // 15


上面代码中:


   1、变量 b 和函数 doSomethingElse(..) 应该是 doSomething(..) 内部具体实现的“私有”内容。


   2、给予外部作用域对 b 和 doSomethingElse(..) 的“访问权限”没有必要


   3、这种访问权限可能导致它们被有意或无意地以非预期的方式使用,具有一定的“危险性”。


2、另一个例子:

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(..) 所控制。

(最小授权或最小暴露原则)在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来。


规避冲突


   “隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突。

例如:


function foo() {
    function bar(a) {
        i = 3; // 修改 for 循环所属作用域中的 i
        console.log( a + i );
    }
    for (var i=0; i<10; i++) {
        bar( i * 2 ); // 糟糕,无限循环了!
    }
}
foo();


在上面代码中:

  • 1、bar(..) 内部的赋值表达式 i = 3 意外地覆盖了声明在 foo(..) 内部 for 循环中的 i。
  • 2、i 被固定设置为 3,永远满足小于 10 这个条件,导致了无限循环


规避冲突两种方法


1、全局命名空间

在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间


2、模块管理

通过依赖管理器的机制将库的标识符显式地导入到另外一个特定的作用域中。




函数作用域


区分函数声明和表达式最简单的:

看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。


匿名和具名

例如:

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


上面这种就叫做匿名函数表达式,因为 function()… 没有名称标识符。

  • 1、函数表达式可以是匿名的
  • 2、函数声明则不可以省略函数名——在 JavaScript 的语法中这是非法的。


匿名函数表达式书写起来简单快捷,但是有几个缺点

  • 1、调试很困难:匿名函数在栈追踪中不会显示出有意义的函数名
  • 2、可读性 / 可理解性很差…


立即执行函数表达式

术语:IIFE,代表立即执行函数表达式(Immediately Invoked Function Expression)


例如下面代码就是:

var a = 2;
(function foo() {
    var a = 3;
    console.log( a ); // 3
})();
console.log( a ); // 2


IIFE 用于倒置代码的运行顺序

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



块作用域


with

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


try/catch


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


例如:

try {
    undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {
    console.log( err ); // 能够正常执行!
}
console.log( err ); // ReferenceError: err not found


let

es6 引入关键字 let ,它可以将变量绑定到所在的任意作用域中(通常是 { … } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。

例子:

var foo = true;
if (foo) {
    let bar = foo * 2;
    console.log( bar ); // 2
}
console.log( bar ); // Uncaught ReferenceError: bar is not defined



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

{
    console.log( bar ); // Uncaught ReferenceError: Cannot access 'bar' before initialization
    let bar = 2;
}



1、垃圾收集

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


2、let 循环


例子:

for (let i=0; i<10; i++) {
console.log( i );
}
console.log( i ); // Uncaught ReferenceError: i is not defined



for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环

的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。



const


ES6 还引入了 const,同样可以用来创建块作用域变量,但其值是固定的(常量)。之后任何试图修改值的操作都会引起错误。


例子:如果在if里面写了b = 4这个赋值,就会导致报错,程序不会往下执行,去掉这个赋值就会往下执行。

var foo = true;
if (foo) {
    var a = 2;
    const b = 3; // 包含在 if 中的块作用域常量
    a = 3; // 正常 !
    b = 4; // Uncaught TypeError: Assignment to constant variable.
}
console.log( a ); // 3
console.log( b ); // Uncaught ReferenceError: b is not defined.



目录
相关文章
|
1月前
|
JavaScript 前端开发
块级作用域和函数作用域有什么区别?
【10月更文挑战第29天】块级作用域和函数作用域在JavaScript中各有特点和用途。块级作用域提供了更精细的变量控制,有助于避免变量提升和意外的全局变量污染等问题;而函数作用域则在函数封装和模块化编程等方面有着重要的应用。在实际开发中,需要根据具体的需求和场景合理地选择使用哪种作用域来声明变量和组织代码。
|
2月前
|
自然语言处理 JavaScript 前端开发
词法作用域和静态作用域有什么区别
【10月更文挑战第12天】词法作用域和静态作用域有什么区别
|
7月前
|
存储 程序员 C++
C++程序局部变量:生命周期与作用域的探讨
C++程序局部变量:生命周期与作用域的探讨
184 1
|
3月前
|
Java
作用域
作用域
24 2
|
3月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
7月前
|
JavaScript 前端开发 Python
函数与作用域
编程中的函数与作用域概念。函数是可重用的代码块,能提高代码的可读性、可维护性和复用性。基础用法包括定义、调用和返回值。高级用法涉及函数嵌套、匿名函数(lambda函数)和装饰器。装饰器能在不修改原函数代码的情况下添加功能。 作用域决定了变量的可见范围,从内到外是局部、嵌套、全局和内置作用域。闭包是能访问外部函数变量的内部函数,即使外部函数执行完毕,闭包仍能保留其状态。闭包常用于实现特殊功能,如记忆化和延迟执行。 立即执行函数表达式(IIFE)是JavaScript中的模式,用于创建私有作用域和防止变量污染全局。IIFE常用于封装变量、避免命名冲突以及实现模块化和函数作为参数传递。
|
7月前
|
JavaScript 前端开发 Java
什么是作用域,它的作用是什么?
什么是作用域,它的作用是什么?
202 1
|
Linux 网络架构
暂时性死区以及函数作用域
暂时性死区以及函数作用域
186 0
|
自然语言处理 JavaScript 前端开发
作用域是什么
作用域是什么
128 0