var, let, const

126 阅读7分钟

var 声明与变量提升

使用var 关键自声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部,如果声明不在任意函数内,则视为全局作用域的顶部,这就是所谓的变量提升。

function getValue(condition){

	if(condition){
		var value = 'red';

		return value;
	}else{
		// value 在此处可以访问,值为 undefined
		return null;
	}

	// value 在此处可以被访问,值为 undefined
}

// value 变量的声明被提升到了顶部,而初始化工作则保留在原处。
// 在else 分支内 value 变量可以访问,此处的值是 undefined,因为它没有被初始化

块级声明

块级声明就是让所声明的变量在指定块的作用域外无法被访问,块级作用域又被称为词法作用域,在如下情况被创建:

  1. 在一个函数内部
  2. 在一个代码块内部

let 声明

let 声明的语法与var 的语法一致,但是let 会将变量的作用域限制在当前代码块中。由于 let 声明并不会被提升到当前代码块的顶部,因此需要手动将 let 声明放置在顶部,以便让变量在整个代码块内部可用。

function fun(condition){
	
	if(condition){
		
		let value = 'red';
		return value;

	} else {

		// value 在此处不可访问
		return null;

	}

	// value 在此处不可用

}

// 由于变量 value 声明使用的是let, 声明没有被提升到函数定义的顶部
// 变量在if 代码块外部无法被访问 
// 在condition的值为false时,该变量永远不会被声明初始化

禁止重复声明

如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行let 声明会抛出错误


var count = 30;

// 语法错误
let count = 20;

// count 被变量声明了两次
// let 不能在同一作用域内重复声明一个已有标识符
var count = 30;

// 不会抛出错误
if(condition){

	let count = 20;

}

// 在嵌套的作用域内使用let 声明一个同名的新变量,不会抛出错误
// 此处的let 在if语句内部创建了一个新的count 。不是在同一级再次创建
// 在if 代码块内部,这个新变量会屏蔽全局的count

常量声明

使用 const 声明的变量被认为是常量(constant),它们的值在被设置完成后就不能再被改变


// 有效的常量
const value = 30;

// 语法错误,未进行初始化
const name;

// value 变量被初始化, 它的const 声明能正常起效
// name 变量没有被初始化,导致在试图运行这段代码时抛出了错误

常量声明与 let 声明

常量声明与let 声明都是块级声明,在语句块外部都无法访问,并且不会被提升


if(condition){
	const value = 5;

	// do something
}

// value 在此处无法访问
// value 在if 语句中被声明,value 在代码块外无法被访问

const 声明会在同一作用域(全局或是函数作用域)内定义一个已有变量时会抛出错误


var message = 'Hello';
let age = 30;

// 均会抛出错误
const message = '你好';
const age = 25;

// const 声明前面添加了 var 与 let 声明的情况下,二者都会出问题

使用const 声明对象

const 声明会阻止对于变量绑定与变量自身值的修改,这意味着const 声明不会阻止对变量成员的修改


const person = {
	name: 'xiaoming'
};

// 正常
person.name = 'lisi';

// 抛出错误
person = {
	name: 'wangwu'
}

// person 初始化时被绑定了带有一个属性的对象,修改person.name是可能的,该操作只修改了 person 对象的成员,没有修改person 的绑定值
// 当代码试图为person 对象自身赋值时,这会改变变量绑定,会抛出错误

// const 阻止的是对于变量绑定的修改,不会阻止对成员值对修改

暂时性死区

使用 let 或 const 声明对变量,在达到声明处之前都是无法访问的,试图访问会导致一个引用错误,暂时性死区经常被用于描述 let 或 const 声明的变量为何在声明处之前无法被访问。

if(condition) {
	console.log(typeof value); // 引用错误
	let value = 'red';
}
 
// value 变量使用了let 进行定义与初始化,但该语句不会被执行,因为声明之前 抛出了错误
// value  位于被JS 社区称为暂时性死区的区域内
// 当JS引擎检视代码发现变量声明时,在面对var 的情况下将声明提升到函数或全局作用域顶部,而 let 或 const 会将声明放在暂时性死区

