首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES6语法学习(let与var区别、块级作用域、const命令)

ES6语法学习(let与var区别、块级作用域、const命令)

原创
作者头像
帅的一麻皮
修改2020-06-22 10:44:03
9860
修改2020-06-22 10:44:03
举报
文章被收录于专栏:前端与Java学习前端与Java学习

01-let与var的区别:

1.1-let声明的变量只在let命令所在的代码块内有效

1.1.1let与var在代码块中的区别:

{
    let a = 10;
    var b = 1;
}
console.log(b)//1
console.log(a)//报错 a is not defined

for循环的计数器就很适合let:

//以下i只在for循环体内有效,在循环体外就会报错
for(let i=0;i<10;i++){
   //执行代码
}
console.log(i);//报错i is not defined

1.1.2示例:

下面的代码中,变量ivar声明的,所以i是一个全局变量在全局范围内都有效,所以全局只有一个变量i,每一次循环i的值都会发生改变,被赋给数组a的函数内部的console.log(i)中的i指向全局的i,因此所有数组a的成员中的i指向的都是同一个i,导致运行时输出的是最后一轮的i值10

var a = [];
for (var i = 0; i < 10; i++) {
     a[i] = function () {
         console.log(i);
     }
}
a[6]();//10

当我们使用let再来观察一下,变量ilet声明,当前的i只在本轮循环中有效,所以每一次循环都是一个新的变量,于是最后输出6。

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

思考:如果每一轮循环的变量i都是重新声明的,那么它怎么知道上一轮循环的值从而进行计算出本轮循环的值呢?

原因:其实因为JavaScript引擎内部都会记住上一轮循环的值,初始化本轮的变量i时就在上一轮的基础上进行计算。

1.1.3 for循环的特别之处:

设置循环变量的那一部分是一个父作用域,而循环体内部是一个单独的子作用域,下面的代码中最终结果是打印三次‘abc’

    for(let i=0;i<3;i++){
        let i ="abc";
        console.log(i);
    }        

这表明了函数内部的变量i与循环变量i不在同一个作用域,而是各自有各自单独的作用域。

1.2-let不存在变量提升

var命令会发生“变量提升”的现象,即变量可以在声明之前使用,值为undefined。这种现象多少有些奇怪,按照一般的逻辑,变量应该在声明语句之后才可以使用,为了纠正这种现象,let命令改变了语法行为,他所声明的变量一定要在声明后使用,否则报错。

    //var 的情况
    console.log(foo);//undefined
    var foo = "18"

    //let 的情况
    console.log(bar);//报错 ReferenceError
    let bar = "12" 

1.3-暂时性死区

暂时性死区本质就是:只要进入作用域,所要使用的变量就已经存在了,但是不可以获取,必须等到声明变量的哪一行代码出现。

只要块级作用域中存在let命令,它所声明的变量就“绑定了”这个区域,不在收到外部影响。

例1:下面的代码中,存在全局变量tmp,但是块级作用域中let又声明了一个全局变量tmp,导致后者绑定这个块级作用域,所以在let声明变量之前,对tmp赋值就会报错

    var tmp = 123;
    if (true) {
        tmp = "abc"; //报错ReferenceError
        let tmp;
    }

例2:下面代码汇总也是因为暂时性死区,只要在变量还没有声明前使用,就会报错

var x = x;//不报错
let y = y;//报错ReferenceError

1.4-不允许重复声明

let不允许在相同的作用域内重复声明同一个变量

    function a() {
        let a = 5;
        var a = 10;//报错SyntaxError
    }
    function a() {
        let a = 5;
        let a = 10;//报错SyntaxError
    }

因此不能再函数内部重新声明参数

 function b(args) {
       let args; //报错 SyntaxError: Identifier 'args' has already been declared
 }

 function c(args) {
        {
          let args;//不报错
        }
 }

小结:

  • var声明变量:
    • 1.有变量提升
    • 2.没有块级作用域,是函数作用域
    • 3.能重复声明
    • 4.可以重新赋值
  •  let声明变量:
    • 1.没有变量提升
    • 2.有块级作用域
    • 3.不能重复声明
    • 4.可以重新赋值

02-块级作用域

2.1-为什么需要块级作用域?

ES5中只有全局作用域,没有块级作用域,这导致了很多场景不合理

第一种场景:内层变量可能会覆盖外层变量

例:以下代码的原义是指:if代码块外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升导致内层的tmp变量覆盖了外层的tmp变量。

    var tmp = new Date();
    function foo() {
        console.log(tmp); //undefined
        if (false) {
            var tmp = "hello world";
        }
    }
    foo();

第二种场景:用来计数的循环变量泄露为全局变量

例:下面的代码中,变量i只用来控制循环,但是循环结束后,它没有消失,而是泄露了全局变量

    var s = "hello";
    for (var i = 0; i < s.length; i++) {
        console.log(s[i]);
    }
    console.log(i); //5

2.2-ES6中的块级作用域:

let实际上为JavaScript新增了块级作用域。

例:下面的函数中有两个代码块,都声明了变量n,运行后输出5,这表示外层代码块不受内层代码块的影响,如果使用var定义变量n,最后输出的值就是10

  function f1() {
        let n = 5;
        if (true) {
            let n = 10;
        }
        console.log(n); //5
    }

