这是我参与更文挑战的第1天,活动详情查看: 更文挑战
变量声明
有var、let、const三个关键字,可以声明变量。
var
变量的声明会自动提升到作用域顶部,也就说预编译时(通篇扫描一遍),把变量声明的语句提到最前面执行。
有函数作用域(局部作用域)和全局作用域。在同一个作用域中var多次声明同一个变量,词法分析中与声明一次没有区别。
globalVar = "隐式声明一个变量";
复制代码
如果globalVar从未出现过,那这里就是隐式声明变量后立即赋值。
隐式声明的变量会自动变成全局变量,是window的属性。
通过var定义的全局变量和函数都会成为window对象的属性和方法。
let
声明
不能重复声明同一个变量,也不能在声明之前调用它。js引擎会记录用于变量声明的标识符及其所在的块级作用域。
混用var、let声明同一个标识符也会报错,var、let这两个关键字并不是不同类型的变量,只是指出变量所在的作用域。
不会挂载到全局对象(window)上。
声明范围是块级作用域,被{ }
所包含的范围。
块级作用域是函数作用域的子集。
if(true) {
let age = 18;
}
复制代码
let声明的变量只存活在{}
中。
外部无法访问age
变量。
所以在类似try/catch、if/else语句中,let只存在块级作用域中。
for循环特殊处理
let声明的变量在for循环中进行特殊处理。
for(let i = 0; i < 10; i++) {
setTimeOut(() => console.log(i), 0);
}
// 0 1 2 3 4 5 6 7 8 9
复制代码
因为let变量和块级作用域是绑定在一起的,块级作用域结束,let变量也就消失了。
每次进入循环体,都会开启一个新的作用域,并且将迭代变量绑定到该作用域(每次循环,使用的是一个全新的迭代变量)。
所以每一次箭头函数被setTimeout
保存到外部时,连接的块级作用域都是不一样的,里面都有一个全新的i
。
迭代变量的作用域仅限于for循环块内部,在循环结束后会销毁。
for(var i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 0);
}
// 10 10 10 10 10 10 10 10 10 10
复制代码
var声明的变量,会渗透到循环体外部。不会创建一个全新的i
。
所以函数每次保存的都是同一个作用域,调用的都是同一个i
。
暂时性死区
name = "111"; // 会报错
let name = "Herb";
复制代码
个人理解:预编译时,let声明语句let name
也会提升,只是把这个变量name
存放在暂时性死区中,不会被赋予初始值。等到执行时,如果在声明之前调用name
就会报错,直到执行到let name
时,把name
从暂时性死区中释放出来。
typeof name;
let name = "Herb";
// Cannot access 'name' before initialization
复制代码
所以也无法在name定义之前调用typeof。
const
const声明变量时,必须初始化。
尝试修改,会导致运行时错误。
块级作用域中。
const变量是引用数据类型(object、array、function等)时,不能再赋值为其他引用值,但可以对里面的内容进行更改。
如果希望对象里面的内容也不能更改的话,可以使用Object.freeze()
,虽然赋值时不会报错,但会静默失败。
const obj = { name: "Herb" };
obj = Object.freeze(obj);
obj.name = "hhhhh";
console.log(obj);
// { name: "Herb" }
复制代码
由于const声明暗示变量的值是单一类型且不可修改的,JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量的查找。谷歌的V8引擎就执行这种优化。
在开发中应该尽可能地多使用const声明。
发现需要更改时再修改声明。