循环中的块级绑定

在for 循环内,想让一次性的循环计数器仅能在循环内部使用

for (var i = 0; i<10; i++) {
	process(item[i]);
}

// 此处i 不能访问,会抛出错误
console.log(i);

// i 只能在for 循环内部可用,一旦循环结束,变量在任意位置都不可访问

循环内的函数

var 的特点使得循环变量在循环作用域之外仍然可被访问,于是在循环内创建函数就变得有问题。

var funcs = [];

for(var i=0; i<10; i++){
	funcs.push(function(){console.log(i);});
}

funcs.forEach(function(func){
	func();  // 输出数值 ‘10’ 10次
})

// 变量i 在循环的每次迭代中都被共享了,意味着循环内创建的那些函数都拥有对于同一变量的引用
// 循环结束后,变量 i 的值会是10

在循环内使用立即调用函数表达式(IIFEs),以便在每次迭代中强制创建变量的一个新副本

var funcs = [];

for(var i=0; i<10; i++) {
	funcs.push((function(value){
		return function(){
			console.log(value)
		}
	}(i)));
}

funcs.forEach(function(func){
	func();  // 从 0 到 9 依次输出
})

// 变量 i 被传递 IIFE ,从而创建了value 变量作为自身副本并将值储存于其中
// value 变量的值被迭代中的函数所使用,因此在循环从0 到9 的过程中调用每个函数都返回了预期的值
// 使用let 与 const 的块级绑定可以在ES6 中为你简化这个循环

循环内的let 声明

let 声明通过有效模仿IIFE的作用简化了循环。在每次迭代中,都会创建一个新的同名变量并对其进行初始化

var funcs = [];

for(let i=0; i<10; i++) {
	funcs.push(function() {
		console.log(i);
	});
}

funcs.forEach(function(func){
	func();    // 从 0 到9 一次输出
})

// 循环中let 声明每次都创建了一个新的 i 变量,
// 在循环内部创建的函数获得了各自的 i 副本,
// 每个 i 副本的值都在每次循环跌打声明变量时被确定
// 这种方式在 for - in 和 for -of 中同样适用

循环内的常量声明

在常规的for 循环中,可以在初始化时使用 const ,但循环会在试图改变该变量的值时抛出错误


var funcs = [];

// 在一次迭代后抛出错误
for(const i = 0; i<10; i++){
	funcs.push(
		function(){
			console.log(i);
		}
	)
}

// i 被声明为一个常量,循环第一次迭代成功执行,此时 i 的值为0
// i ++ 执行时,错误会被抛出
// 循环中只能使用const 来声明一个不会被改变的变量

// const 在for -in 和 for -of 循环中与let 变量效果相同

全局块级绑定

let 与 const 不同于 var 的另一个方面是在全局作用域上的表现,当在全局作用域上使用 var 时,它会创建一个新的全局变量,并成为全局对象的一个属性

var RegExp = 'Hello';
console.log(window.RegExp);  // "Hello"

// 尽管全局的RegExp 是定义在window 上的,它仍不能防止被var 重写
// 声明了一个新的全局变量 RegExp 覆盖了原有的对象

若在全局作用域上使用let 或const , 虽然全局作用域上会创建新的绑定,但不会有任何属性被添加到全局对象,这意味着不能使用let 或 const 来覆盖一个全局变量,只能将其屏蔽。

let RegExp = 'Hello';
console.log(RegExp);   // 'Hello'
console.log(window.RegExp === RegExp);  // false

const ncz = "Hi!";
console.log(ncz);   // "Hi!"
console.log("ncz" in window);  // false

// let 声明创建了RegExp 的一个绑定,并屏蔽了全局的RegExp
// 这表示 window.RegExp 与 RegExp 是不同的,全局作用域没有被污染

// const 声明创建了一个 ncz 的绑定,但未在全局对象上创建属性,
// 若不想在全局对象上创建属性时,这种特性会让 let const 在全局作用域中更安全

块级绑定新的最佳实践

在默认情况下使用const 并且只在知道变量值需要被更改的情况下才使用let