手机版

8个有趣的JavaScript面试问题

时间:2021-08-21 来源:互联网 编辑:宝哥软件园 浏览:

JavaScript是一种有趣的语言,我们都喜欢它,因为它的本质。浏览器是JavaScript运行的主要场所,两者在我们的服务中协同工作。JS有一些概念,人们往往会掉以轻心,有时会忽略。原型、闭包和事件循环等概念仍然是大多数JS开发人员绕过的模糊领域之一。众所周知,无知是一件危险的事情,可能会导致错误。

接下来,让我们来看看一些问题。你也可以试着想一想,然后回答。

Q1:浏览器控制台上会打印什么?

var a=10function foo(){ console . log(a);//?var a=20} foo();问题2:如果我们用let或const代替var,输出是否相同?

var a=10function foo(){ console . log(a);//?设a=20} foo();问题3:“新数组”中有哪些元素?

var数组=[];for(var I=0;I 3;I){ array . push(()=I);} var new array=array . map(El=El());console . log(NewArray);//?问题4:如果我们在浏览器控制台中运行‘foo’函数,会不会导致堆栈溢出错误?

function foo() { setTimeout(foo,0);//是否有堆栈溢出错误?}问题5:如果在控制台中运行以下功能,页面(选项卡)的UI是否仍有响应

function foo(){ return Promise . resolve()。然后(foo);}问题6:我们能否以某种方式对以下语句使用扩展操作,而不会导致类型错误

var obj={ x: 1,y: 2,z : 3 };[.obj];//类型错误问题7:运行以下代码片段时,控制台上会打印什么?

var obj={ a: 1,b : 2 };object . set rototypeof(obj,{ c : 3 });object . definepreproperty(obj,' d ',{ value: 4,enumerable : false });//当我们运行for-in循环时,将打印哪些属性?for(let prop in obj){ console . log(prop);}问题8:8:xGetter()打印什么值?

var x=10var foo={ x: 90,getx : function(){ return this . x;}};foo . GetX();//打印90var xGetter=foo.getXxGetter();//打印?回答

现在,让我们从头到尾回答每个问题。我会给大家简单解释一下,同时尝试揭开这些行为的奥秘,并提供一些参考资料。

问题1:未定义

用var关键字声明的变量在JavaScript中升级,未定义的值在内存中分配。但是初始化恰好发生在给变量赋值的地方。另外,var声明的变量是函数作用域,let和const是块作用域。这就是这个过程的样子:

var a=10//全局使用字段函数foo() {//var a的声明将被提升到函数的顶部。//例如, var a console . log(a);//print undefined //实际的初始化值20只发生在这里。var a=20//局部作用域}问题2: 2:ReferenceError:a undefined

let和const声明允许变量被其使用的块、语句或表达式限制其范围。与var不同的是,这些变量没有提升,存在所谓的暂时死区(TDZ)。试图在TDZ访问这些变量会抛出一个ReferenceError,因为它们只能在到达声明被执行时被访问。

var a=10//未初始化的“a”console . log(a)是使用域函数foo() {//TDZ创建的。//ReferenceError //TDZ结束,‘a’只在这里初始化,值为20让a=20}下表总结了与JavaScript中使用的不同关键字所声明的变量相对应的推广行为和使用领域:

问题3: [3,3,3]

在for循环的开头声明一个带有var关键字的变量会为该变量创建一个绑定(存储空间)。阅读更多关于闭包的内容。让我们再看看for循环。

//误解作用域:认为存在块级作用域var数组=[];for(var I=0;I 3;I) {//三个箭头函数体` ` I ' '都指向同一个绑定,//这就是它们在循环结束时返回相同值' 3 '的原因。array . push(()=I);} var new array=array . map(El=El());console . log(NewArray);//[3,3,3]如果使用let声明一个具有块级作用域的变量,则为每个循环迭代创建一个新的绑定。

//使用ES6块级作用域var数组=[];for(设I=0;I 3;I) {//这一次,每个‘I’都指向一个新的绑定,并保持当前值。//因此,每个箭头函数返回不同的值。array . push(()=I);} var new array=array . map(El=El());console . log(NewArray);//[0,1,2]解决这个问题的另一种方法是使用闭包。

让数组=[];for(var I=0;I 3;I){ array[I]=(function(x){ return function(){ return x;};})(I);} const NewArray=array . map(El=El());console . log(NewArray);//[0,1,2]问题4 :不会溢出

JavaScript并发模型基于“事件循环”。当我们说“浏览器是JS的家”时,我真正的意思是浏览器提供了一个运行时环境来执行我们的JS代码。

浏览器的主要组件包括调用栈、事件循环、任务队列和Web API。像setTimeout、setInterval和Promise这样的全局函数不是JavaScript的一部分,而是Web API的一部分。JavaScript环境的可视化形式如下:

JS调用栈是LIFO。引擎一次从堆栈中取出一个函数,然后从上到下运行代码。每当它遇到一些异步代码,比如setTimeout,它就把它交给Web API(箭头1)。因此,每当触发一个事件,回调就被发送到任务队列(箭头2)。

事件循环不断监视任务队列,并按照它们排队的顺序一次处理一个回调。每当调用堆栈为空时,事件循环获得回调,并将其放入堆栈(箭头3)进行处理。请记住,如果调用堆栈不为空,事件循环不会将任何回调推送到堆栈上。

现在,有了这些知识,让我们来回答前面提到的问题:

Step calls foo()将把foo函数放在调用堆栈上。处理内部代码时,JS引擎遇到setTimeout。然后,foo回调函数被传递给WebAPIs(箭头1),并从函数返回。调用堆栈再次为空,计时器设置为0,因此foo将被发送到任务队列(箭头2)。因为调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。该过程再次重复,堆栈不会溢出。

操作示意图如下:

代码部署后可能存在的bug无法实时获知。为了事后解决这些bug,需要花费大量时间调试日志。顺便推荐一个不错的bug监控工具Fundebug。

问题5 :不予答复

大多数时候,开发人员假设事件循环图中只有一个任务队列。但事实并非如此。我们可以有多个任务队列。浏览器选择其中一个队列并处理队列中的回调。

在底层,JavaScript中有宏任务和微任务。SetTimeout回调是一个宏任务,而Promise回调是一个微任务。

主要区别在于实施方式。宏任务在单个周期中一次一个地被推入堆栈,但是微任务的队列总是在执行后返回事件周期之前被清空。因此,如果您以处理条目的速度向该队列添加条目,您将始终在处理微任务。只有当微任务队列为空时,事件循环才会重新呈现页面。

现在,当您在控制台中运行以下代码片段时

function foo(){ return Promise . resolve()。然后(foo);}每次调用‘foo’时,另一个‘foo’回调会继续添加到微任务队列中,因此事件循环无法继续处理其他事件(滚动、点击等)。)直到队列完全清空。因此,它会阻止渲染。

问题6 :导致类型错误错误

展开语法和for-of语句遍历可迭代对象以定义要遍历的数据。数组或映射是具有默认迭代行为的内置迭代器。对象是不可迭代的,但是可以通过使用可迭代和迭代器协议使它们可迭代。

在Mozilla文档中,如果一个对象实现了@@iterator方法,那么它就是迭代的,这意味着这个对象(或者它的原型链中的一个对象)必须有一个带有@@iterator键的属性,这个属性可以通过常量符号获得。

上面的陈述可能看起来有点长,但是下面的例子会更有意义:

var obj={ x: 1,y: 2,z : 3 };符号。iterator]=function(){//iterator是一个带有next方法的对象,//它的返回至少有一个object//两个属性:value&done。//返回迭代器对象返回{next:function () {if (this。_倒计时===3) {const lastvalue=this。_倒计时;返回{ value: this。_倒计时,done : true };}这个。_倒计时=这个。_倒计时1;返回{ value: this。_倒计时,done : false };},count down _ : { 0 };};[.obj];//打印[1,2,3]还可以使用生成器函数自定义对象的迭代行为:

var obj={ x: 1,y: 2,z : 3 };obj[symbol . iterator]=function *(1){ yield 1;产量2;产量3;};[.obj];//打印[1,2,3]问题7 : a,b,c

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

var obj={ a: 1,b : 2 };var descriptor=object . getowntpropertysdescriptor(obj,' a ');console . log(descriptor . enumerable);//trueconsole.log(描述符);//{value: 1,可写: true,可枚举: true,可配置: true}现在您已经掌握了这些知识,应该很容易理解为什么我们的代码会打印这些特定的属性。

var obj={ a: 1,b : 2 };//a、b都是可枚举的属性//集合{c: 3}作为‘obj’的原型,我们知道//for-in循环也迭代了obj//从其原型继承的属性,‘c’也是可以访问的。object . set rototypeof(obj,{ c : 3 });//我们在“obj”中定义了另一个属性“d”,但是//将“可枚举”设置为false。这意味着d将被忽略。object . definepreproperty(obj,' d ',{ value: 4,enumerable : false });for(let prop in obj){ console . log(prop);}//print//a///bqueue问题8 : 10

当x被全局初始化时,它成为窗口对象的一个属性(不是严格的模式)。看看下面的代码:

var x=10//global scopevar foo={ x: 90,getx : function(){ return this . x;}};foo . GetX();//打印90let xGetter=foo.getXxGetter();//prints 10我们可以断言:

window.x===10//truethis总是指向调用方法的对象。因此,在foo.getx()示例中,它指向foo对象并返回一个值90。在xGetter()的情况下,这指向窗口对象,并返回窗口中X的值,即10。

要获取foo.x的值,可以通过使用Function.prototype.bind将该值绑定到foo对象来创建一个新函数

让getFooX=foo . getx . bind(foo);getFooX();//90就这样!如果你的答案都是正确的,那就做得好。我们都是通过犯错来学习的。这一切都是为了了解其背后的“原因”。

代码部署后可能存在的bug无法实时获知。为了事后解决这些bug,需要花费大量时间调试日志。顺便推荐一个不错的bug监控工具Fundebug。

摘要

以上是边肖介绍的8个有趣的JavaScript面试问题,希望对大家有所帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!如果你觉得这篇文章对你有帮助,请转载,请注明出处,谢谢!

版权声明:8个有趣的JavaScript面试问题是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。