开辟一个内存空间,给这个内存空间贴一个标签,标签即变量名,通过变量名,可以访问到该变量对应开辟的内存空间。
let
const
var
类似 let
,老的JS中声明变量的唯一方式。
let,const 和 var的差异(重点)
let const
有块级作用域,而var
没有。什么是块? 简单理解即 >
{}
包裹的代码块。 ```javascript if(true){ var test = true } console.log(test) // 正常打印true
if(true) { let test = true } console.log(test) // 报错:test is not defined
- **var**相比let和const,允许**重复声明**。
- **var**声明的变量,可以再其声明语句之前被使用(**变量提升,hosting,但赋值停留原地**)。而**let, const具有暂时性死区**,会报错。
> MDN的定义,在变量初始化前访问该变量会导致引用错误,该变量处在一个自块顶部到初始化处理的暂存死区中。
```javascript
console.log(a)
var a = 1;
// 经过hosting相当于
var a;
console.log(a) // 打印undefined
a = 1 // 赋值停留原地
// 甚至这样也会提升,因为var无视代码块,会提升到当前函数作用域顶部
function sayHi() {
a = 'hello'
if(false) { // 这个if代码块无效,var直接提升到sayHi函数最顶部
var a;
}
console.log(a) // 打印hello
}
暂时性死区和此法作用域的例子
function foo() {
var a = 1;
if(a) {
// 这里的let a劫持了if块,而a + 2中的a访问的是if块内的a,而不是外层的var a,
// 在还未初始化时就访问,因此报错。
let a = a + 2
}
}
foo() // 报错,Cannot access 'a' before initialization。a初始化前不能访问
demo(经典)
经常会遇到的一道面试题,通常用于考察闭包的使用。如下展示借助块级作用域
for(var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
// 打印10个10。
// 原因在于 var 的没有块级作用域,1秒后,i值变为了10,10个定时器此时访问的是同一个i,因此打印10。
// 使用let,劫持块级作用域
for(let i = 0; i < 10; i ++) {// 同上} // 顺序打印0 到 9
// 注意如果你把let i = 0 提到外面,则不行,因为没有劫持到每个循环的块。
let i = 0;
for(; i < 10; i ++) {// 同上} // 打印10个10。
IIFE
ES6前的时代,只有函数作用域和全局作用域,因此借助函数作用域模仿块级作用域,这种方式叫立即调用函数。
function() {}() //报错,函数声明需要一个函数名
function name() {}() // 报错,语法错误,JS不允许立即调用函数
// 所以通过()将函数声明包裹,告诉JS,这是一个函数表达式可以直接调用。
(function() {
// 函数体
})()
// 所以上面的demo也可以借助IIFE来完成。
for(var i = 0; i ++; i < 10) {
setTimeout(
(function d(i) {console.log(i)})(i), // 每个IIFE会访问创建时传入给自己的变量i,而不会访问到最外层的i
1000
)
}
变量命名
驼峰规则
let a = 'isMyBook'
除首字母外的单词的首字母大写。
常量命名
通常固定的,不会变值称为常量。通常是大写命名。 const PI = '3.14'
。当然通过计算出来的值,我们还是采用驼峰命名即可 const a = 1 + 2
,因为并不是在计算机运算前就知道的,而 π
这个是世界公认的无需计算的值。