JS基础系列--理解var/const/let

470 阅读6分钟

    对于写js的人来说,这三个关键字再熟悉不过了吧。ES6之前,只要是定义变量,哪儿都丢一个var过去,都不需要多想的。到了ES6标准,突然新增了两个新玩意儿"const"和"let"。以前是没得选,现在在定义变量的时候总是得想一下应该用哪一个了。那么既然已经有了var可以定义变量了,为什么还需要提供新的东西来提高使用复杂度呢?我们必须相信,一个新事物能够流行或替代旧的事物,必定是以前的东西存在缺点和不足,新东西能够解决这些问题。那么接下来我们就来聊一聊这几个关键字之间的关系。

按照惯例,我们先罗列几个常识:

  1. var是ES5中的,let和const是ES6标准中新增的变量定义关键字
  2. let和const定义的变量的作用域在块级作用域中;var变量在函数外则是全局变量,在函数内则是局部变量
  3. 使用var来创建变量,声明后未赋值的变量输出会提示 “undefined”。在方法函数外声明的变量未使用var ,会报错“x is not defined”,在函数内未使用var声明的变量自动变为全局变量
  4. var会导致变量提升(预编译),const和let定义的变量则不会

问:var到底有什么问题,以至于ES6中需要提供const和let来定义变量?

1.解决ES5使用var初始化变量时会出现的变量提升问题

案例1:变量定义前置

console.log('var: ',a1); // undefined
console.log('let: ',a2); // 报错
var a1 = '这是var变量';
let a2 = '这是let变量';

打印结果:


原因:程序在检查完语法问题之后,会进行预编译,预编译就是在执行代码会把所有的变量声明和函数声明预先处理,换句话说就是将函数以及变量先提出来,var定义的变量会提出来,并赋值undefined,函数会提出但不传值,不执行。但是let定义的变量不动。所以上面的代码在预编译的时候就等价于:

var a1;
console.log(a1); // undefined
console.log(a2); // 报错
a1 = '这是var变量';
let a2 = '这是let变量';

案例2:内层变量覆盖外层变量

var a = 'outerA';
function fn(){
    console.log(a); // undefined;
    var a = 'innerA'; 
    console.log(a);
}


我记得对于外面定义的变量,函数内部是可以访问的,为什么到了上面的例子,第一个打印的a确实undefined呢?

原因是因为在函数内部又定义了一个a变量,内层变量在预编译的时候,提升到了函数顶部,相当于:

var a = 'outerA';
function fn(){
    var a ;
    console.log(a); // undefined;
    console.log(window.a); // outerA
    a = 'innerA'; // 单纯的赋值
    console.log(a);
}

案例3:函数提升优先于变量提升

fn1();
function fn1(){
    console.log(a);
}
var a = 'outerA';


函数执行了,但是获取到的a是undefined,上面代码就相当于:

function fn1(){
    console.log(a);
}
var a;
fn1();
a = 'outerA';

那么怎么才能获取到a呢?很简单,函数在变量赋值之后调用就行了呀~

2. var在for和if这种块级作用域中,变量会泄露

我们来看一个非常经典的循环例子:

通过循环,将计算a数组中每个数字平方的函数一一对应存储在aFn这个数组中,记住,存的是函数,不是结果值。

var a = [0,1,2,3,4,5,6,7,8,9,10]
var aFn = [];
for (var i = 0; i < 10; i++) {
    aFn[i] = function() {
        return a[i]**2;
    } 
}
//执行aFn中函数
for (var j = 0; j < 10; j++) {
    console.log(aFn[j]());
}

结果:


原因:上面案例1我们已经提到过,程序在执行之前会进行预编译,函数声明提出来,不传值不执行,函数自身的作用域在这一个阶段已经产生了。由于var定义的变量,没有块作用域的概念,所以循环中的i处于全局作用域。当我们调用aFn[j](),正式执行函数的时候,其实i的值都是10,以至于结果都是aFn数组中的每一个函数执行的结果都是100。那我们来看看let定义的结果:

var a = [0,1,2,3,4,5,6,7,8,9,10]
var aFn = [];
for (let i = 0; i < 10; i++) {
    aFn[i] = function() {
        return a[i]**2;
    }
    
}
//执行aFn中函数
for (var j = 0; j < 10; j++) {
    console.log(aFn[j]());
}

结果:


原因:函数在预编译的时候已经确定自己的作用域了,因为let是块级作用域,每个循环相当于定义了属于自己的块级作用域,不管什么时候执行,函数取得的i都是各自块内部的值。

上面的示例只是为了讲解var存在的问题,以及let和块级作用域在程序中解决了一个什么样的问题(变量泄露),当然如果单从结果而言,打出aFn这样一个结果列表,完全没必要将不好控制的函数作为数组项存储,立即执行函数存结果,遍历结果就行了,这里切不要纠结这种问题。

var a = [0,1,2,3,4,5,6,7,8,9,10]
var aFn = [];
for (var i = 0; i < 10; i++) {
    aFn[i] = function() {
        return a[i]**2;
    }()
    
}
//执行aFn中函数
for (var j = 0; j < 10; j++) {
    console.log(aFn[j]);
}

接下来简单说一下const这个关键字

const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。const 和 let 的作用域是一致的,不同的是 const 变量一旦被赋值,就不能再改变了;

这里说的不可变是指数据的内存地址,对于基本数据类型的数据来说,数据本身是存在栈内存的,所以const定义的变量就相当于一个常量,不可修改。但是对于引用数据类型(函数、数组、对象)来说,只要是本身内存地址不变,里面数据的修改是可以的。

常量:

const name = 'moose';
name = 'Lavar';

结果:


引用数据类型:

const moose = {
	id: '0001',
	age: '18'
}
moose.id ='0002';
console.log(moose.id);

结果:


到此为止,我们大体的梳理了var、let以及const三个变量定义关键字相关的内容

特别感谢提供相关内容参考:

    www.cnblogs.com/ykli/p/9681…

    blog.csdn.net/recoluan/ar…

附:感谢您的阅读,希望对您有所帮助。如果以上内容中存在疑问和错误,欢迎留言或者私信。