JS声明变量的六种方式

7,702 阅读6分钟

开始

先说一下JS中有哪六种变量的声明方法吧,然后我们带着三个问号去看文章:what?how? where?

六种声明变量的方法:

  1. var
  2. let
  3. const
  4. function
  5. import
  6. class

没有对比就没有伤害(var、let、const)

面试中,有不少的面试官喜欢问let、var、const的区别,虽然这个问题很简单,但是从这道题中,可以看出你的编码习惯和风格; 有的同学表示很疑惑了,从这道题怎么会看出自己的编码习惯和风格呢,那么好,疑问三连送给你:

  1. 你见过全是var变量的JavaScript吗?
  2. 你经历过滥用let的后果吗?
  3. 你知道const的命名规范吗?

下面我们来看一下它们之间的区别 (如果大佬需要补充,请到评论区):

varletconst
存在变量提升不存在变量提升不存在变量提升
代码块外部可调用存在块级作用域存在块级作用域
赋值后值不可改变
通过上面的表格我们可以看到它们之间的大体区别,虽然差别不是很多,但是在实际开发中,却有着大大小小的坑

来看第一段代码(耐心看完下面三段代码,并不难哦):

  function fun(str){
    let str = 'hello'+'world!';
    console.log(str);
  }
  fun('123');

没错,这是一段有问题的代码,运行后是一个语法错误:Uncaught SyntaxError:Identifier 'code' has already been declared,进一步解析一下它运行后的样子:

  function fun(str){
    var str;   // 这个变量代表的是函数形参
    let str = 'hello'+'world!';
    
    // 这时候fun函数的作用域顶部,有两个变量名为str,所以报错!
    console.log(str);
  }
  fun('123')

这也说明了在同一作用域中,任何变量是不可以重复声明的,我就是我,颜色不一样的烟火!

总结:这段代码需要注意的坑:作用域、变量提升、变量名禁止重复;


如果对上面的代码理解了,咱们来看第二段代码:

  var str = 'hello';

  function fun(){
    console.log(str);
    let str = 'world';
    console.log(str);
  }
  fun();

只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响,这也就是传说中的 暂时性死区,ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,所以上面是一段错误代码:Uncaught ReferenceError: Cannot access 'str' before initialization

总结:这段代码需要注意的坑:暂时性死区、块级作用域


消化完上面代码,继续来看最后一段代码:

  const obj = {};
  let str = '坚持一周写两篇博客';
  let addObj = obj.names = str;
  
  console.log(addObj);

看到这段代码后,有的同学就会说了:你不是说const的值不能改变吗?你个骗子! const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。

const需要注意的点:

  1. 只声明不赋值,就会报错
  2. 只在声明所在的块级作用域内有效
  3. const命令声明的常量也是不提升,同样存在暂时性死区
  4. 不可重复声明
  5. 想将对象冻结,应该使用Object.freeze方法

function、import、class

function

ES6规定:

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。

注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下:

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

import

import用于加载文件,在大括号接收的是一个或多个变量名,这些变量名需要与你想要导入的变量名相同。

举个栗子:你想要导入action.js文件中的某一个变量,这个变量里保存了一段代码块,所以你要写成:import { Button } from 'action',这个时候,你就从action.js中获取到了一个叫 Button 的变量,既然获取到了,你就可以对Button里的代码猥琐欲为了

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名,比如:

import { NewButton as Button } from 'action.js';

上面的代码表示从action.js文件中,获取一个变量名称叫做Button的代码段,然后你又声明了一个变量 NewButton ,将 Button 保存在 NewButton


class

关于class,后期我会单独写一篇文章,详细的介绍一下,这里就简单说一下: 首先通过看ES5中的构造函数,然后再用ES6的class去实现:

// ES5写法 :
  function fun ( x, y ){
    this.x = x;
    this.y = y;
  };

  fun.prototype.GetHair = function(){
    return `${this.x}掉了两根头发,${this.y}说俺也一样!`;
  };

  let setHair = new fun('小明','老王');
  console.log(setHair.GetHair()); // 小明掉了两根头发,老王说俺也一样!

再来看一下ES6的class写法:

  class Interest {

    constructor( x, y, e, z ){
      this.x = x;
      this.y = y;
      this.e = e;
      this.z = z;
    }

    MyInterest(){
      let arr = [];
      console.log(`我会${[...arr,this.x,this.y,this.e,this.z]}!`);
    }
  }

  let GetInterest = new Interest('唱','跳','rap','篮球');
  console.log(GetInterest.MyInterest());  //我会唱,跳,rap,篮球!

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,constructor方法就是构造方法,而this关键字则代表实例对象。也就是ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

结束

关于JS声明变量的六种方式就介绍完了,如果对你有帮助的话,就点个赞鼓励一下吧; 文章内容如有不同意见,欢迎评论区指出(初写文章,请多指教)~ 本人微信: