首页 > 技术文章 > js--var、let和const的学习笔记

zaishiyu 2021-07-04 12:42 原文

前言

  变量的声明是每一种编程语言中最基础的部分,在大多数的编程语言中,变量总是在声明的地方创建,然后 JavScript 并不是这样,变量的实际创建位置取决于你如何声明它,加上 es6 引入了 let 和 const 便于在开发中更好的控制变量的作用域。同样这部分知识也是面试中经常遇到的问题。本篇博客就来记录一下关于 JavScript 中变量声明绑定的学习笔记。

正文

  1.var 的声明和变量提升

  (1)var声明变量

  var 声明的变量,如果不在函数内部,则视为全局作用域顶部声明的变量,若在函数内部,视为当前函数内部的全局变量。如代码段1:

    var value = "value";
    function getValue() {
      var value = "backValue";
      return value;
    }
    console.log(getValue()); //backValue
    console.log(value); //value

  上面的代码中,用 var 定义一个全局变量 value,在函数内部也同样用 var 声明一个函数内部的全局变量 value ,在 getValue() 的时候,返回函数内部的 value 变量值 backValue,直接访问 value 变量时访问的是最外层全局的 value 值为 value。在来看下面这段代码。如代码段2:

    var value = "value";
    function getValue() {
      value = "backValue";
      return value;
    }
    console.log(getValue()); //backValue
    console.log(value); //backValue

  这段代码和上面代码段1项比较就是在getValue()方法内部没用用 var 声明 value ,此时函数内部访问的是最外层全局的value ,一次执行 getValue() 函数时候,将最外层全局的value值改变成了 backValue,后面正常输出。接下来再看下面的代码段3:

    var value = "value";
    function getValue() {
      return value;
    }
    console.log(getValue()); //value
    console.log(value); //value

  这段代码中,在getValue()方法中访问 value 变量,并返回其对应的值,但是函数内部作用域并没有声明该变量,一次js引擎回帮助我们项上层作用域中选择,即找到了最外层的全局作用域,之后正常返回。理解了上面的代码片段,我们在来看下下面的代码段4:

    function getValue(flag) {
        if (flag) {
          var value = "backValue";
          return value;
        } else {
          console.log(value) 
          return null;
        }
      }
      console.log(getValue(1)); //backValue
      console.log(getValue()); //undefined null

  这段代码得出结果不难理解,但是你知道为什么会是这样的结果吗,你知道js引擎在帮我们做了哪些事情吗?针对上面的代码段4 ,js引擎会对其代码的位置进行调整,如下代码段5:

    function getValue(flag) {
      var value;
      if (flag) {
        value = "backValue";
        return value;
      } else {
        console.log(value);
        return null;
      }
    }
    console.log(getValue(1)); //backValue
    console.log(getValue()); //undefined null

  不难看出,var 声明的变量在函数内部也会提升,先声明,初始值为undefined,在else分支内部也可以拿到 value 这个变量。

  (2)变量提升

  使用 var 关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部,如果不在任意函数内,则视为全局作用域的顶部,这就是所谓的变量提升。

  使用var 声明的变量,不在任意函数内部的时候,相当于给全局window 对象添加一个属性,这导致使用 var 可能无意间覆盖 window对象的已有属性。如果你在全局作用域上使用let 或者const 创建新的变量,虽然 全局作用域上会创建新的绑定,但是不会有任何属性被添加到全局window对象上,这就以为着不能使用let 或者const 来覆盖一个全局变量,只能将其屏蔽。如下面代码段6:

    var RegExp = "hello";
    console.log(RegExp); //hello
    console.log(window.RegExp === RegExp); //true

    let RegExp = "hello";
    console.log(RegExp); //hello
    console.log(window.RegExp === RegExp); //false

    const RegExp = "hello";
    console.log(RegExp); //hello
    console.log(window.RegExp === RegExp); //false

  2.let 块级作用域

  (1)创建方式及使用

  es6 引入了let 声明的块级作用域,用let 所声明的变量在指定的作用域外无法访问,块级作用域的创建方式如下:

    a、在函数内部创建

    b、在一个代码块中创建,比如 if...else...中的花括号内。

  仔细阅读如下代码段7:

      function getValue(flag) {
          if (flag) {
            let value= "backValue";
            return value;
          } else {
            console.log(value);
            return null;
          }
        }
        console.log(getValue(1)); //backValue
        console.log(getValue()); //报错value is not defined

  上面的代码,在if 分支中用let 声明一个value变量,属于块级变量,因此在else 分支中是无法访问的,因此执行else 分支的代码时,访问value 变量报错,value is not defined 。同样在 if...else...结束了判断后也是无法访问这个变量的。

  (2)禁止重复声明

  let 不像 var 一样,let 不能在同意作用域中重复声明一个已有标识。如下代码段8:

        let a = 1;
        let a = 2; //Identifier 'a' has already been declared语法报错

        var b = 1;
        var b = 2;
        console.log(b);//2

        var c = 1;
        let c = 2;//Identifier 'c' has already been declared语法报错

  (3)暂时性死区

  只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量,暂时性死区知识块级作用域中的一个特殊性质。如下代码段9:

  var tmp = 123; // 声明
  if (true) {
    tmp = 'abc'; // 报错 因为本区域有tmp声明变量
    let tmp; // 绑定if这个块级的作用域 不能出现tmp变量
  }

  (4)在循环体中的使用注意

  先来看下下面代码段10:

    for (var a = 1; a < 5; a++) {
      console.log(a);
    }
    console.log(a);//1 2 3 4 ==》5   //跳出循环时a为5

    for (let i = 1; i < 5; i++) {
      console.log(i)
    }
    console.log(i)//1 2 3 4 报错:i is not defined

  for循环中使用 var 声明一个a 变量,相当于在外层作用域中先声明一个a变量,值为undefined,然后在每次循环中给 a 赋值,并访问 a 的值,但是如果使用 let 声明一个变量 i ,i只属于for 循环的块级作用域,循环结束后任意位置都不能访问该变量。再次访问就会报错 。

  代码段11如下:

      var funcs = [];
      for (var i = 0; i < 10; i++) {
        funcs.push(function () {
          console.log(i);
        });
      }
      funcs.forEach(function (func) {
        func(); // 输出数值 "10" 十次
      });

  这段代码输出十次10,是因为循环内创建的函数都拥有对同一个变量的引用,循环结束的时候,全局的i变量值为10,因此每次访问 func()这个方法每次都输出10。在来看下用 let 的时候,代码段12如下:

     var funcs = [];
      for (let i = 0; i < 10; i++) {
        funcs.push(function () {
          console.log(i);
        });
      }
      funcs.forEach(function (func) {
        func(); // 从 0 到 9 依次输出
      });  

  使用了let之后,在循环中let声明的变量,每次都会创建一个新的i变量,因此在循环中创建的函数获得了各自的 i 的副本,而每个 i 副本都在每次循环迭代声明变量的时候被确定了。同样这种方式适用于 for -of ,for -in中,如下代码段13:

    var funcs = [],
      object = {
        a: true,
        b: true,
        c: true,
      };
    for (let key in object) {
      funcs.push(function () {
        console.log(key);
      });
    }
    funcs.forEach(function (func) {
      func(); // 依次输出 "a"、 "b"、 "c"
    });

  3.const 声明常量

  const 和let 一样,声明的变量都属于块级作用域,因此const具有 let 相同的特性,除此之外,还有一些特性如下:

  (1)初始化必须赋值

  const在声明变量初始化的时候必须赋值,例如:

        const a ;
        a=2; //Missing initializer in const declaration,语法报错

  (2)禁止重复赋值,重复赋值会抛错。

        const a = 1;
        a = 2;//Assignment to constant variable.

  (3)const声明对象

        const person = { name: "小明" };
        person.name = "小强";
        console.log(person);//{name:"小强"}

  如上面的代码,const声明对象的时候,对象的值是可以被修改的,const会阻止变量绑定和变量自身值的修改,但是无法阻止变量成员的修改,在 js 中,变量名存在在栈内存中,而变量对应的值存储在堆内存中,const只能阻止由栈内存指向堆内存的地址不发生改变,但是无法阻止堆中具体的数据发生改变。

  (4)const 在循环体中的使用注意

      for (const i = 0; i < 5; i++) {
          console.log(i);
        }
      // 0   Assignment to constant variable.

  上面的代码中 i 被定义为一个常量,第一次初始化时候赋值为 0 ,然后打印出0,之后执行 i++ ,改变 i 的值,语法报错,这个错误容易理解。

  但是,const 在 for-of 和 for-in 中使用和 let 效果一样

     var funcs = [],
        object = {
          a: 1,
          b: 2,
          c: 3,
        };
      // 不会导致错误
      for (const key in object) {
        console.log(key); //a b c
      }

  4.var、let、const 三者的区别

  (1)块级作用域

  (2)变量提升

  (3)暂时性死区

  (4)重复声明

  (5)是否属于window属性,覆盖问题

写在最后

  以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

推荐阅读