一、使用 var 的函数作用域声明---变量提升机制
在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中:最接近的上下文是函数的局部上下文;在 with 语句中,最接近的上下文也是函数上下文;如果变量未经声明就被初始化,那么它就会自动被添加到全局上下文。
function add(num1, num2){ var num = num1 + num2 return sum } let result = add(10, 20); // 30 console.log(sum) //报错,sum在这里不是有效变量
这里函数 add 定义了一个局部变量 sum,保存加法操作的结果。但是变量 sum 在函数外部是访问不到的,如果省略了上面例子中的关键字 var ,那么 sum 在 add 被调用后就变成可以访问的了
function add(num1, num2){ num = num1 + num2 return sum } let result = add(10, 20); // 30 console.log(sum) // 30
这一次,变量 sum 变量并没有使用 var 声明,在调用 add() 之后 ,sum 被添加到了 全局上下文,在函数退出之后依然存在,从而在后面可以访问到。
总结:
var 声明会被拿到函数 或 全局作用域的顶部,位于作用域中 所有代码之前。这个现象叫作“提升”(hoisting)。提升让同一作用域的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致 合法却奇怪的现象。
未经声明而初始化变量时 JavaScript 编程中 一个非常常见的错误,会导致很多问题。为此,在初始化变量之前,一定要先声明变量。在严格模式下,未经声明就初始化的变量会报错
二、使用 let 的块级作用域声明---暂时性死区( temporal dead zone )
ES6新增的 let 关键字跟 var 很相似,但它的作用域是块级的,这也是 JavaScript 中的新概念。块级作用域是由最近的一对 { } 界定,比如:if块、while块、function块、甚至连单独的块也是 let 声明变量的作用域。
var a; var a; // 不会报错,后声明都变量会覆盖先声明的变量 { let b; let b; } // SyntaxError : 标识符 b 已经声明过了
看上面示例:let 与 var 的另一个不同之处是在同一作用域内不能声明两次。重复的 var 声明会被忽略,而重复的 let 声明 会抛出 SyntaxError
for(var i = 0; i < 10; ++i){ } console.log(i); // 10 for(let j = 0; j < 10; ++j){ } console.log(j); // ReferenceError : j 没有定义
来看上面两个例子:let 的行为非常适合再循环中声明迭代变量。使用 var 声明的迭代变量 会泄露到循环外部,这种情况应该避免。
总结:
严格来讲,let 在 JavaScript 运行时也会被 提升,单由于“暂时性死区”(temporal dead zone) 的缘故,实际上不能再声明之前使用 let 变量。因此,从写 JavaScript 代码的角度来说,let 的提升和 var 是不一样的。
暂时性死区:用一句话来概括就是 JavaScript引擎 一进入作用域创建变量,到变量开始可以被访问之间的一段时间,就称之为 TDZ。
三、使用 const 的常量声明
const a; // SyntaxError : 常量声明时没有初始化 const b = 3; console.log(b); // 3 b = 4; TypeError : 给常量赋值
除了 let ,ES6同时还增加了 const 关键字。使用 const 声明的变量必须同时初始化某个值,一经声明,在其声明周期都不能在重新赋予新值。const 除了要遵循以上规则,其他方面与 let 声明是一样的;
const o1 = {}; o1 = {}; // TypeError : 给常量赋值 const o2 = {}; o2.name = 'Barry'; console.log(o2.name); // 'Barry'; const o3 = Object.freeze({ }); o3.name = 'Barry'; console.log(o3.name); // undefined
const 声明只应用到最顶级原语或对象。 换句话说,赋值为对象的 const 变量 不能再被重新赋值为其他引用值,但对象的键则不受限制;
如果想让整个对象都不能修改,可以使用 Object.freeze() , 这样再给属性赋值时虽然不会报错,但会静默失败;
四、let const 更推荐那个?
开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明,除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现重新赋值导致的bug。