2021前端JavaScript面试题及答案

准备面试的你,必看这6道JavaScript面试题,摸透JavaScript的基础

一、执行以下代码,测试浏览器会输出什么

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    /* 执行以下代码,观察输出结果 */
    var a = 10  // 全局作用域,全局变量 a = 10
    function foo() {  

      // var a

      console.log(a) // a = undefined

      
      var a = 20  // a的实际赋值在这
      console.log(a)  // a = 20
    }
    foo()

    /* 解释如下 */
    /* 
      使用var关键字声明的变量在JS中会被提升,并在内存中开辟空间,由于没有赋值,无法定义数值类型
        所以默认为 undefined ;var声明的变量,真正的数值初始化是在你确定赋值的位置

      var声明的变量是函数作用域的
      
    */
  </script>
</body>
</html>

在这里插入图片描述
如果将上述代码的var换成let或者const会发生什么效果?
在这里插入图片描述

二、newArray中有哪些元素

<script>
    /* newArray中有哪些元素 */
    var array = []
    for (var i = 0; i < 3; i++) {
      array.push(() => i)
    }
    var newArray = array.map(el => el())
    console.log(newArray)
  </script>

这个问题是循环结构带来的一种块级作用域的午去,在循环内部使用var声明的变量,就是单个声明的变量绑定(单个存储空间)。在循环过程中,这个var变量会随着循环变化。但是循环中执行的push()方法,最后实际上是push了i最终的循环结束的3个值。所以最后push进去的全都是3。

在这里插入图片描述
如果想记录每一次循环的值,可以用let声明一个具有块级作用域的变量,这样可以为每一个循环迭代创建一个新的绑定。
在这里插入图片描述

三、如果我们在浏览器中控制台中运行foo函数,是否会导致堆栈溢出错误

在这里插入图片描述
JavaScript的并发模式基于我们通常所说的事件循环,浏览器是提供运行时环境来给我们执行JS代码的。浏览器的主要组成包括调用堆栈事件循环任务队列WEB API。像一些常用的定时器setTimeout,setInterval这些全局函数就不是JavaScript的一部分,而是WEB API给我们提供的。
在这里插入图片描述
JS调用栈是先进后出的,引擎每次从堆栈中取出一个函数,然后从上到下依次运行代码。每当它遇到一些异步代码,它就把它交给web APi(箭头1).因此,每当事件被触发时,callback都会被发送到任务队列(箭头2)。

事件循环不断地监视任务队列,并按他们排队的顺序一次处理一个回调。每当调用栈为空时,事件循环获取回调并将其放入堆栈(箭头3)中处理。注意:如果调用堆栈不为空,则事件循环不会将任何回调推入堆栈。

那么上面这道题的实现步骤如下:
①调用foo()会将foo函数放入调用堆栈(callback)
②在处理内部代码时,JS引擎遇到了setTimeout
③然后将foo回调函数传递给web API(箭头1)并从函数返回,调用堆栈再次为空
④计时器被设置为0,因此foo将被发送到任务队列(箭头2)
⑤由于调用堆栈时空的,事件循环将选择foo回调,并将其推入调用堆栈进行处理
⑥进程再次重复,堆栈不会溢出

四、如果在控制台中运行以下函数,页面选项卡是否会有响应

在这里插入图片描述
很多时候,我们都会认为循环事件图中只有一个任务列表,实际上是可以有多个任务列表的。由浏览器选择其中一个队列并在该队列进行处理回调。

从底层来看,JavaScript中是可以有宏任务和微任务的,比如setTimeout回调就是一个宏任务,Promise回调就是微任务。

区别如下:
主要区别在于他们的执行方式。宏任务在单个循环周期中一次一个地堆入堆栈,但是微任务队列总是在执行后返回到事件之前清空。所以,如果你以处理条目的速度像这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面。

这一问题,每次我们去调用foo时,都会在微任务队列加上另一个foo的回调,因此事件循环没办法继续处理其他的事件了,(比如说滚动、点击事件等等),直到该队列完全清空为止。因此,不会执行渲染,会被阻止。

五、运行以下代码片段,控制台上会打印什么?

在这里插入图片描述
在运行for in循环时,我们首先要理解for in循环,它会遍历本身的可枚举属性对象从原来的原型继承来的属性。可枚举属性是可以在for-in循环期间可以访问的属性。在这里插入图片描述

六、xGetter会打印什么值?

在这里插入图片描述
首先我们可以看到var x是一个全局变量,在不是严格模式下,这个x实际直接是window对象的属性了。这段代码最重要的是理解this的指向问题了----->this始终指向的是调用方法的对象的。所以,在 foo,getX() 的情况下,this指向的是foo对象返回的就是foo中的属性x,就是20。但是在调用==xGetter()==的情况下,他是直接调用了foo的getX(),但是其中的this指向的是在 xGetter的作用域,就是指向window对象的x,也就是全局变量的x,所以值就为10。
在这里插入图片描述

  • 17
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值