变量声明
变量声明
在 JavaScript 中,基本的变量声明可以用 const 方式;JavaScript 允许省略 var,直接对未声明的变量赋值。也就是说,const a = 1
与 a = 1
,这两条语句的效果相同。但是由于这样的做法很容易不知不觉地创建全局变量(尤其是在函数内部),所以建议总是使用 const 命令声明变量。在 ES6 中,对于变量声明的方式进行了扩展,引入了 let 与 const。var 与 let 两个关键字创建变量的区别在于,const 声明的变量作用域是最近的函数块;而 let 声明的变量作用域是最近的闭合块,往往会小于函数块。另一方面,以 let 关键字创建的变量虽然同样被提升到作用域头部,但是并不能在实际声明前使用;如果强行使用则会抛出 ReferenceError 异常。
const
const 是 JavaScript 中基础的变量声明方式之一,其基本语法为
const x; // Declaration and initialization
x = 'Hello World'; // Assignment
// Or all in one
const y = 'Hello World';
ECMAScript 6 以前我们在 JavaScript 中并没有其他的变量声明方式,以 const
声明的变量作用于函数作用域中,如果没有相应的闭合函数作用域,那么该变量会被当做默认的全局变量进行处理。
function sayHello() {
const hello = "Hello World";
return hello;
}
console.log(hello);
像如上这种调用方式会抛出异常 : ReferenceError: hello is not defined
,因为 hello
变量只能作用于 sayHello
函数中,不过如果按照如下先声明全局变量方式再使用时,其就能够正常调用
const hello = "Hello World";
function sayHello() {
return hello;
}
console.log(hello);
let
在 ECMAScript 6 中我们可以使用 let
关键字进行变量声明
let x; // Declaration and initialization
x = "Hello World"; // Assignment
// Or all in one
let y = "Hello World";
let
关键字声明的变量是属于块作用域,也就是包含在 {}
之内的作用于。使用 let
关键字的优势在于能够降低偶然的错误的概率,因为其保证了每个变量只能在最小的作用域内进行访问。
const name = "Peter";
if (name === "Peter") {
let hello = "Hello Peter";
} else {
let hello = "Hi";
}
console.log(hello);
上述代码同样会抛出 ReferenceError: hello is not defined
异常,因为 hello
只能够在闭合的块作用域中进行访问,我们可以进行如下修改
const name = "Peter";
if (name === "Peter") {
let hello = "Hello Peter";
console.log(hello);
} else {
let hello = "Hi";
console.log(hello);
}
我们可以利用这种块级作用域的特性来避免闭包中因为变量保留而导致的问题,譬如如下两种异步代码,使用 const 时每次循环中使用的都是相同变量;而使用 let 声明的 i 则会在每次循环时进行不同的绑定,即每次循环中闭包捕获的都是不同的 i 实例:
for(let i = 0;i < 2; i++){
setTimeout(()=>{console.log(`i:${i}`)},0);
}
for(const j = 0;j < 2; j++){
setTimeout(()=>{console.log(`j:${j}`)},0);
}
let k = 0;
for(k = 0;k < 2; k++){
setTimeout(()=>{console.log(`k:${k}`)},0);
}
// output
i:0
i:1
j:2
j:2
k:2
k:2
const
const
关键字一般用于常量声明,用 const
关键字声明的常量需要在声明时进行初始化并且不可以再进行修改,并且 const
关键字声明的常量被限制于块级作用域中进行访问。
function f() {
{
let x;
{
// okay, block scoped name
const x = "sneaky"; // error, const
x = "foo";
} // error, already declared in block
let x = "inner";
}
}
JavaScript 中 const 关键字的表现于 C 中存在着一定差异,譬如下述使用方式在 JavaScript 中就是正确的,而在 C 中则抛出异常:
# JavaScript
const numbers = [1, 2, 3, 4, 6]
numbers[4] = 5
console.log(numbers[4]) // print 5
# C
const int numbers[] = {1, 2, 3, 4, 6};
numbers[4] = 5; // error: read-only variable is not assignable
printf("%d\n", numbers[4]);
从上述对比我们也可以看出,JavaScript 中 const 限制的并非值不可变性;而是创建了不可变的绑定,即对于某个值的只读引用,并且禁止了对于该引用的重赋值,即如下的代码会触发错误:
const numbers = [1, 2, 3, 4, 6];
numbers = [7, 8, 9, 10, 11]; // error: assignment to constant variable
console.log(numbers[4]);
我们可以参考如下图片理解这种机制,每个变量标识符都会关联某个存放变量实际值的物理地址;所谓只读的变量即是该变量标识符不可以被重新赋值,而该变量指向的值还是可变的。
JavaScript 中存在着所谓的原始类型与复合类型,使用 const 声明的原始类型是值不可变的:
# Example 1
const a = 10
a = a + 1 // error: assignment to constant variable
# Example 2
const isTrue = true
isTrue = false // error: assignment to constant variable
# Example 3
const sLower = 'hello world'
const sUpper = sLower.toUpperCase() // create a new string
console.log(sLower) // print hello world
console.log(sUpper) // print HELLO WORLD
而如果我们希望将某个对象同样变成不可变类型,则需要使用 Object.freeze()
;不过该方法仅对于键值对的 Object 起作用,而无法作用于 Date、Map 与 Set 等类型:
# Example 4
const me = Object.freeze({name: “Jacopo”})
me.age = 28
console.log(me.age) // print undefined
# Example 5
const arr = Object.freeze([-1, 1, 2, 3])
arr[0] = 0
console.log(arr[0]) // print -1
# Example 6
const me = Object.freeze({
name: 'Jacopo',
pet: {
type: 'dog',
name: 'Spock'
}
})
me.pet.name = 'Rocky'
me.pet.breed = 'German Shepherd'
console.log(me.pet.name) // print Rocky
console.log(me.pet.breed) // print German Shepherd
即使是 Object.freeze()
也只能防止顶层属性被修改,而无法限制对于嵌套属性的修改,这一点我们会在下文的浅拷贝与深拷贝部分继续讨论。