原来JavaScript的let和var不能乱用

225 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第一天,点击查看活动详情

原来JavaScript的let和var不能乱用

可能很多小白在刚学习JavaScript的时候在定义变量的时候会使用var或者let,也有一些开发者直接就只用var或者let,但是贸然使用var或者let其实在隐性的给你自己留bug,到时可能你找bug都可能找不到!!!

什么是var关键字

常常我们要定义一个变量会使用var关键字,后跟变量名:

//普通的定义一个变量
var test;
//初始化一个变量
var test2="我是用来测试的";

有时有些开发者会进行如下操作

var t="hi";
t=100

在这个例子中,变量t首先被定义为一个保存字符串值hi的变量,然后又被重写为保存了数值100。虽然不推荐改变变量保存值的类型,但这在ECMAScript中是完全有效的。

var 声明作用域

无疑var容易出错的地方也就是它的作用域,不出意外很多看前端面试题的开发者都可能会看到一些var变量相关的面试,而且很多时候他们的答案总是让人出乎意料,例如下面这道题

function test() 
{ 
    var message = "hi"; 
}
test();
console.log(message);
function test2() 
{ 
    message2 = "hi"; 
}
test2();
console.log(message2);

上面的打印结果会是什么?

image.png

image.png

首先我们要先明确一点,var的作用域是怎么定义的?

使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

接下来我们在回顾到上面那道简单的面试题

这里,message变量是在函数内部使用var定义的。函数叫test(),调用它会创建这个变量并给它赋值。调用之后变量随即被销毁,因此示例中的最后一行会导致错误。去掉之前的var操作符之后,message就变成了全局变量。只要调用一次函数test(),就会定义这个变量,并且可以在函数外部访问到。

注意:虽然可以通过省略var操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略var是不是有意而为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出ReferenceError。

要不再来一道简单的面试题

function foo()
{ 
    console.log(age); 
    var age = 26; 
}
foo(); 

这段代码会报错?

image.png

之所以不会报错,是因为ECMAScript运行时把它看成等价于如下代码:

function foo()
{ 
    var age;
    console.log(age); 
    age = 26; 
}
foo(); 

这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也没有问题:

什么是let

let跟var的作用差不多,但有着非常重要的区别。最明显的区别是,let声明的范围是块作用域,而var声明的范围是函数作用域。

if (true) {
    var name = 'Matt';
    console.log(name); // Matt
}
console.log(name);    // Matt
if (true) {
    let age = 26;
    console.log(age);    // 26
}
console.log(age);      // ReferenceError: age没有定义

看到这段代码就很快会有人联系到一个东西,也就是for循环,因为很多时候我们会定义一个局部变量i作为索引,但是var和let定义这个变量i确实是有很大区别的,很多时候我们都是想着变量i循环完后就不在使用它也就是for结束变量i就应该被释放掉,但是如果我们用var去定义变量i的话则会出现以下问题

image.png

也就是变量i并没有被释放掉,这也可能会给后续带来一些隐藏的bug,解决这个问题我们就可以使用let来定义变量i

image.png

这样变量i就会在for循环结束后被释放掉

let没有提升

let与var的另一个重要的区别,就是let声明的变量不会在作用域中被提升。

image.png

在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

let和var,const最佳实践

不使用var

有了let和const,大多数开发者会发现自己不再需要var了。限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

const优先,let次之

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。