JS中var,let以及const的区别

359 阅读7分钟

ES6新增的两个关键字

在ES5中我们声明变量都是使用的var关键字,从ES6开始新增了两个关键字可以声明变量:let、const。

let关键字

从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量。

const关键字

const关键字是constant的单词的缩写,表示常量、衡量的意思,它表示保存的数据一旦被赋值,就不能被修改, 它表示保存的数据一旦被赋值,就不能被修改。

  • 注意:let、const不允许重复声明变量

案例

//从直观角度来看let/const与var都是声明变量没有太大区别。
var foo = "foo"
let bar = "bar"


const name = "abc" //constant(常量)
name = "zahngsan" 
console.log(name); //wrong

//注意事项一:用const定义的变量不可修改
//但是传递的如果是一个引用类型(内存地址),可以通过引用(地址)找到对应的对象,进而修改对象的属性

const obj = { //这里obj就是一个引用了 obj保存的是类似0x100的地址
    foo:"foo"
}
obj.foo = "aaa" //这个是可以的,通过地址拿到值
console.log(obj.foo); //aaa

//但是如果obj = {} //这个就错了,这是直接修改了值

//注意事项二:无论是let还是const定义的变量名都是不可以重复定义的
let foo = "foo"
let foo = "abc" //wrong
const way = 'that way'
const way = 'migos' //wrong

探讨作用域提升的问题

什么是作用域提升?

作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升。

探讨let与const的作用域提升

我们都知道var定义的变量是有作用域提升的。如果您对这方面的知识不了解,可以看下我写的第一篇文章浏览器执行原理、V8引擎。 但是let 与const定义的变量是没有作用域提升的。这个在社区中争议很大。争议的点是,这些变量在被定义之前有没有创建。

官方(ECMA262)给出的解释是这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值。

所以说,当词法环境创建的时候,变量就已经被创建了,但是在被赋值之前,你不能访问它而已。

// console.log(foo); //undefined
// var foo = "foo"  

console.log(foo); //wrong let是没有作用域提升的
//let/const它们是没有作用域提升的
let foo = "foo" 

var的变量在window对象中的保存

var定义的变量是会被保存在window对象(Go里)中的。这是很不好的现象,很容易造成bug。而const与let定义的变量就没有这个毛病。

var foo = "foo"
var message = "Hello World"
console.log(window.foo);
console.log(window.message); //GO对象

window.message = "哈哈哈"
console.log(message);

//VE是有这些变量的,这些变量被v8放在了variables对象里面,它的数据类型是VariableMap(它是一个hashmap)


// v8是不实现window的,它是包含v8的浏览器实现的
// window:{Date/Number,还要那些变量}这其实是bug的温床,不应该放到window里面。

块级作用域的理解

ES5或之前就没有块级作用域。什么是块级作用域呢。

//block(块)
// {
//     //表达式
//     var foo = "foo"
// }

// 在C语言内,你块里声明的变量,外面是访问不了的,因为它是由作用域的。
//但是ES5之前是没有块级作用域的,这玩意({})形同虚设,没有任何意义。

//在es5中只有两个东西会形成作用域
//1.全局作用域
//2.函数作用域(函数的代码块),函数会顺着作用域链往外找,但是到全局作用域就停止寻找。

ES6的块级作用域

ES6的块级作用域对let/const/function/class声明的类型有效。但函数要注意下,大部分浏览器为了兼容以前的代码,让function没有块级作用域了。

// ES6的块级作用域对let/const/function/class声明的类型有效
{
    let foo = "why"
    function demo(){}
    class person{}

}
console.log(foo); //报错foo is not defined
demo() //函数能访问,这是因为不同的浏览器有不同的实现,大部分浏览器为了兼容以前的代码,让function没有块级作用域了

// 如果是只支持es6的浏览器,那必然报错。
var p = new Person() //Person is not defined

if,switch for循环的块级作用域

//if语句的代码块就是块级作用域
if(true){
    var foo = "foo" //外面可以访问
    let bar = "bar" //外面不能访问
}

//块级作用域
switch(color){
    case"red":
        let bar = "abar" 
}
console.log(bar); //报错

for(var i = 0; i < 10; i++){
    console.log("hello"+i);
}
console.log(i); //能访问的

for(let i = 0; i < 10; i++){
    console.log("hello"+i);
}
console.log(i); //报错undefined

块级作用域的应用场景

相信很多朋友都做过类似轮播图的那种案例,或者多个按钮点击的案例,要么用闭包要么this解决问题,反正是一言难尽,看下下面的案例就懂了。

//html代码省略,定义了5个按钮
const btns = document.getElementsByTagName('button')

for(var i = 0; i < btns.length; i++){
    btns[i].onclick = function(){
        console.log("第" + i + "个按钮被点击"); //分析:要访问i要往上层作用域中找,找到最外面的全局作用域,这时候i已经变成4了
    }

    //解法1(立即执行函数)闭包 (函数会形成作用域)
    // (function(n){
    //     btns[i].onclick = function(){
    //         console.log("第" + n + "个按钮被点击"); //分析:要访问i要往上层作用域中找,找到最外面的全局作用域,这时候i已经变成4了
    //     }
    // })(i) //将i传入
}

//解法2 let 
for(let i = 0; i < btns.length; i++){
    btns[i].onclick = function(){
        console.log("第" + i + "个按钮被点击"); 
    }
}

分析let定义的i在for循环中的表现形式

const names = ['abc','cba','ccc']

for(let i = 0; i < names.length ; i++){
    console.log(names[i]);
}

//用let的for循环会遍历3次,形成三次块级作用域
{
    let i = 0
    console.log(names[i]);
}

{
    //let会再定义一次i
    将上面的i++
    let i = 上面的那个结果 //temp = i++  i = temp
}

//这里是不能用const的,因为要做++的操作,因为const是不允许你做修改的
//比如
// const num = 0
// num ++ //wrong

//for of ES6新增的遍历数组(对象)
for(let item of names){  //尽量不要用var,这里是可以用const的(它是直接赋值的) 
    console.log(item);
}

ES6新增的遍历数组(对象)方式 for of

const names = ['abc','cba','ccc']

//for of ES6新增的遍历数组(对象)
for(let item of names){  //尽量不要用var,这里是可以用const的(它是直接赋值的) 
    console.log(item);
}

let/const定义的变量存在暂时性死区

在一个代码中,在使用let,const声明的变量,在声明之前,变量都是不可以访问的,我们称之为暂时性死区(TDZ)。

var foo = "foo"

if(true){
    console.log(foo);

    let foo = "abc" //报错这就是暂时性死区
    //只要是在内部用let或者const使用了这个变量,前面的你就是不能访问的。
}

function bar(){
    console.log(foo);

    let foo = "abc"  //这样子也会报错的
}

bar()

var,let,const的选择

var表现出更多的特殊性:作用域提升,window全局对象添加属性,没有块级作用域,这都是历史遗留问题,这些特殊性都是js设计之初的语言缺陷。市面上还是有很多题目利用这些缺陷出一系列的面试题,用来考察大家对js语言本身以及底层的理解。但是实际工作中,我们最好用最新的规范来编写,不要再用var来定义变量了,兼容性问题大家也不用操心,babel可以帮我们处理。

let与const的选择

优先推荐使用const(不确定会不会改),如果确实有一天要改,再改它的类型。const可以改变数据的安全性。减少bug的产生。