let和块级作用域

1.1 函数作用域

在ES5中,JS的作用域分为全局作用域和局部作用域。通常是用函数区分的,函数内部属于局部作用域。

//ES6之前只有函数才构成局部作用域
//案例1
{ var a = 10; }
console.log(a);

//案例2
console.log(a);
if (true) {
    var a = 10;
}
console.log(a);

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景

  • 内层变量可能会覆盖外层变量。

    var b = 1;
    fn1();
    function fn1() {
        console.log(b);//undefined
        if (true) {
            var b = 2;
        }
    }
  • 用来计数的循环变量泄露为全局变量。

    //变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变
    for (var i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log(i);//5 5 5 5 5
        })
    }

1.2 块级作用域

  • 在ES6中新增了块级作用域的概念,使用{}扩起来的区域叫做块级作用域

  • let关键字声明变量,实际上为 JavaScript 新增了块级作用域。

  • 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。

  • 在块内使用let声明的变量,只会在当前的块内有效。

//全局声明一个变量b
let b = 1;
fn1();
function fn1() {
    //当前的作用于没有b,沿着作用于寻找到全局的b
    console.log(b);
    if (true) {
        //只在当前的作用于中生效
        let b = 2;
    }
}

1.3 let关键字

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效,也就是增加了块级作用域。

  • 使用块级作用域(let定义的变量属于块级作用域) 防止全局变量污染

    {
        let b = 20;
    }
    console.log(b);//Uncaught ReferenceError: b is not defined
    
  • 块级作用域可以任意嵌套

    //外层作用域无法读取内层作用域的变量
    {
        {
            let a = 1;
            {
               console.log(a); //1
            }
        }
        console.log(a);//Uncaught ReferenceError: a is not defined
    }
  • for循环的计数器,就很合适使用let命令,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

    • for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
    • for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。
    //计数器i只在for循环体内有效,在循环体外引用就会报错
    for (let i = 0; i < 4; i++) {
        console.log(i);
    }
    console.log(i);
    
    for (let i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log(i);//0 1 2 3 4
        })
    }
  • 练习

    //练习1:
    var a = [];
    for (var i = 0; i < 10; i++) {
        a[i] = function () {
            console.log(i);
        };
    }
    a[6]();
    
    // 练习2:
    var a = [];
    for (let i = 0; i < 10; i++) {
        a[i] = function () {
            console.log(i);
        };
    }
    a[6]();

1.4 let关键字特点

  1. let同样存在变量提升,只是let存在暂时性死区。(在赋值之前读写变量都会导致 ReferenceError 的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。)

    //ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错)
    // 这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错
    let c = 1;
    {
        console.log(c);//Uncaught ReferenceError(引用错误): Cannot access 'c' before initialization
        let c = 2;
    }
  2. 不允许重复声明

    // let不允许在相同作用域内,重复声明同一个变量
    // 报错
    function func() {
        let a = 10;
        var a = 1;
    }
    
    // 报错
    function func() {
        let a = 10;
        let a = 1;
    }
  3. 块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了

    // IIFE 写法
    (function () {
        var tmp = "...;"
    }());
    
    // 块级作用域写法
    {
        let tmp = "...;"
    }

2 const关键字

常量:不会变化的数据,有些时候有的数据是不允许修改的,所以需要定义常量。

  • const声明一个只读的常量。定义常量可以写大写。

    const PI = "3.1415926";
    console.log(PI)
  • const声明的常量不得改变值

    const PI = "3.1415926";
    PI = 22;//Assignment to constant variable.(报错:给常量赋值)
  • const声明的常量如果是对象,可以修改对象的内容

    // const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
    //但是对象的引用不能被修改,比如用一个对象替代这个对象,那么引用就被修改了
    const PATH = {};
    PATH.name = "lily";
    console.log(PATH);//{name:"lily"}
    PATH = {}//Assignment to constant variable
  • const一旦声明变量,就必须立即初始化,不能留到以后赋值

    const time;//SyntaxError: Missing initializer in const declaration
  • const只在声明所在的块级作用域内有效

    {
        const PI = 3.14;
    }
    console.log(PI)//ReferenceError(引用错误)PI is not defined
  • const命令声明的常量也拥有暂时性死区

    console.log(c);//Cannot access 'c' before initialization
    const c = "hello"
  • const声明的常量,也与let一样不可重复声明

    const PI = "3.14";
    const PI = "3.14";//SyntaxError: Identifier(标识符) 'PI' has already been declared(声明)
  • let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

    let a = 1;
    console.log(window.a);//1
    var a = 2;
    console.log(window.a)//undefined

3 块级作用域的函数声明

函数声明一般常用的是两种,一种是function声明,一种是函数表达式。

//函数声明 及 两种声明的区别
f1()//可以使用
f2();//f2 is not a function

function f1() {}
var f2 = function () {

}

函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。

  • ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
  • ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
  • ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式
  • 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
//尽量避免以下写法
fn1();
if (true) {
    function fn1() {
        alert(1);
    }
}
fn1();

//如果真的想在声明块级作用域函数,使用函数表达式
if (true) {
    let fn1 = function () {
        alert(1);
    }
}
posted @ 2023-01-29 13:24  z_bky  阅读(246)  评论(0)    收藏  举报