JavaScript中var,let,const

JavaScript中var,let,const


ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据,每个变量只不过是一个用于保存任意值的命名占位符,有三个关键字可以声明变量:var,let,const,其中 var在ECMAScript的所有版本中都可以使用,而const和let只能在ECMAScript6及更晚版本中使用

var关键字

var关键字

  • 用var定义变量(var是关键字),后面跟变量名(即标识(zhi四声)符)
    var temp;
    这样定义了一个名为temp的变量,可以用它保存任何类型的值(不初始化的情况下,变量会保存一个特殊值undefined)
  • ECMAScript实现变量初始化,可以在定义变量同时设置变量的值:
    var temp = "hello"
    temp被定义为一个保存字符串hello的变量,但是这样初始化变量不会将它标识为字符串类型,只是一个简单的赋值。不仅可以改变保存的值,还可以改变值的类型
var temp = "hello";
temp = 1;//合法但是不推荐
//在这里temp首先被定义为一个保存字符串hello的变量,
//然后被重写保存了数值100
//虽然不推荐改变变量保存值的类型,但是在ECMAScript中完全有效

var 声明作用域

var 声明的范围是函数作用域

先写一段代码来看

 function test(){
     var temp = 'hello' //局部变量
 }
 test();
 //调用之后函数退出 局部变量temp销毁
 console.log(temp);//出错temp is not defined

可以看出,使用var定义的变量temp成为了包含它的函数test的局部变量,这意味着在test函数退出时这个temp变量会被销毁(可以了解一下内存管理和垃圾回收)

在函数内定义变量时省略var操作符(var是一个关键字),创建的变量就是全局变量

 function test(){
     temp = 'hello' //局部变量
 }
 test();
 //调用之后函数退出 局部变量temp销毁
 console.log(temp);//hello 
 //变量是全局变量 即使函数退出 变量依然未被销毁

虽然可以通过省略var操作符定义全局变量,但是不推荐,在局部作用域中定义的全局变量很难维护,在严格模式下,像这样给未声明的变量赋值或导致抛出ReferenceError(引用错误)

var声明提升

还是先看代码

 function test(){
     console.log(temp);
     var temp = 'hello' 
 }
 test();//undefined

看到这个代码应该会想到在第二行console.log(temp);中是不是会报错,而最后结果test();//undefined没报错,执行结果还是undefined,这是为什么?
这就涉及到var的声明提升
var声明的变量会自动提升(hoist)到函数作用域的顶部
注意是变量的声明提升但是赋值不提升
上面的代码等价于以下代码:

 function test(){
     var temp; 
     console.log(temp);//undefined
     temp = 'hello' 
 }
 test();//undefined

所以声明提升就是这段代码没有报错而且变量值是undefined的原因

var可以反复多次声明一个变量

function test(){
    var temp = '1';
    var temp = '2';
    var temp = '3';
    var temp = '4';
    console.log(temp);
}
test();//4

let声明

let声明

let和var作用差不多,但是有着非常重要的区别

let作用域

最明显的区别就是,let 声明的范围是块作用域,var声明的范围是函数作用域(块作用域是函数作用域的子集,可以了解一下作用域)
还是看一段代码:

if(true){
    let temp = 1;
    console.log(temp);//1
}
console.log(temp);//Uncaught ReferenceError: temp is not defined

用let声明的变量temp 它的作用域仅限于if块内部,所有在if外部不能引用,所以console.log(temp);//Uncaught ReferenceError: temp is not defined
注意这里要是换成var声明就可以在if块外部引用(因为块作用域是函数作用域的子集,但是也说明了适用于var作用域的限制也适用于let)

let的全局声明

与var变量不同,let在全局作用域中声明的变量不会成为window对象的属性,但是var声明的变量则会
但是这并不意味着可以在页面中重复声明同一个变量,let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续,所以,为了避免SyntaxError,必须确保页面中不会重复声明同一个变量(具体看下面)

let的重复声明与var的重复声明

上面已经知道了var是可以重复声明的,但是let不允许在同一个块作用域中冗余声明(导致SyntaxError报错)
看代码

let temp;
let temp;
//Uncaught SyntaxError: Identifier 'temp' has already been declared

var name;
var name;//重复声明没事

但是
混用let和var进行冗余声明(即重复声明)就会有声明冗余报错

let temp;
var temp;
//Uncaught SyntaxError: Identifier 'temp' has already been declared

var temp;
let temp;
//Uncaught SyntaxError: Identifier 'temp' has already been declared

这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在
既然let的块作用域中重复声明会报错,那如果不在同一个块中重复声明还会报错吗?答案当然是: 不是。
看代码:

let temp = 1;
console.log(temp);//1
if(true){
    let temp = 1;
    console.log(temp);//1 
}
var temp = 1;
console.log(temp);//1
if(true){
    var temp = 1;
    console.log(temp);//1 
}

