变量声明
变量声明
在const a = 1
与 a = 1
,这两条语句的效果相同。但是由于这样的做法很容易不知不觉地创建全局变量
const
const x; // Declaration and initialization
x = 'Hello World'; // Assignment
// Or all in one
const y = 'Hello World';
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
在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);
}
我们可以利用这种块级作用域的特性来避免闭包中因为变量保留而导致的问题,譬如如下两种异步代码,使用
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 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]);
从上述对比我们也可以看出,
const numbers = [1, 2, 3, 4, 6];
numbers = [7, 8, 9, 10, 11]; // error: assignment to constant variable
console.log(numbers[4]);
我们可以参考如下图片理解这种机制,每个变量标识符都会关联某个存放变量实际值的物理地址;所谓只读的变量即是该变量标识符不可以被重新赋值,而该变量指向的值还是可变的。
# 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()
;不过该方法仅对于键值对的
# 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()
也只能防止顶层属性被修改,而无法限制对于嵌套属性的修改,这一点我们会在下文的浅拷贝与深拷贝部分继续讨论。