几道常见的JavaScript面试题

问题1:下面这段代码,浏览器控制台上会打印什么?

在这里插入图片描述
答案:

使用var关键字声明的变量在JavaScript中会被提升,并在内存中开辟空间,
由于没有赋值,无法定义数值类型,所以分配默认值undefined。
var声明的变量,真正的数值初始化,是发生在你确定赋值的位置。
var声明的变量是函数作用域的,也就是我们需要区分局部变量和全局变量,
而let和const是块作用域的。所以我们这道题的运行过程是这样的:

在这里插入图片描述
所以答案是:undefined

问题2:如果我们使用 let 或 const 代替 var,输出是否相同

在这里插入图片描述
答案:

let和const声明可以让变量在其作用域上受限于它所在的块、语句或表达式中。
和var不同的地方在于,这两个声明的变量,不会被提升。并且我们会有一个称为暂时死区。
如果访问暂时死区中的变量的话,就会报ReferenceError,
因为他们的的作用域是在他们声明的位置的,不会有提升。所以必须在执行到声明的位置才能访问。

在这里插入图片描述
答案:ReferenceError: a is not defined

问题3:“newArray”中有哪些元素?

在这里插入图片描述
答案:

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

在这里插入图片描述

如果想记录每一次循环的值下来,可以使用let声明一个具有块级作用域的变量,这样为每个循环迭代创建一个新的绑定。

在这里插入图片描述
还有解决这个问题的另外一种解决方案就是使用闭包就好了。

在这里插入图片描述
答案:3,3,3

问题4:如果我们在浏览器控制台中运行’foo’函数,是否会导致堆栈溢出错误?

在这里插入图片描述
答案:

JavaScript的并发模式基于我们常说的”事件循环“。
浏览器是提供运行时环境来给我们执行JS代码的。浏览器的主要组成包括有调用堆栈,事件循环,任务队列和WEB API。
常用的定时器setTimeout,setInterval这些全局函数就不是JavaScript的一部分,而是WEB API给我们提供的。

在这里插入图片描述

JS调用栈是后进先出(LIFO)的。引擎每次从堆栈中取出一个函数,然后从上到下依次运行代码。
每当它遇到一些异步代码,如setTimeout,它就把它交给Web API(箭头1)。
每当事件被触发时,callback 都会被发送到任务队列(箭头2)。
事件循环(Event loop)不断地监视任务队列(Task Queue),并按它们排队的顺序一次处理一个回调。
每当调用堆栈(call stack)为空时,Event loop获取回调并将其放入堆栈(stack )(箭头3)中进行处理。
请记住,如果调用堆栈不是空的,则事件循环不会将任何回调推入堆栈。
  1. 调用 foo()会将foo函数放入调用堆栈(call stack)。
  2. 在处理内部代码时,JS引擎遇到setTimeout。
  3. 然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空
  4. 计时器被设置为0,因此foo将被发送到任务队列(箭头2)。
  5. 由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。
  6. 进程再次重复,堆栈不会溢出。

答案:堆栈不会溢出。

问题5: 如果在控制台中运行以下函数,页面(选项卡) 是否会有响应

在这里插入图片描述
答案:

我们是可以有多个任务列表的。由浏览器选择其中一个队列并在该队列进行处理回调。
从底层来看,JavaScript中是可以有宏认为和微任务的,比如说setTimeout回调是宏任务,而Promise回调是微任务
区别在于它们的执行方式,宏任务在单个循环周期中一次一个低堆入堆栈,但是微任务队列总是在执行后返回到事件之前清空。
所以,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。
只有当微任务队列为空时,事件循环才会重新渲染页面。

在这里插入图片描述

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

答案:不会响应。

问题6: 我们能否以某种方式为下面的语句使用展开运算而不导致类型错误

在这里插入图片描述
答案:

展开语法和for-of语句去遍历iterable对象定义要遍历的数据。要使用迭代器的时候,Array和Map都是有默认迭代操作的内置迭代器的。
但是,对象是不可迭代的,这道题里,是一个对象的集合。但是可以使用iterable和iterator协议来把它变成可以迭代的。
在研究对象的时候,如果一个对象他实现了@@iterator方法,那么它就是可以迭代的。
这意味着这个对象(在他的原型链上的一个对象)必须是又@@iterator键的属性的,然后我们就可以利用这个键,通过常量Symbol.iterator获得。

在这里插入图片描述
答案:如上图是一种方案,可以避免TypeError异常。

问题7:运行以下代码片段时,控制台上会打印什么?

在这里插入图片描述
答案:

for-in循环遍历本身的可枚举属性和对象从原来的原型继承来的属性。可枚举属性是可以在for-in循环期间可以访问的属性。

在这里插入图片描述
答案:a、b、c

问题8:xGetter() 会打印什么值?

在这里插入图片描述
答案:

首先我们可以看到var x是一个全局遍历,在不是严格模式下,这个X就直接是window对象的属性了。
最重要是要理解this的对象指向问题,this始终是指向调用方法的对象的。
所以,在foo,xGetter()的情况下,this指向的是foo对象,返回的就是在foo中的属性x,值就是90。
但是在xGetter()的情况下,他是直接调用的foo的getx()方法,
但是其中this的指向是在xGetter的作用域,就是指向的window对象中,这时指向的就是全局变量x了,值也就是10。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值