2.3-ES6中允许块级作用域的任意嵌套

例:

    {{{{{let insane = "hello world"}}}}}//这个代码使用了一个5层的块级作用域
    {{{{
        {let insane = "hello world"}
        console.log(insane);//insane is not defined 外层作用域无法读取内层作用域的变量
    }}}}
    //内层作用域可以定义外层作用域的同名变量
    {{{{
        {let insane = "hello world"}
        let insane = "hello world"
    }}}}

块级作用域的出现,实际上让前面获得广泛应用的立即执行匿名函数(IIFE)不再必要了

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

2.4-块级作用域和函数声明

函数能不能在块级作用域中声明?这是一个让人困惑的问题。

ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域中声明。下面两种函数声明在ES5中都是非法的,但是浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持了在块级作用域中声明函数,因此下面两种情况实际上都能运行,并不会报错。

    //情况一:
    if (true) {
        function f() {}
    }
    //情况二:
    try {
        function f() {}
    } catch (e) {
        //...
    }

ES6引入了块级作用域,明确允许在块级作用域之中声明函数,ES6规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可以引用

    function f() {
        console.log("hello world");
    }
    (function () {
        if (false) {
            //重复声明一次函数f
            function f() {
                console.log('I am inside!');
            }
        }
        f();//报错 Uncaught TypeError: f is not a function
    }())    

03-const命令

const可以声明一个只读的常量

3.1-一旦声明,常量的值就不能改变

例:下面的代码表明改变常量的值就会报错

 const PI = 3.1415;
 console.log(PI);
 PI = 3; //报错TypeError: Assignment to constant variable

3.2-一旦声明,就必须立即初始化,不能保留到以后赋值

例:

const a;//只声明不赋值就会报错SyntaxError: Missing initializer in const declaratio

3.3-const的作用域和let命令相同:只在声明所在的块级作用域内有效

    if(true){
        const MAX = 10;
    }
    console.log(MAX);//MAX is not defined        

3.4const命令声明的常量也不会提升,同样存在暂时性死区,只能在声明后使用

   if(true){
        console.log(MAX);//ReferenceError: Cannot access 'MAX' before initialization
        const MAX = 5;
    }        

3.5使用const声明常量也和let一样,不可以重复声明

    var message = "hello";
    let age = 18;
    //下面两行都会报错
    const message = "good";
    const age = 40;

3.6-const本质:

const实际上保证的并不是变量的值不改动,而是变量指向的内存地址不得改动。对于简单类型的数据而言,值就保存在变量指向的内存地址中,因此等同于常量。但是对于引用类型的数据(对象、数组),变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的这是不能控制的,因此,将一个对象声明为常量时需要非常小心。

    const foo = {};
    // 为foo对象添加一个属性,可以成功
    foo.pname = "老八";
    console.log(foo.pname);//老八
    //如果将foo指向另一个对象,就报错
    foo = {};//TypeError: Assignment to constant variable.
    const a = [];
    a.push("666");//可执行
    a.length = 3;//可执行
    a = ['1']//报错    

补充:如果真的想将对象冻结,应该使用Object.freeze方法

例:以下代码,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式下还会报错

    const foo = Object.freeze({})
    //严格模式下,下面一行会报错,在普通模式下不起作用
    foo.pname = "老狗"
    console.log(foo.pname); //03-const命令.html:71 undefined

除了将对象本身冻结之外,对象的属性也应该冻结:下面是一个将对象彻底冻结的函数

    function constantize(obj) {
        Object.freeze(obj);
        Object.keys(obj).forEach((key, i) => {
            if (typeof obj[key] === 'object') {
                constantize(obj[key])
            }
        })
    }
    var p1 = {
        name:"张三",
        age:18,
        say:function(){
            console.log('你好');
        }
    }
    p1.name = "李四";
    constantize(p1);//冻结p1对象
    p1.name = "Rose";
    console.log(p1);//{name: "李四", age: 18, say: ƒ}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01-let与var的区别:
    • 1.1-let声明的变量只在let命令所在的代码块内有效
      • 1.1.1let与var在代码块中的区别:
      • 1.1.2示例:
      • 1.1.3 for循环的特别之处:
    • 1.2-let不存在变量提升
      • 1.3-暂时性死区
        • 1.4-不允许重复声明
          • 小结:
          • 02-块级作用域
            • 2.1-为什么需要块级作用域?
              • 2.2-ES6中的块级作用域:
                • 2.3-ES6中允许块级作用域的任意嵌套
                  • 2.4-块级作用域和函数声明
                  • 03-const命令
                    • 3.1-一旦声明,常量的值就不能改变
                      • 3.2-一旦声明,就必须立即初始化,不能保留到以后赋值
                        • 3.3-const的作用域和let命令相同:只在声明所在的块级作用域内有效
                          • 3.4const命令声明的常量也不会提升,同样存在暂时性死区,只能在声明后使用
                            • 3.5使用const声明常量也和let一样,不可以重复声明
                              • 3.6-const本质:
                                • 补充:如果真的想将对象冻结,应该使用Object.freeze方法
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档