基础
参考《JavaScript高级程序设计》(第四版),白话理解
第三章-语言基础
3.3变量
Js变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有三个:var、const和let
3.3.1var 关键字
var a;
// 这里声明了一个变量a,值是undefined
var b = ’hi'
// 这里声明一个变量b,值类型是 string
// 我们也可以修改类型,但是不推荐
b = 1
1.var 声明作用域
使用var定义的变量,会成为包含它的函数的局部变量。如:使用var在函数内定义,意味着该变量将在函数退出时被销毁。
function test(){
var mes = 'hi'
// mes = 'hi' 全局变量,不推荐
console.log(mes);
}
test()
console.log(mes); // 这里报错,not defined
上面例子中var创建的变量,在test函数中,运行后就销毁了,我们也可以省略 var
关键字,这样就成为了全局变量,但是不推荐,不可维护和理解。
2.声明提升
声明提示的意思是,关键字 var
声明的变量总会提到作用域顶部进行声明:
function test(){
console.log(age);
var age = 1
}
test()
// 这里先log出age,但是前面没声明,测试中没有报错,变量声明提升为下
function test(){
var age
console.log(age);
age = 1
}
test()
// 这就是声明提升,把变量声明都提到顶部
// 这样我们反复var一个变量也没问题,var都变为一个在顶部,只是修改变量
function test(){
var age = 1
var age = 10
var age = 100
console.log(age);
}
test()
3.3.2let 声明
1.作用域
var的作用域是函数,而let的作用域是块作用域
if(true){
var name = 'Matt'
console.log(name);
}
console.log(name);
if(true){
let age = 1
console.log(age);
}
console.log(age); // age is not defined
在上面代码中,age
是在 if
外被引用,会报错,因为它作用域在块内部
另外一点,JavaScript引擎会记录用于声明变量的标识符和块作用域,因此嵌套使用相同标识符不会报错,因为同一个块中没有重复声明
2.暂时性死区
let
和 var
的另一个区别,var会声明提升,而let不会。
解析代码时,JavaScript引擎会注意到出现在块后面的 let
声明,只不过在此之前不能以如何方式来引用未声明的变量。在let声明之前的执行瞬间被称为《暂时性死区》
3.全局声明
在全局作用域中,var
声明的变量会成为window对象的属性,但是 let
不会
var age = 1
console.log(window.age); // 1
let name = 1
console.log(window.name); // undefined
但是 let
变量一样存在于全局作用域。
4.条件声明
var
声明变量时,由于声明提升,会自动将多余的声明提升到作用域顶部合为一个声明。
因为 let
是块作用域,所以不可能检查前面是否已经使用 let
声明过同名变量,同时也不可能在没有声明的情况下声明它。
var name ='Matt'
let age = 26
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设还没声明过
var name = 'ZZZZ'
// 上面是没有问题,因为可以被作为一个提升声明来处理
// 不需要检查之前是否声明过同名变量
let age =36 // 这里如果age 之前声明过,这里会报错 Identifier 'age' has already been declared
var name = 'Matt'
let age = 26
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设还没声明过
if(typeof name === 'undefined'){
let name
}
// name被限制在if {} 块的作用域内
// 因此这个赋值形同全局赋值,是另外的赋值
name = 'ZZZ'
console.log(name);
try {
console.log(age); // 如果age,没有声明过,则会报错
} catch (error) {
let age
}
age = 26 // 上面的age是局部块作用域,这里age=26,是另外创建的变量
注意:不能使用 let
进行条件声明是件好事,因为条件声明是一种反模式,会变的更难理解。我们在块作用域中可以使用 let
限制它的范围,但是不是用来条件声明。
5.for循环中的let声明
1.变量渗透
在for循环中,使用 var
进行声明,由于 var
是函数作用域,所以它会渗透到这个函数的所有地方,造成污染
for(var i =0;i < 5;++i){
}
console.log(i); // 在这里for循环体外,也可以输出i的值,造成污染
2.let-for循环
使用 let
可以限制变量作用域在for循环内,不会造成污染,在 for
循环外也读不到这个变量
for(let i =0;i < 5;++i){
}
console.log(i); // i is not defined
3.迭代变量
在使用 var
时,我们每次迭代的是同一个变量,然后这个变量造成推出循环的原因,这有可能不利于我们。
for(var i =0;i < 5;++i){
setTimeout(()=>{console.log(i),0})
}
// 我们以为输出会是 0 1 2 3 4 5
// 但是实际却是这样 5 5 5 5 5 5
在上面的例子中,var
声明的变量进行迭代,每次+1,都是同一个变量,这可能是正确的逻辑,因为我们确实是在循环 i
但是我们换成 let
:
for(let i =0;i < 5;++i){
setTimeout(()=>{console.log(i),0})
}
// 这样输出的却是 : 0 1 2 3 4 5
换成 let
后,我们得到的是这样,是因为在使用 let
声明迭代变量时,JavaScript引擎会为每一个迭代循环声明一个新的迭代变量。这样每次循环体内引用的变量都是不同的变量实例。
这其实很好理解,我们要理解 let
的作用域,for循环其实可以看做是每次循环体都是一个块作用域(如下),这样每次循环体里面用 let
声明的变量当然也就是全新的变量,是不同的实例,所以能正确输出 0~5
// 这样写是错误的,只是方便理解
// for循环其实也就是重复一个循环体,即如下的操作,这样我们就理解了,为什么使用《let》,每次循环都是一次全新的迭代变量,而且也不会污染块作用域外的地方
{
let i =0
console.log(i);
}
{
let i = 1
console.log(i);
}
3.3.3const声明
- 块作用域
- 不允许重复声明
- 没有声明提升
- 不可修改
const
的行为和 let
基本相同,唯一一个区别就是它声明变量的同时,必须初始化变量,并且不能修改变量的值
const age =26
age = 11 // 不可修改,报错
// const 也不允许重复声明
const name = 'Matt'
const name = 'Lisa' // Identifier 'name' has already been declared
// const 作用域也是块
const name = 'Matt'
if(true){
const name = 'Lisa'
}
console.log( name); // Matt
const
声明的限制只适用于它指向的变量的引用。意思是,如果 const
变量引用的是一个对象,那么修改对象内部属性是可以的。
const name = {}
name.person = 'Lisa'
console.log(name);
// { person: 'Lisa' }
3.3…4声明的选择和实践
ES6增加了 let
和 const
从客观上是为这门语言更精确地说明作用域和语义提供了更好的的支持。行为怪异的 var
所造成的各种问题得到解决。
1.不适用var
有了 let
和 const
,大多数开发者发现不需要 var
了。限制自己只使用这两个有助于提升代码质量,因为变量有了限制,作用域,声明位置,不能改变等等,这些有利于程序的运行稳定和调试。
2.const优先,let次之
const
的优势在于不能改变变量,可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现问题。
只有在提前知道变量需要改变,我们才用 let