ES6学习(一): var、let和const

到ES6为止,我们一共有4种方式可以定义变量,今天我们就来聊一聊这4种方式都会有怎样的行为。

一、不使用任何关键字

  a = 1

在大部分的学习资料上对这行代码的解释是:声明一个全局变量。这么说没问题,但是不严谨。有如下代码:

  function fn() {
    var a;
    function fn2() {
      a = 1  //只是单纯的赋值,并没有声明全局变量
    }
  }

从上述代码可以看出,a=1不再是声明全局变量,而仅仅是单纯的赋值。因此,更严谨一点的说法应该是只有在不存在变量a的情况下才会隐式地声明一个全局变量

二、var

  var a = 1  //在当前作用域内声明变量a

但是用var定义变量会存在一个非常大的问题。先看下面的代码

  function fn() {
    if (true) {
      console.log(a)  //理论上此处应打印 a is not defined
    } else {
      var a;
    }
  }
  fn()

按照我们正常的思路,应该打印出'a is not defined',但是事实上代码执行完打印出的结果是undefined,这不符合我们一般的理解。实际上,用var定义的变量会存在一种叫做变量/函数声明提升的行为。上述代码和下面代码等价:

  function fn() {
    var a;
    if (true) {
      console.log(a)  //undefined
    } else {}
  }
  fn()

下面再看一个例子:

  {
    var a = 1;
    window.fn = function() {
      console.log(a)
    }
  }
  //需求: 有且仅有fn为全局函数,a不可被外部(window)访问

通过上面的解释我们知道,a变量声明会提升,于是全局存在了变量a和函数fn,显然不符合我们的要求。

我们知道要想一个变量不被外部访问,可以用函数的方式来包裹变量,于是有了以下的代码:

  function fn2() {
    var a = 1;
    window.fn = function() {
      console.log(a)
    }
  }
  fn2()

这种写法下变量a的确不会被外部(window)访问,但是问题由来了,全局下多了fn2函数,这也不符合我们的需求。那既然这样,我们会想到,那我把fn2藏起来不就行了,于是我写出了这样的代码:

  //立即执行匿名函数
  (function () {
    var a = 1;
    window.fn = function() {
      console.log(a)
    }
  }()) 

我们可以看到,我仅仅是想要让变量a不可被外部(window)访问,就得写一个立即执行匿名函数,太麻烦了。于是let应运而生!

三、let

同样是上述的代码:

  {
    let a = 1;
    window.fn = function() {
      console.log(a)  //1
    }
  }
  console.log(a)  // a is not defined

这种情况下,a变量就像'被困在了大括号里面',你只能在大括号之间活动,出了大括号,就再也访问不到a了。

let还有另外两个特点,其中一个叫做Temp Dead Zone(临时死区),看如下代码:

  {
    let a = 1
    {
      console.log(a)  // a is not defined   
      let a = 2
      {
        let a = 3
      }
    }
  }

也就是说,在let定义的变量还没有初始化之前是无法使用该变量的。

另外一个特点就是不能重复定义

  {
    let a = 1;
    let a = 2;  //报错
  }

四、const

const只有一次赋值机会,而且必须在声明的时候立马赋值

  {
    const a;  //报错 必须赋值
    const b = 1;
    b = 2 //  报错
  }

刚刚我们用const定义了基本数据类型的值,接下来我们看下定义引用数据类型的值。

  const a = {
    b: 4
  }
  //第一种
  a.b = 5;
  console.log(a)  // {b:5}
  //第二种
  a = {
    b: 6
  }
  console.log(a)  //Uncaught TypeError: Assignment to constant variable.

也就是说,用const定义的对象内部的值是可以改变的,但是不能改变对象的引用地址。

到此为止,ES6的4种定义变量的方式就说完了,但是还有一个问题,为什么会发生Temp Dead Zone(临时死区),那我们就说说创建变量的过程。变量创建大体分为3步(忽略分配内存空间什么的):

  1. 变量声明
  2. 变量初始化
  3. 变量赋值

那我们就用var/function/let/const来举例:

  • var
  function fn(){
    var x = 1
  }
  fn()

fn执行大致进行了以下步骤:

  1. 进入fn后,声明x
  2. x初始化为undefined
  3. 执行代码, x赋值为1

即代码执行之前就已经完成了声明和初始化的过程

  • function
  fn()
  function fn() {
    console.log(1)
  }

执行步骤大致如下:

  1. 找到所有用function声明的变量
  2. 初始化并赋值
  3. 执行代码

即代码执行之前就已经完成了声明、初始化和赋值的过程

  • let
  {
    let x = 1  //x初始化为1
    //let x //x初始化为undefined
    x = 2
  }

执行步骤大致如下:

  1. 找到所有用let声明的变量
  2. 执行代码
  3. 初始化x=1
  4. x赋值为2
  • const

const由于必须在声明的时候初始化,所以只存在声明和初始化的过程,不存在赋值过程

由上述解释我们知道: 临时死区产生的原因是用let定义的变量在没有被初始化之前是无法被使用的

下面有个有意思的现象:

1.jpg

就是说,如果let声明变量的初始化过程失败了,那么:

  • 变量将永远处于声明状态
  • 无法再次对变量进行初始化
  • 由于变量无法被初始化,所以永远处在临时死区

面试题

  for (var i=0; i<6;i++) {}
  console.log(i)  //6

这个没啥好说的,由于变量提升,i上升为全局变量,执行完循环之后被赋值为6

  for (var i=0; i<6;i++) {
    function fn() {
      console.log(i)
    }
    /** 第一种情况,如果直接调用fn
     * fn()  // 0, 1, 2, 3, 4, 5
     */
    
    /**
     * 第二种情况,绑定在button的点击事件上
     * btn.onclick = fn  // 6
     * 这是因为在点击时循环已经结束,i的值是6
     */
  }
  var tags = document.querySelectorAll('li') //6个
  for (var i=0; i

那么怎么才能输出0 1 2 3 4 5呢?

  • 第一种: 利用let保存每一次循环的变量
  var tags = document.querySelectorAll('li') //6个
  for (var i=0; i
  • 第二种: 利用立即执行匿名函数,把每次循环产生的变量传入
  var tags = document.querySelectorAll('li') //6个
  for (var i=0; i
  • 第三种: 将循环的变量用let定义
  var tags = document.querySelectorAll('li') //6个
  for (let i=0; i

你可能感兴趣的:(ES6学习(一): var、let和const)