原因就是:嵌套使用相同的标识符不会报错,因为同一个块中没有重复声明

let条件声明
<script>
   var name = "叶鹤";
   let age = 21;
</script>
<script>
   var name = "青玄"; //已经挂载在window上面了
   let age = 22;
//Uncaught SyntaxError: Identifier 'age' has already been declared 
</script>
<script>
    let name = "叶鹤";
    let age = 21;
</script>
<script>
    console.log(name === '叶鹤');  //true 因为上面的script声明并赋值了
    if (name === '叶鹤') {
        let name = '88';
        console.log(name); //88  在块作用域中name赋值为88
    }
    console.log(name);//叶鹤 
//在if块中的let重新赋值对name没有影响 因为let声明的作用域仅限于该块
    name = '青玄';
    console.log(name); 
    //这里相当于把上面script中的let声明的'叶鹤'重新赋值成'青玄' 
    console.log(window.name);
    //因为是let声明 所以不挂载在window对象上
    try {
        console.log(age); //21 
 //即使是try/catch语句也不能解决,条件块中let声明的作用域仅限于该块
    }
    catch (error) {
        let age;
    }
    age = 22;
</script>

这样就可以看出let关键字不能依赖条件声明模式

不能使用let进行条件式声明是好事,因为条件式声明是一种反模式,如果你发现自己在用这个模式,那一定会有更好的替代方法

暂时性死区(可以与var的声明提升相对理解)

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

//var声明的temp不会被提升
console.log(temp);
//Uncaught ReferenceError: Cannot access 'temp' before initialization
let temp = 1;
//var声明的temp被提升
console.log(temp);//undefined
var temp = 1;

其实在JavaScript引擎在解析代码时候,它也会注意到块后面的let声明,但是在此之前不能以任何方式引用未声明的变量(var有声明提升,但是let没有)。在let声明之前的执行瞬间被称为**“暂时性死区”**,在此阶段 引用任何在后面才声明的变量都会抛出ReferenceError

for循环中的let声明

还是先看代码:

for (var i = 0; i < 5; i++) {

}
console.log(i);//5

循环我们都知道 ,for()括号中的条件中的i就是临时的变量,循环退出i 就应该被销毁,如果用var进行声明,那么for循环中定义的迭代变量会渗透到循环体外部,最后拿到的i就是5

for (let i = 0; i < 5; i++) {

}
console.log(i); //Uncaught ReferenceError: i is not defined

改使用let,for循环中定义的迭代变量会渗透到循环体外部 的问题就消失了,因为迭代变量的作用域仅限于for循环块内部
用let声明迭代变量时,JavaScript引擎会在后台为每个迭代变量声明一个新的迭代变量,这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-in和for-of循环

const声明

const声明

const的行为和let基本一致,唯一一个重要区别就是用它声明变量时必须同时初始化变量,修改const声明的变量会导致运行时错误

那么会有思考,如果我const声明的是对象或者数组,那我修改数组或者对象内部属性还会报错吗?答案是:不会
说一下原因,这个涉及到原始值和引用值(可自行了解),
const声明的限制只适用于它指向的变量的引用。

const a = 1;
console.log(a); //1
a = 2;//Uncaught TypeError: Assignment to constant variable.

这里a就是原始值,可以理解为a存的是一个指针或者地址,它指向了一个内存空间(栈),这个内存空间内保存的是数值1,如果给变量重新赋值,就相当于修改了指向的变量的引用,这在const声明中是限制的
看一下代码:

const arr = [1,2,3];
// arr = 4;//Uncaught TypeError: Assignment to constant variable.
arr[0] = 5;
console.log(arr);//5 2 3

这里arr就是引用值,arr依旧指向一个内存空间(栈),这个内存空间保存着数组的地址(也就是又指向数组的内存空间(堆)),所以修改arr就又修改了指向的变量的引用,还是会报错,但是修改数组里面的值就不在const限制的范围内,就可以修改了

  • const也不允许重复声明
const声明的作用域也是块
for循环中的const声明

JavaScript引擎也会为for循环中的let声明分别创建独立的变量实例,虽然const变量和let变量很相似,但是不能用const来迭代变量(因为迭代变量会自增但是修改const声明的变量会导致运行时错误)
但是,const可以用在for-in和for-of循环,因为每次迭代都会创建新的变量,也就是说在在for-in和for-of循环中const声明的for循环变量每次都是新创建的,是未修改的

声明风格及最佳实践

不使用var

用let或者const的变量有明确的作用域,声明位置,以及不变的值

const优先,let次之

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作

将理解与思考整理出来实属不易,请勿未经允许搬运,很多都融合了自己思考,如果有不正确的帮忙指出修改。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值