关于的var、let、const的几个题目,你能答对几个?

一、Const

const声明一个只读的常量。

一旦声明,常量的值就不能改变,改变常量的值会报错。

因此const声明变量,必须立即初始化。只声明不赋值,就会报错。

除了上面三条性质,const 变量提升和作用域和let一样。

  • 1.1

let user = 'Lucy';
const USER_INFO = user;
console.log(user)
console.log(USER_INFO)

user = 'Marry';
console.log(user)
console.log(USER_INFO)
结果:

Lucy
Lucy
Marry
Lucy

  • 1.2

let user = {name:'Lucy',age:17};
const USER_INFO = user;
console.log(user)
console.log(USER_INFO)

user.age = 32;
console.log(user)
console.log(USER_INFO)
结果:

{name:‘Lucy’,age:17}
{name:‘Lucy’,age:17}
{name:‘Lucy’,age:32}
{name:‘Lucy’,age:32}

解析:
对象存放在堆内存中,栈中只存了对象在堆内存中的地址,可以把它称为指针。
常量 USER_INFO 始终指向 user 对象在堆内存中的地址,这个指针是不变的。但是如果地址中的值变了,USER_INFO的值也就跟着变了。

二、结合window对象

在任意位置,一个变量未经声明就赋值,都可以看做是在window对象上寻找这个变量。

2.1

let a = 'aaa';
console.log(a);  
console.log(window.a);  
var b = 'bbb';
console.log(b);  
console.log(window.b);  
c = 'ccc';
console.log(c);  
console.log(window.c);  
结果:

aaa
undefined
bbb
bbb
ccc
ccc

解析:

window.a输出undefined:
使用 let 声明的变量, 不会作为 window对象 的属性, 单纯的就是声明了一个块级变量。


window.b输出bbb:
所有在脚本中(没有在函数的范围内)通过var定义的变量,都挂在window对象上。
换句话说:var 声明的全局变量,默认会添加到 window 对象下,作为 window 对象的一个属性值存在。


window.c输出ccc:
在任意位置,一个变量未经声明就赋值,都可以看做是在window对象上寻找这个属性。

2.2

var a = 1; 
b = 2; 
delete a; //无法删除  
delete b;// 删除

三、作用域

let 所声明的变量,只在 let 命令所在的代码块内有效。

let 不允许在相同作用域内,重复声明同一个变量。

var 所声明的变量,在函数内部声明的是局部变量,在函数外部的是全局变量。

-局部变量全局变量
声明位置函数内部函数外部
生存期从被声明的时间开始,函数运行后被删除从被声明的时间开始,页面关闭后删除
作用域只能在函数内部访问页面上所有的脚本和函数都能访问

3.1

if(1){
   a = 1;
   console.log(a)
}
console.log(a)

if(1){
  var b = 2;
  console.log(b)
}
console.log(b)

if(1){
  let c = 3;
  console.log(c)
}
console.log(c)
结果:

1
1

2
2

3
c is not defined

3.2

function a(){
  aa = 'aaaa';
}
a();
console.log(aa);

function b(){
  var bb = 'bbbb';
}
b();
console.log(bb);

function c(){
  let cc = 'cccc';
}
c();
console.log(cc);
结果:

aaaa
bb is not defined
cc is not defined

3.3

for (var i = 0; i < 3; i++) {
   console.log(i)
}
console.log(i)

for (let j = 0; j < 3; j++) {
   console.log(j)
}
console.log(j)
结果:

0 1 2
3

0 1 2
j is not defined

四、结合for循环

for循环有一个特别之处:设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

4.1

for (let i = 0; i < 3; i++) {
  let i = 'aaa';
  console.log(i);
}

for (var j = 0; j < 3; j++) {
    var j = 'bbb';
    console.log(j);
}
结果:

aaa
aaa
aaa

bbb

解析:
首先明确一点: let不允许在相同作用域内,重复声明同一个变量。此处 for循环内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域。
两个var声明的 j,都指向一个全局的变量 j 。执行var j =‘bbb’,之后,不满足条件,循环就结束了。

