案例分析
// 案例1
console.log(name);
var name = 'xiaoming';
console.log(name);
// 案例2
console.log(name);
let name = 'xiaoming';
console.log(name);
// 案例3
console.log(name);
const name = 'xiaoming';
console.log(name);
每个案例 两个输出分别是什么?为什么?
案例1输出 undefined 和 xiaoming (第一个打印使用name时,name尚未初始化赋值,因此是undefined)
案例2和案例3均报错,因为let 和 const 没有 var 变量声明提升的优势,造成了未定义先使用的错误。
var let const 有何不同
以下是 三个关键字 在MDN 的 解释。
var :声明会提前
let :声明不会提前 限定作用域
const :声明不会提前,常量 声明时必须赋值 赋值后不可变更 不可重新声明 限定作用域
var
作用:使用var关键字来声明一个变量。在声明的同时,可以进行初始化,也可以不初始化,不初始化值为未定义undefined
重点:
Variable declarations, wherever they occur, are processed before any code is executed. The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value.
变量的声明 不管在哪里声明的,它们都早于任何代码之前执行 变量的声明操作.
使用var声明的变量其作用于在它当前的执行上下文,要么在一个封闭的函数内,或者在函数外声明的,全局的。
而且,如果你重新声明了这个变量,这个变量的值并不会丢失。
因为声明是在任何代码之前就会执行的,这就解释了 为什么 第一个案例 后声明先使用没有报错了。
当然还有更确切的解释,请接着看:
var 提升
Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code.
因为声明变量是在代码执行前执行的,不管在哪里声明一个变量都等于把它声明在顶部。这就意味着一个变量可以出现在它被声明之前,这种行为叫做提升(hoisting),因为它看起来把变量声明移到了函数或者全局代码的顶部。
For that reason, it is recommended to always declare variables at the top of their scope (the top of global code and the top of function code) so it's clear which variables are function scoped (local) and which are resolved on the scope chain.
因此,推荐在变量作用域顶部(全部代码的顶部和函数代码的顶部)声明变量,这样就非常清楚 哪些变量是函数作用域 哪些变量是在作用域链上解决的。
非常重要的一点需要指出来,var 提升,会影响变量的声明,但是不影响它值的初始化,当执行到赋值表达式的时候,会去赋值。
let
作用:声明一个块作用域的局部变量,是否初始化也是可选的。
let 允许你声明一个限制在块,声明,表达式内使用的变量。它不像var关键字,var关键字定义了一个全局变量或者限定到整个函数中的变量而不管块的范围。
这里的块可能有很多含义,比如括在{}中的 从 {开始 到}结束 都可以称之为块,
范围规则
使用let声明的变量被限定在它们被定义的代码块内,在子代码块内也是一样的。在这里,let和var主要的区别在于var变量作用在整个函数,下面两个例子很好解释:
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
注意:let 没有 var 提升的 优待
In ECMAScript 2015, let bindings are not subject to Variable Hoisting, which means that let declarations do not move to the top of the current execution context. Referencing the variable in the block before the initialization results in a ReferenceError (contrary to a variable declared with var, which will just have the undefined value). The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.
let声明不会移动到当前执行上下文的顶部,如果在let定义前使用,会抛出错误。
这就解释了案例2中为什么会报错了
const
使用const创建一个常量,这个常量可以是全局的,也可是被声明的块内。
不像var变量,全局常量不会成为window对象的属性。
常量一旦声明必须初始化赋值,且后续不可重新声明,不可重新赋值。
const声明创建了一个值的只读引用,者并不意味着引用指向的对象是不可变的。只是这个变量标识符不能重新赋值罢了。
和 let 一样, const也没有 var提升的优势。所以 定义前就使用 也是会报错的。
延伸
首先,var是js的大大坑,为了填补var的坑,es6引入了let。
想要了解var,就要先掌握代码在处理过程是对变量的查找过程。
console.log(name);
var name = 'xiaoming';
console.log(name);
从上面的代码,很直观的看出第一行输出name时,name这个变量并没有声明。如果变量没有声明,代码肯定执行会抛出异常。但上面代码并没有抛出异常,反而输出了一个‘undefined’。这是为什么?
事实上,上面的代码等价于
var name;
console.log(name);
name = 'xiaoming';
console.log(name);
通过等价代码能看出,最先声明了一个变量name,并没有赋值,没有赋值的变量默认值是'undefined'。
通过这样的等价转换是不是就明白了结果。
JS编译器在处理代码分为两个过程:编译期和执行期。
再来看源代码
console.log(name);
var name = 'xiaoming';
console.log(name);
重点看第一行代码,在编译期中,第一行需要输出name,name在哪里呢?后面有一个var name,所以编译会把var name提前,紧接着是执行期,name只是声明提前了,但并没有赋值,所以输出了默认的undefined。
所以,是编译器在编译期做了一些手脚,把var变量的声明提前了。
从上面这件事我们可以引申到函数表达式定义的理解。
fun();
var fun = function() {
console.log('hello, js');
}
fun();
以上代码在执行第一行fun()时会抛出一个错误。
TypeError: fun is not a function
at Object.<anonymous> (/Users/youngxu/Desktop/sample.js:1:63)
at Module._compile (module.js:569:30)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:503:32)
at tryModuleLoad (module.js:466:12)
at Function.Module._load (module.js:458:3)
at Function.Module.runMain (module.js:605:10)
at startup (bootstrap_node.js:158:16)
at bootstrap_node.js:575:3
虽然fun声明被提前了,但fun的定义并没有提前,fun只是被默认赋值了一个undefined值。这又不是一个函数,此时执行fun()时就会抛出错误,编译器很明白发生了什么。为什么?因为编译器抛出的是TypeError错误呀,好好体会一些。
事实上,js对var在编译器的处理过程会带来很多问题,最好的处理原则就是先明确声明,再明确定义。所以,js在后面引入了let,let的变量并不会提前声明,没有声明的变量会明确抛出(ReferenceError: name is not defined)
。所以建议以后在写js代码时,能用let就不要用var。
至于const,const是用来定义不能修改的常量的,只能有一次赋值,不能提前声明。