一、声明全局变量
a = 1
一定是在声明一个全局变量吗?
function fn() {
a = 1
}
fn()
console.log(a) // 1
上面的代码的确是在声明一个全局变量a
,然后我们再来看看下面这种情况
var a
function fn() {
var a
function fn2() {
a = 1
}
}
fn()
console.log(a) // undefined
在这种情况下很明显就不是在声明全局变量a
,a = 1
会向上寻找离它最近的一个声明,即函数fn
里的var a
,并没有到达最顶层,因此上面的代码实际上等价于下面这种写法
var a
function fn() {
var a = 1
function fn2() {
}
}
fn()
console.log(a) // undefined
总结一下就是,如果在当前作用域内存在变量a
的声明,那么a = 1
就会直接去利用这个声明;如果在当前作用域内不存在对a
的声明,那么就会按照“作用域链”一级一级地向上寻找所有父对象的变量。只有当找完了所有父对象的变量之后都没有发现有对变量a
的声明,这时候才是在声明一个全局变量a
(window.a
)
二、ES5 作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面代码的原意是,if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。但是,函数f
执行后,输出结果为undefined
,原因在于变量提升,导致内层的tmp
变量覆盖了外层的tmp
变量。
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。(可以使用立即执行函数来解决这个问题)
由于以上var
的种种缺陷,因此就有了下文的let
和const
以及“块级作用域”
三、let 命令
1. 块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函数有两个代码块,都声明了变量n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var
定义变量n
,最后输出的值才是 10。
2. 暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
3. 不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
image.png
四、const 命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。而且必须在声明的时候立刻进行赋值。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const foo;
// SyntaxError: Missing initializer in const declaration
五、面试题
- 下面代码的打印结果一定是1吗?
var a = 1
function fn() {
console.log(a)
}
/*这里有一行代码*/
fn()
如果加的一行代码是a = 2
呢?
var a = 1
function fn() {
console.log(a) // 2
}
a = 2
fn()
- 下面的代码为什么打印出
i
的值总是6?
for (var i=0; i<6; i++){
}
console.log(i) // 6
因为它等价于下面这种写法
var i
for (i=0; i<6; i++){
}
console.log(i) // 6
- 下面的代码一定会打印出0~5吗?
for (var i=0; i<6; i++){
function fn(){
console.log(i) // ?
}
/*这里有一行代码*/
}
console.log(i) // 6
如果是直接调用fn
,会打印出1~5,我的理解是每进入for
循环中一次,就执行一次fn
,因此会打印出对应的i
的值
for (var i=0; i<6; i++){
function fn(){
console.log(i) // 0,1,2,3,4,5
}
fn()
}
console.log(i) // 6
但如果加的代码是x.onclick = fn
,则直接就打印出6,我的理解是当点击这个按钮的时候,i
已经累加到6了
/*<button id="x">x</button>*/
for (var i=0; i<6; i++){
function fn(){
console.log(i) // 6
}
x.onclick = fn
}
console.log(i) // 6
- 一道面试题
<ul>
<li>导航1</li>
<li>导航2</li>
<li>导航3</li>
<li>导航4</li>
<li>导航5</li>
<li>导航6</li>
</ul>
var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
liTags[i].onclick = function() {
console.log(i) // 为什么打印出i的值总是6?
}
}
这就是因为用来计数的循环变量i
泄露为全局变量
有以下三种解法:
解法一:用ES5的立即执行函数来创造一个局部作用域
var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
(function(j) {
liTags[j].onclick = function() {
console.log(j) // 0,1,2,3,4,5
}
})(i)
}
解法二:在for
循环内部用另一个变量来接收i
的值
var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
let j = i // 注意这里依然只能用let,不能用var
liTags[j].onclick = function() {
console.log(j) // 0,1,2,3,4,5
}
}
解法三:直接在for
循环的条件中使用let
来声明变量
var liTags = document.querySelectorAll('li')
for (let i = 0; i < liTags.length; i++) {
liTags[i].onclick = function() {
console.log(i) // 0,1,2,3,4,5
}
}
注意:上面解法三的这种写法,i
的作用域只在for
后面的圆括号中,而下面的花括号又是另外一个作用域,可以理解为下面这样
在这个过程中一共产生了7个i
,块里面6个,圆括号里面1个
网友评论