zoukankan      html  css  js  c++  java
  • var let const的区别

    es6有许多特别棒的特性,你可能对该语言的整体非常熟悉,但是你知道它在内部是如何工作的吗?当我们知道它的内部原理以后,我们使用起来也会更加的安心一些。这里我们想逐步的引导你,让你对其有一个更深入,更浅显的认识。让我们就先从es6中的变量开始讲起吧。

    let和const

    在es6中新引入了两种方式来申明变量,我们仍然可以使用广为传诵的var变量(然而你不应该继续使用它了,继续阅读来了解其中原因),但是现在我们有了两种更牛的工具去使用:let和const。

    let

    let和var非常的相似,在使用方面,你可以使用完全相同的方式来声明变量,例如:

    但是实际上,他们之间有几处明显的不同。他们不仅仅是关键字变了,而且实际上它还让会简化我们的一些工作,防止一些奇怪的bug,其中这些不同点是:

    let是块状作用域(我将会在文章后面着重讲一下作用域相关的东西),而var是函数作用域

    let不能在定义之前访问该变量(var是可以的,它确实是js世界中许多bug和困扰的源头)。

    let不能被重新定义。

    在我们讲解这些不同点之前,首先我们看一个更酷的变量:const

    const

    const和let非常像(跟var相比来说,他们之间有许多相同点),但是他们有一个主要的不同点:let可以重新赋值,但是const不能。因此const定义的变量只能有一个值,并且这个值在声明的时候就会被赋值。

    但是const是完全不可变的吗?

    有一个常见的问题:虽然变量不能被重新赋值,但是也不能让他们真正的变为不可变的状态。如果const变量有一个数组或者对象作为其值的话,你可能会像下面的代码一样改变它的值。

    const a=1;
    //a=2;//报错提示a不可变
    const b={name:"miyue",do:"houru"};
    b.do="yanshe";
    console.log(b);//{ name: 'miyue', do: 'yanshe' }
    const c=[1,2]
    c.push(3);
    console.log(c);//[ 1, 2, 3 ]

    当然你不能用原始数据类型的值,比如string,number,boolean等,因为他们本质上是不可变的。

    全局变量和函数作用域变量

    在js中,究竟什么是作用域呢?本文不会给出一个关于作用域的完整解释。简单来说,变量的作用域决定了变量的可用位置。从不同的角度来看,可以说作用域是你可以在特定区域内使用的那些变量(或者是函数)的声明。作用域可以是全局的(因此在全局作用域中定义的变量可以在你代码中任何部分访问)或者是局部的。

    很显然,局部作用域只能在内部访问。在ES6以前,它仅仅允许一种方式来定义局部作用域 - function,咱们来看一下下面的例子:

    // global scope
    var globalVariable = 10;
    
    function functionWithVariable() {
      // local scope
      var localVariable = 5;
      console.log(globalVariable);  // 10
      console.log(localVariable);   // 5
    }
    
    functionWithVariable();
    
    //global scope again
    console.log(globalVariable);  // 10
    console.log(localVariable);   // undefined

    上面的例子中,变量globalVariable是全局变量,所以它可以在我们代码中的函数内或者是其他区域内被访问到,但是变量localVariable定义在函数内,所以它只在函数内可访问。

    因此,所有在函数内创建的内容都可以在函数内被访问到,包括函数内部里所有的嵌套函数(可能会被嵌套多层)。在这里可能要感谢闭包了,但是在文章里我们并不打算介绍它。

    提升

    简单来说,提升是一种吧所有的变量和函数声明“移动”到作用域的最前面的机制。让我们看一下下面的例子。

    function func() {
      console.log(localVariable);   // undefined
      var localVariable = 5;
    
      console.log(localVariable);   // 5
    }
    
    func();

    它为什么依然会正常工作呢?我们还没有定义这个变量,但是它依然通过console.log()打印出了undefined。为什么不会报出一个变量未定义的错误呢?让我们再仔细看一下。

    编译变量

    Javascript解析器要遍历这个代码两次。第一次被称为编译状态,这一次的话,代码中定义的变量就会提升。在他之后,我们的代码就变成类似于下面的这样子的(我已经做了一些简化,只展示出相关的部分)。

    function func() {
      var localVariable = undefined;
    
      console.log(localVariable); // undefined
      localVariable = 5;
    
      console.log(localVariable); // 5
    }
    
    func();

    我们看到的结果是,我们的变量localVariable已经被移动到func函数的作用域的最前面。严格来说,我们变量的声明已经被移动了位置,而不是声明的相关代码被移动了位置。我们使用这个变量并打印出来。它是undefined是因为我们还没有定义它的值,它默认使用的undefined。

    提升的例子 - 会出什么问题

    来让我们看一个令人讨厌的例子,我们的作用域范围对于我们来说,是弊大于利的。也不是说函数作用域是不好的。而是说我们必须要警惕一些由于提升而引起的一些陷进。我们来看看下面的代码:

    var callbacks = [];
    for (var i = 0; i < 4; i++) {
      callbacks.push(() => console.log(i));
    }
    
    callbacks[0]();
    callbacks[1]();
    callbacks[2]();
    callbacks[3]();

    你认为输出的值是多少呢?你猜可能是0 1 2 3,是吗?如果是的话,对于你来说,可能会有一些惊喜。实际上,他真实的结果是4 4 4 4。等等,它到底发生了什么?我们来“编译”一下代码,代码现在看起来就像这样:

    var callbacks;
    var i;
    
    callbacks = [];
    for (i = 0; i < 4; i++) {
      callbacks.push(() => console.log(i));
    }
    
    callbacks[0]();
    callbacks[1]();
    callbacks[2]();
    callbacks[3]();

    你看出问题所在了吗?变量i在整个作用域下都是可以被访问到的,它不会被重新定义。它的值只会在每次的迭代中不断地被改变。然后呢,当我们随后想通过函数调用打印它的值得时候,他实际上只有一个值 - 就是在最后一次循环赋给的那个值。

    我们只能这样了吗?不是的

    Let和Const的拯救

    除了定义变量的新方式以外,还引入了一种新的作用域:块级作用域。块就是由花括号括起来的所有的内容。所以它可以是if,while或者是for声明中的花括号,也可以是单独的一个花括号甚至是一个函数(对,函数作用域是块状作用域)。let和const是块作用域。意味着无论你在块中无论定义了什么变量,什么时候定义的,它都不会跑到块作用域外面去。我们来看一下下面的例子:

    function func() {
      // function scope
      let localVariable = 5;
      var oldLocalVariable = 5;
    
      if (true) {
        // block scope
        let nestedLocalVariable = 6;
        var oldNestedLocalVariable = 6;
    
        console.log(nestedLocalVariable); // 6
        console.log(oldNestedLocalVariable); // 6
      }
    
      // those are stil valid
      console.log(localVariable); // 5
      console.log(oldLocalVariable); // 5
      // and this one as well
      console.log(oldNestedLocalVariable); // 6
      // but this on isn't
      console.log(nestedLocalVariable); // ReferenceError: nestedLocalVariable is not defined
    }

    你能看出来差别吗?你能看出来怎么使用let来解决早些时候提出问题的吗?我们的for循环包含一组花括号,所以它是块作用域。所以如果在定义循环变量的时候,使用的是let或者是const来代替var的话,代码会转为下面的形式。注意:我实际上已经简化了很多,不过我确定你能理解我的意思。

    let callbacks = [];
    for (; i < 4; i++) {
      let i = 0 //, 1, 2, 3
      callbacks.push(() => console.log(i));
    }
    
    callbacks[0]();
    callbacks[1]();
    callbacks[2]();
    callbacks[3]();

    现在的每一次循环都有它自己的变量定义,所以变量不会被重写,我们确信这行代码可以完成让他做的任何事情。

    这是这一部分结束的例子,但是我们再看一下下面的例子,我相信你明白打印出来的值的原因,以及对应的表现是什么。

    function func() {
      var functionScopedVariable = 10;
      let blockScopedVariable = 10;
    
      console.log(functionScopedVariable);  // 10
      console.log(blockScopedVariable);  // 10
      if (true) {
        var functionScopedVariable = 5;
        let blockScopedVariable = 5;
    
        console.log(functionScopedVariable);  // 5
        console.log(blockScopedVariable);  // 5
      }
    
      console.log(functionScopedVariable);  // 5
      console.log(blockScopedVariable);  // 10
    }
    
    func();

    临时死区

    实际上,这种描述引出了我们的另一个定义。他很让人可怕,因为他叫:临时死区(TDZ)。这个属于明确了一个我们无法访问我们的变量的代码的区域。我们来看一下下面的代码和相关联的注释,来简单的解释一下TDZ是什么。

    function func() {
      // Start of TDZ for deadVariable
      // we can still do something here, just our deadVariable is not available yet
      const exampleVariable = 5;
      console.log(exampleVariable); // 5
      // End of TDZ for deadVariable
      let deadVariable = 10;
    
      console.log(deadVariable);  // 10
    }
    
    func();

    有一件事情值得去提醒。就是对于名字的建议,这是一个临时死区,意思这个区域是由时间定义的,而不是位置。因此当运行代码的时候,你的声明在被JS解析器解析之前是不能被访问的。因此你把使用的变量的位置放在哪里并不重要,只要是在声明执行后访问该变量就可以。所以看下面的代码:

    function func() {
      return deadOrAlive;
    }
    
    let deadOrAlive = 'alive!'
    console.log(func());  // alive!

    这是运行代码的步骤:

    函数被声明

    变量deadOrAlive被声明,并且初始化了一个值“alive”

    现在我们调用我们的函数。

    由于变量deadOrAlive已经被声明,是可访问的,因此会打印出正确的结果 “alive”。

    但是下面的例子却会报错,思考一下原因。

    function func() {
      return deadOrAlive;
    }
    
    console.log(func());  // ReferenceError: deadOrAlive is not defined
    let deadOrAlive = 'dead!'

    所以TDZ是一个避免因先使用后声明而导致的一些诡异的bug而出现的一个很好机制(具体看“提升”相关内容)。我们不需要去额外做什么事情,就是记住永远不要在变量声明之前使用这个变量。即使我们这样做了,我们也会得到一个很好的报错信息。只有一个条件-你必须使用let或者是const来替换掉var。

    双定义

    var和let,const的另一个区别是 - 后者仅仅可以被定义一次。而对于var的话,如果被同时定义多次,程序也依然会很好的运行。

    var doubledVariable = 5;
    var doubledVariable = 6;
    
    console.log(doubledVariable); // 6

    是现在,当你用let和const来做同样的事情,就会得到一个语法错误:

    let doubledVariable = 5;
    let doubledVariable = 6;  // SyntaxError: Identifier 'doubledVariable' has already been declared

    但是,在嵌套的块级作用域中,使用相同名字的变量依然会很好的工作的,这个我想大家已经清楚了,就不用过多解释了吧。

    let doubledVariable = 5;
    
    if (true) {
      let doubledVariable = 6;
      console.log(doubledVariable); // 6
    }
    
    console.log(doubledVariable); // 5

    不能重复定义这个功能实际上是很有用的,可以组织很多bug的发生。比如说你曾经在一个函数内,在不同地方用var定义了多个相同名称的变量,此时之前定义的变量可能会被覆盖,这样对于代码来说无疑是一个隐患,也就是因为这样,这个特性实际上是一个简单的,开箱即用的解决方案。

  • 相关阅读:
    应用SecureCRT(发送接收文件)
    应用mysql(Linux中安装)
    应用tomcat(Linux中安装)
    应用Oracle(Linux中的安装)
    关于Linux(时间网路同步)
    VmWare问题解决(网络变更后虚拟主机无法上网)
    [Android学习笔记]Bitmap,BitmapDrawable,BitmapFactory学习笔记
    [Android学习笔记]获取view的尺寸和坐标
    [Android学习笔记]继承自ViewGroup的控件的过程学习
    [Android学习笔记]Canvas的使用
  • 原文地址:https://www.cnblogs.com/luzhanshi/p/10996166.html
Copyright © 2011-2022 走看看