在 ES6 呈现之前,js 定义变量的关键字只有 var
。在 ES6 之后才新增了 let
和 const
。
新的变量申明关键字呈现,也带来了一些新的变动。Enjoy it :)
var
在 ES6 呈现以前,JavaScript 定义变量的关键字只有 var
申明作用域
应用 var
操作符定义的变量,会成为蕴含它的函数的局部变量,这意味着该变量会在函数退出的时候被销毁。
function foo() {
var name = 'foo';
console.log(name); // foo
}
foo();
console.log(name); // Error
全局变量
如果在定义变量时,没有应用 var
关键字,这时候,所申明的变量将会变成 全局变量。
function foo() {
// without keyword 'var'
name = 'foo';
console.log(name); // foo
}
foo();
console.log(name); // foo
上述代码将会变成下述代码,变量 name
的申明被提到了全局顶部。尽管能够通过这种形式来申明一个全局变量,这种形式 强烈不举荐,这会使得全局变量被净化,也会使得代码变得难以保护。
var name;
function foo() {name = 'foo';}
在严格模式下,如果未声明间接进行赋值操作,会间接抛出
ReferenceError
异样。
晋升 Hoisting
在开始这大节之前,先思考一下下述代码会打印什么内容呢? 会间接报错抛出 Error 么? 还是失常打印出一个 foo? 或者是 undefined?
function foo() {console.log(name);
var name = 'foo';
}
在解答之前,首先明确,申明和赋值其实是两个动作。当咱们用 var name = 'foo'
, 其实这外面蕴含着两个动作:申明和赋值。首先是申明 var name
, 申明了一个 name
的变量,其次是赋值,将 name
赋值为 foo
。
当初开始答复,上述代码不会报错。当应用 var
进行变量申明时,这时变量的申明会主动晋升到函数作用域的顶部,而后执行打印,这时候,name
还是一个只申明为赋值的变量,所以这时候打印 name
会打印出 undefined
, 之后才执行变量的赋值操作。听起来云里雾里,也就是,下面的代码理论执行的程序如下:
function foo() {
var name;
console.log(name); // undefined
name = 'foo';
}
所谓的变量晋升指的就是将所有变量申明晋升到函数作用域顶部,如果申明屡次,只有在同一个作用域内,反复申明也没有关系。然而留神,赋值依旧会停留在原地。晋升只针对申明而言,赋值仍旧是依照原来的程序进行赋值操作。
function foo() {console.log(name); // undefined
var name = 'foo';
var name = 'bar';
console.log(name); // bar
var name = 'ba1r';
console.log(name); // ba1r
var name = 'bar2';
}
let & const
let
和 const
是自 ES6 公布以来新增的变量定义关键字,和 var
最大的差别是,新增的 let
和 const
是块级作用域,而非是函数作用域。
块级作用域
let
和 const
是块级作用域。JavaScript 引擎会记录用于变量申明的标识符及其块作用域。块级作用域顾名思义,它的作用域仅限于该块的外部。
if (true) {
let age = 30;
console.log(age); // 30
}
console.log(age); // ReferenceError: age is not defined
Quick question: 上面的执行后果是什么呢?能够本人入手试试看。
{let a = 42;}
console.log(a); // ?
No hoisting & TDZ 暂时性死区
经由 var
申明的变量是能够反复进行申明,得益于 hoisting, 所有的变量申明是会晋升到作用域的顶部,所以即便反复申明也是没有问题的。和 var
不同,let
和 const
的变量申明是不会在作用域中晋升的。在解析代码的时候,JavaScript 引擎会留神到块作用域中,会存在一个 let
的申明,在执行到对应语句之前,不能用任何形式来援用未声明的变量。由此引出了一个 暂时性死区 temporal dead zone 的概念。
TDZ (temporal dead zone)
TDZ 是 ES6 标准中定义的一个新的概念,指的是 因为代码中的变量尚未进行初始化而无奈被援用的状况。 在执行到对应语句之前,无奈用任何形式来援用尚未申明的变量。因而,在块级作用域执行到对应 let
的申明之前,称之为暂时性死区 (temporal dead zone, TDZ)。如下文的代码,在执行到理论申明 age
之前,如果援用到 age
这个变量,会抛出 ReferenceError
。
if (true) {console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 30;
}
同样的,在函数参数再传参过程中也有所谓的“死区”,举个例子🌰
function bar(x = y, y = 2) {...}
这个时候,y
尚未被申明,所以这样的参数申明会报错。而,上面这种状况,调换了 x
和 y
的程序,在拜访 y
时,刚好跨出了 x
的 TDZ,所以 y
的这种拜访形式则没有问题。
function bar(x = 2, y = x) {...}
因为
const
变量要求申明时立即进行初始化,则不存在 TDZ 的问题。
全局申明
var
在全局作用域中申明的变量会成为 window
对象的属性;而 let
在全局作用域申明的变量,不会成为 window 对象的属性。
然而雷同的是,在全局作用域产生的申明,会在生命周期内续存。因而,let
必须保障不会反复申明同一变量。
typeof & try…catch 的陷阱
划重点!let
和 const
是块级作用域。 在下述的例子中,let 的申明都会被限度在对应的块级作用域之中,所以无奈实现对应的条件申明的成果。
if (typeof name === 'undefined') {let name; // name 只存在于 if{...} 之中
}
try {console.log(age);
} catch {
let age = 25;
console.log(age); // age 只存在于 catch 之中
}
循环利器
咱们常常会看到一道面试题,请问到底会打印出什么内容。
for (var i = 0; i < 5; i++) {setTimeout(() => {console.log(i); // 5, 5, 5, 5, 5
}, 200);
}
这是因为,应用了定时器,打印被提早执行。而 var
申明的变量是函数作用域,i
曾经走到了 5。当定时器理论 log 的时候,其实循环曾经执行结束了,所以打印进去了 5 个 5。
如果咱们更换成了 let
来申明迭代变量,则可能打印出 0,1,2,3,4。这是因为,改用了 let
之后,每次迭代作用域被限度在了 for 循环块外部。JavaScript 引擎会为每个迭代循环申明一个新的迭代变量,因而,每个定时器援用的都是不同的变量实例,也就是循环执行过程中每个迭代变量的值。
for (let i = 0; i < 5; i++) {setTimeout(() => {console.log(i); // 0, 1, 2, 3, 4
}, 200);
}
What about const
后面常常把 let
和 const
并列起来讲。然而,let
和 const
之间也是有些许的差别。次要的差别点能够从 const
的这个单词看进去,const
means constant,意为常量,应用const
申明的变量存在不可变性,变成一个“常量”。
let
和 const
的行为最大的差别,在于 const
的申明和赋值操作必须同时,尝试批改 const
申明的变量会导致运行时出错。
然而,const
申明的限度只实用于它所指向的变量的援用。换言之,如果 const
变量援用了一个对象,那么批改这个对象外部的属性值并不违反 const
的限度。
const a = 2;
a = 3; // TypeError: Assignment to constant variable.
const obj = {a: 2}
obj.a = 4; // It's ok
Summary
ES6 所减少的 let
和 const
能够帮忙 JavaScript 社区解决长久以来 var
的诡异执行形式带来的一系列懊恼。let
和 const
这两个块级作用域变量申明关键字,能够让开发过程中,对于变量申明的作用域和语义提供了更加准确的反对,有助于开发过程的代码了解和代码品质的晋升。
在理论开发过程中,更加提倡应用 let
和 const
,这能够让变量的申明、应用变得更加可预测。同时,只有当开发者预期会发生变化的变量时,才举荐应用 let
,这会更加有助于问题的发现和解决。简略总结一下,var
、let
和 const
之间的异同点。
申明形式 | 作用域 | 晋升 hoisting | 初始化 |
---|---|---|---|
var |
函数作用域 | 应用 var 会将变量申明晋升到函数作用域顶部 |
能够申明之后再进行初始化 |
let |
块级作用域 | 不会进行晋升 | 能够申明之后再进行初始化 |
const |
块级作用域 | 不会进行晋升 | 必须同时进行初始化 |
Reference
- JavaScript 高级程序设计(第 4 版)—— chap 3
- 你不晓得的 JavaScript 中卷 —— chap 5