4.2

for (var i = 0; i < 3; i++) {
    let i = 'bbb';
    console.log(i);
}

for (let j = 0; j < 3; j++) {
    var j = 'aaa';
    console.log(j);
}
结果:

bbb
bbb
bbb
Identifier ‘j’ has already been declared.(已声明标识符“i”)

解析:
在第二个for循环内用var声明 j 变量,变量 j 是全局变量,作用域是整个代码块。for循环内部 let 声明的变量 i ,作用域是 for循环内部。j 的作用域涵盖了 i 的作用域,而 let 不允许在相同作用域内,重复声明同一个变量。

4.3

var a = [];
for (let i = 0; i < 10; i++) {
    console.log(i);
    a[i] = function () {
        console.log(i);//执行此代码时,for循环已经执行完毕。
    };
}
a[6]();
结果:

0 1 2 3 4 5 6 7 8 9
6

解析:
变量 i 是 let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。

4.4

var a = [];
for (var i = 0; i < 10; i++) {
 console.log(i);
 a[i] = function () {
   console.log(i);  //执行此代码时,for循环已经执行完毕,i 的值是10。
 };
}
a[6]();
结果:

0 1 2 3 4 5 6 7 8 9
10

解析:
变量 i 是var命令声明的,在全局范围内都有效,所以全局只有一个变量 i 。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的 console.log(i) ,里面的 i 指向的就是全局的 i 。也就是说,所有数组a的成员里面的 i ,指向的都是同一个i,导致运行时输出的是最后一轮的 i 的值,也就是 10。

4.5

var a = 1;
function foo(){
   if(false){
       var a = 2;
   }
   console.log(a)
}
foo();
结果:

undefined

解析:

第二次声明var a = 2;变量a的作用域是函数foo内部。因为var声明变量,存在变量提升,所以输出undefined,而不是1。

五、变量提升

JavaScript 中,函数及变量(var)的声明都将被提升到函数的最顶部。

5.1

console.log(a)
var a = 1;

console.log(b)
let b = 2;

console.log(c)
const c = 2;
结果:

undefined
Cannot access ‘a’ before initialization
Cannot access ‘b’ before initialization

解析:

var声明变量的提升的过程:

var a;//声明
console.log(a)
a = 1;//初始化

5.2 for循环中var变量的提升

let a = 'aaa';
console.log(a);
for (var j = 0; j < 3; j++) {
    console.log(a);
    var a = 'bbb';
    console.log(j);
}
结果:

Uncaught SyntaxError: Identifier ‘a’ has already been declared

解析:

let不允许在相同作用域内重复声明同一个变量。可见for循环中var a提升到for外面了。
if 也是同样的道理:

let a = 'aaa'
console.log(a);
if (true) {
    console.log(a);
    var a = 'bbb';
    console.log(a);
}

结果仍是:Uncaught SyntaxError: Identifier ‘a’ has already been declared

假如把第一行注释掉

console.log(a);
for (var j = 0; j < 3; j++) {
    console.log(a);
    var a = 'bbb';
    console.log(j);
}

undefined
undefined
0
bbb
1
bbb
2

5.3

function fun(){
  console.log(a)    //[1]undefined=>此处输出的a是下面的a变量提升之后的a。
  var a = 99;       //fun函数的变量a
  innerFun();       //调用innerFun函数,函数定义在后面,但是函数声明会提升到作用域顶部。
  console.log(a);   //=>[4]99=>fun函数的变量a
  function innerFun(){  //funA函数声明
    console.log(a); //=>[2]undefined=>此处输出的a是下面的a变量提升之后的a。
    var a = 10;     //=>innerFun函数的变量a
    console.log(a); //=>[3]10>innerFun函数的变量a
  }
}
fun();
结果:

undefined
undefined
10
99

六、暂时性死区

ES6规定,如果区块中存在let和const命令,这个区块对let和const命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。这在语法上,称为“暂时性死区”(Temporal Tead Zone,简称TDZ)。

暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

6.1

