说说var、let和const

334 阅读3分钟

var

ES5时作用域分为全局和函数作用域,我们还在使用var定义变量,但var定义的变量会有些奇奇怪怪的现象。

变量提升

console.log(a);  // undefined

var a = 1;

程序在定义前访问a变量,按照执行顺序程序应该报未找到变量的错误,但却输出undefined,这里涉及到var变量变量提升的概念,上面例子伪代码相当于

var a;

console.log(a);  // undefined

a = 1;

var定义的变量会被提升到所在作用域的顶部

重复定义

var a = 1;

var a = 2;
console.log(a);  // 2

var a = '3'
console.log(a);  // '3'

可以使用var重复定义同一变量

绑定到window

var a = 1;

console.log(window.a);  // 1

浏览器中,全局作用域var定义的变量会绑定到window对象上(Node环境不会绑定global对象),代码里没显示声明,但程序默默的把var变量添加到window对象上了(背地里搞事)

这里补充一下不用任何关键字定义变量的特点

  • 不会提升变量

    console.log(a);  // ReferenceError: a is not defined
    
    a = 1
    
  • 会绑定到全局作用域(浏览器和node)

    // 浏览器环境
    var a = 1;
    b = 2;
    
    console.log(window.a);  // 1
    console.log(window.b);  // 2
    
    
    // node环境
    var a = 1;
    b = 2;
    
    console.log(global.a);  // undefined
    console.log(global.b);  // 2
    
    
  • 可以使用delete删除全局作用域下的变量,var定义的变量不可以

    // 浏览器环境
    var a = 1;
    b = 2;
    
    console.log(Object.getOwnPropertyDescriptor(window, 'a'));  
    // {value: 1, writable: true, enumerable: true, configurable: false}
    console.log(Object.getOwnPropertyDescriptor(window, 'b'));  
    // {value: 2, writable: true, enumerable: true, configurable: true}
    
    

    浏览器中,a、b变量都会绑定到全局作用域,a变量的数据描述符configurable为false,b变量则为true,意味着可以删除b变量,不能删除var定义的a变量

    var a = 1;
    b = 2;
    
    console.log(window.a);  // 1
    console.log(window.b);  // 2
    
    delete window.a;
    delete window.b;
    
    console.log(window.a);  // 1
    console.log(window.b);  // undefined
    

let/const

ES6引入了let/const定义变量的关键字,使用这俩关键字定义变量更加符合我们对程序运行的预期。

不会提升变量

console.log(a);  // ReferenceError: Cannot access 'a' before initialization

let a = 1;

不能重复定义

let a = 1;

let a = 2;  // SyntaxError: Identifier 'a' has already been declared

不绑定全局作用域

// 浏览器环境
let a = 1;

console.log(window.a);  // undefined

let、const没有var定义变量的特点,也就是消除了那些奇怪的现象。

引入块级作用域

if(false) {
  var a = 1;
}

console.log(a);  // undefined

// 伪代码
var a;

if(false) {
  a = 1;
}

console.log(a);  // undefined

var变量只有全局作用域和函数作用域,因为if不是函数, 又经过变量提升,相当于a定义在全局作用域中,console能访问到a

if(false) {
  let a = 1;
}

console.log(a);  // ReferenceError: a is not defined

由于块级作用域的隔离,全局作用域下console不能访问a

let和const的区别

  • let定义的变量可以修改,const用于定义常量,其值可以修改,绑定的内存不可以修改

    const obj = {};
    
    obj.a = 1;  // 可以修改值
    
    obj = 1;  // TypeError: Assignment to constant variable
    // 相当于obj指向的内存空间是不可修改的,内存空间内的值可以修改
    
  • 不同的编程思想

    这里有一个获取商品满减后价格的方法,用let和const可能会写成这样

    // let
    function getReducePrice(originPrice) {
      // 原始价格减去店铺优惠
      let reducePrice = reduceShopDiscounts(originPrice);
      // 店铺优惠基础上再减去活动优惠得到最终价格
      reducePrice = reduceActivityDiscounts(reducePrice);
    
      return reducePrice;
    }
    
    // const
    function getReducePrice(originPrice) {
      const shopDiscountsPrice = reduceShopDiscounts(originPrice);
      const reducePrice = reduceActivityDiscounts(shopDiscountsPrice);
    
      return reducePrice;
    }
    
    

    使用let时,我们一直在改变reducePrice,可以说这样方式是基于过程的,使用const可以说基于命名,通过命名了解方法整个过程。当然目前使用const命名越来越普遍,我们更加期望变量初始化后不再改变,这样就较容易减少由于变量变化造成bug的现象。