var a = 123;
if (true) {
	// 这里会产生 TDZ for a
   a = 'abc';
   let a;//if区块内, 在let命令声明变量a之前,都属于变量a的“死区”。这里结束 TDZ for a
}
结果:

Cannot access ‘a’ before initialization(初始化前无法访问“a”)

6.2

if (true) {
  // TDZ开始
  tmp = 'abc';  
  console.log(tmp);  //在let声明变量tmp之前就使用该变量,会报错。

  let tmp; // TDZ结束
  console.log(tmp);  

  tmp = 123;
  console.log(tmp);  
}
结果:

Cannot access ‘tmp’ before initialization(初始化前无法访问“tmp”)
遇到错误,下面的两个输出都不执行了。

七、结合setTimeout、闭包

调用setTimeout时,把函数参数,放到事件队列中,等主程序运行完,再调用。即便是时间值为0,它也会等主程序执行完再执行,如果主程序队列为空,就会直接调用。

闭包:对函数类型的值进行传递时,保留对它被声明的位置所处的作用域的引用。

7.1

for(var i = 0;i < 10; i++){
  console.log(i);
  setTimeout(function () {//同步注册回调函数到异步的宏任务队列
    console.log(i);//执行此代码时,for循环已经执行完毕。
  },0);
}
结果:

0 1 2 3 4 5 6 7 8 9
10 10 10 10 10 10 10 10 10 10

解析:

注意setTimeout函数里面有个匿名函数哦,在执行这个匿名函数中的console.log(i)的时候,因为闭包(保留了对声明i的作用域的引用),var作用域是全局的,现在for执行完了,i的值是10

7.2

for(let i = 0;i < 10; i++){
  console.log(i);
  setTimeout(function () {//同步注册回调函数到异步的宏任务队列
    console.log(i);//执行此代码时,for循环已经执行完毕。
  },0);
}
结果:

0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

7.3

function foo(){
  for(var i = 0;i < 10; i++){
    console.log(i);  
    setTimeout(function (i) { 
      console.log(i);
    },1000);
  }
  for(var i = 0;i < 3; i++){
    setTimeout(function (i) { 
      console.log(i);
    },1000);
    console.log(i);
  }
}
foo()
结果:

0 1 2 3 4 5 6 7 8 9
0 1 2
3 3 3 3 3 3 3 3 3 3
3 3 3

解析:

setTimeout的意思是传递一个函数,延迟一段时间把该函数添加到队列中,并不是立即执行。
如下图所说,异步任务会把回调函数加到任务队列,等主进程的空闲了,才被读取到主进程中执行。
也就是说所有传递给setTimeout的回调方法都会在整个环境下的所有代码运行完毕之后执行。(这就是为什么先输出for里面的,再输出setTimeout函数里面的)
又如下图所说,任务队列是先进先出的,所以先输出10个3,再输出3个3
在这里插入图片描述

参考文章:浏览器的事件循环机制

7.4

for (var i = 1; i <= 5; i++) {
    (function() {
        setTimeout( function timer() {
            console.log(i);
        },i*1000 );
    })();
}
结果:

6 6 6 6 6

解析:

立即执行也只是立即执行了setTimeout函数,这个函数的作用还是传递一个函数(timer),延迟一段时间把该函数添加到任务队列中。加不加立即执行没影响。

7.5

for (var i = 1; i <= 5; i++) {
    (function() {
        var j = i;
        setTimeout( function timer() {
            console.log(j);
        },i*1000 ); 
    })();
}
结果:

1 2 3 4 5

解析:

这里我们用了一个立即执行函数,那么就创造了新的函数作用域,并用j捕获了每次循环时的i,这样在运行到console.log(j)的时候显示的就是每次循环时的i值。如果你懂上面的3.11,你就能明白这个。

7.6

for (var i = 1; i <= 5; i++) {
    let j = i;
    setTimeout(function timer() {
        console.log(j);
    },j*1000);
}
结果:

1 2 3 4 5

解析:

这里就是用let 声明变量j,函数timer在let 的块级作用域中。

参考文章:ECMAScript6入门

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页