当前位置: 首页 > Web前端 > HTML

8个问题来测试您的基本JavaScript技能!

时间:2023-04-02 20:49:13 HTML

作者:ChidumeNnamdi译者:前端小智来源:smashingmagazine喜欢再看一遍,养成习惯分类,也整理了很多我的文档,教程资料。欢迎来到星和完美。面试时可参考考点复习。我希望我们能在一起。JavaScript是一种有趣的语言,我们都喜欢它,因为它的本质。浏览器是JavaScript主要运行的地方,两者在我们的服务中协同工作。JS的概念人们往往会轻视,有时甚至会忽略。原型、闭包和事件循环等概念仍然是大多数JS开发人员绕道而行的模糊领域之一。众所周知,无知是一件危险的事情,它会导致错误。想阅读更多优质文章,请戳GitHub博客,一年百篇优质文章等你来!接下来我们来看几个问题,大家也可以试着去想一想,然后再作答。问题一:浏览器控制台会打印什么?变量a=10;函数foo(){console.log(a);//??变量a=20;}foo();问题2:如果我们使用let或const代替var,输出是否相同vara=10;functionfoo(){console.log(a);//??让a=20;}foo();问题3:“newArray”中有哪些元素?vararray=[];for(vari=0;i<3;i++){array.push(()=>i);}varnewArray=array.map(el=>el());console.log(新数组);//??问题四:如果我们在浏览器控制台运行'foo'函数,会不会导致堆栈溢出错误?函数foo(){setTimeout(foo,0);//是否有栈溢出错误?};问题5:如果我在控制台运行以下功能,页面(选项卡)的UI是否仍然响应functionfoo(){returnPromise.resolve().then(foo);};问题6:我们能否以某种方式对以下语句使用展开运算符而不会导致类型错误varobj={x:1,y:2,z:3};[...obj];//TypeError问题7:运行下面的代码片段时,控制台会打印什么?varobj={a:1,b:2};Object.setPrototypeOf(obj,{c:3});Object.defineProperty(obj,'d',{value:4,enumerable:false});//什么当我们运行for-in循环时,属性会被打印出来吗?for(letpropinobj){console.log(prop);}问题8:xGetter()会打印什么值?变量x=10;varfoo={x:90,getX:function(){返回this.x;}};foo.getX();//打印90varxGetter=foo.getX;xGetter();//prints??Answer现在,让我们从头到尾回答每个问题,我会给你一个简短的解释,同时试图揭开这些行为的神秘面纱,并提供一些参考。问题一:undefined解析:用var关键字声明的变量会在JavaScript中被提升,在内存中赋值为undefined。但是初始化恰好发生在你为变量赋值的地方。此外,用var声明的变量是函数作用域的,而let和const是块作用域的。所以,这就是这个过程的样子:vara=10;//全局作用域functionfoo(){//vara的声明将被提升到函数的顶部。//例如:varaconsole.log(a);//打印undefined//实际的初始化值20只发生在这里vara=20;//localscope}问题2:ReferenceError:aundefined.解析:let和const声明允许将变量的范围限制在使用它们的块、语句或表达式中。与var不同,这些变量不会被提升,并且有一个所谓的临时死区(TDZ)。尝试访问TDZ中的这些变量将引发ReferenceError,因为只有在执行到声明时才能访问它们。变量a=10;//全局作用域functionfoo(){//TDZ开始//创建未初始化的'a'console.log(a);//ReferenceError//endofTDZ,'a'onlyhereInitializedat,withthevalueof20leta=20;}下表概述了JavaScript中使用不同关键字声明的变量对应的提升行为和使用范围:问题3:[3,3,3]解析:在for循环中带有var关键字的变量的标头声明为该变量创建了一个绑定(存储空间)。阅读更多关于闭包的信息。让我们再看看for循环。//对作用域的误解:认为存在块作用域vararray=[];for(vari=0;i<3;i++){//三个箭头函数体中的每个`'i'`都指向相同的绑定,//这就是为什么它们在最后返回相同的值'3'的循环。array.push(()=>i);}varnewArray=array.map(el=>el());console.log(newArray);//[3,3,3]如果使用let在第一级范围内声明一个带有变量的块,则为每个循环迭代创建一个新的绑定。//使用ES6块作用域vararray=[];for(leti=0;i<3;i++){//这一次,每个“i”指的是一个新的绑定,并保持当前值。//因此,每个箭头函数返回不同的值。array.push(()=>i);}varnewArray=array.map(el=>el());控制台日志(新数组);//[0,1,2]解决这个问题的另一种方法解决这个问题的方法是使用闭包。让数组=[];对于(vari=0;i<3;i++){array[i]=(function(x){returnfunction(){returnx;};})(i);}constnewArray=array.map(el=>el());console.log(newArray);//[0,1,2]问题四:没有溢出分析:JavaScript并发模型是基于“事件循环”的。当我们说“浏览器是JS的家”时,我真正的意思是浏览器提供了运行时环境来执行我们的JS代码。浏览器的主要组件包括调用堆栈、事件循环、任务队列和WebAPI。setTimeout、setInterval和Promise等全局函数不是JavaScript的一部分,而是WebAPI的一部分。JavaScript环境的可视化如下所示:JS调用堆栈是后进先出(LIFO)。引擎一次从堆栈中弹出一个函数,然后从上到下依次运行代码。每当遇到像setTimeout这样的异步代码时,它就会将其交给WebAPI(箭头1)。因此,无论何时触发事件,回调都会被发送到任务队列(箭头2)。事件循环(Eventloop)不断地监控任务队列(TaskQueue),并按照回调的排队顺序一次一个地处理回调。每当调用堆栈为空时,Event循环获取回调并将其放入堆栈(箭头3)以进行处理。请记住,如果调用堆栈不为空,事件循环不会将任何回调压入堆栈。现在,有了这些知识,让我们回答之前提出的问题:对foo()的步骤调用将foo函数放入调用堆栈。JS引擎在处理内部代码时遇到setTimeout。然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空并且定时器设置为0,因此foo将被发送到任务队列(箭头2).由于调用堆栈是空的,事件循环将选择foo回调并将其压入调用堆栈以进行处理。再次重复该过程,堆栈没有溢出。运行图如下:图片说明问题5:无响应解析:开发者大多假设事件循环图中只有一个任务队列。但事实并非如此,我们可以有多个任务队列。由浏览器选择这些队列之一并处理该队列中的回调。在底层,JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务。主要区别在于它们的实现方式。宏任务在单个循环周期中一次一个地被压入堆栈,但微任务队列总是在执行后清空,然后返回事件循环。因此,如果您以处理条目的速度向该队列添加条目,那么您将永远处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面。现在,当您在控制台中运行以下代码片段时foo(){returnPromise.resolve().then(foo);};每次调用“foo”都会在微任务队列中继续添加另一个“foo”回调,因此事件循环无法继续处理其他事件(滚动、点击等),直到该队列完全清空。因此,它会阻止渲染。问题六:会导致TypeError错误解析:unwind语法和for-of语句遍历可迭代对象定义来遍历数据。Array或Map是具有默认迭代行为的内置迭代器。对象不可迭代,但可以通过使用iterable和iterator协议使它们可迭代。在Mozilla文档中,如果一个对象实现了@@iterator方法,那么该对象是可迭代的,这意味着该对象(或其原型链上的对象)必须具有带有@@iterator键的属性,键可以通过常量获得符号.迭代器。上面的语句可能看起来有点冗长,但下面的例子会更有意义:varobj={x:1,y:2,z:3};obj[Symbol.iterator]=function(){//迭代器是一个具有next方法的对象,//它返回至少一个对象//具有两个属性:value和done。//返回一个迭代器对象return{next:function(){if(this._countDown===3){constlastValue=this._countDown;返回{值:this._countDown,完成:真};}this._countDown=this._countDown+1;返回{值:this._countDown,完成:false};},_countDown:0};};[...obj];//print[1,2,3]也可以使用生成器函数来自定义对象的迭代行为:varobj={x:1,y:2,z:3}obj[Symbol.iterator]=function*(){产量1;产量2;产量3;}[...obj];//Print[1,2,3]问题7:a,b,c解析:for-in循环遍历对象本身的可枚举属性和对象从其原型继承的属性。可枚举属性是可以在for-in循环中包含和访问的属性。varobj={a:1,b:2};vardescriptor=Object.getOwnPropertyDescriptor(obj,"a");console.log(descriptor.enumerable);//trueconsole.log(descriptor);//{value:1,writable:true,enumerable:true,configurable:true}现在你已经了解了这些知识,应该很容易理解为什么我们的代码会打印这些特定的属性varobj={a:1,b:2};//a,b都是可枚举属性//将{c:3}设置为'obj'的原型,我们知道//for-in循环也会迭代obj//从其原型继承的属性,'c'也可以访问。Object.setPrototypeOf(obj,{c:3});//我们在'obj'中定义了另一个属性'd',但是//将'enumerable'设置为false。这意味着'd'将被忽略。Object.defineProperty(obj,"d",{value:4,enumerable:false});for(letpropinobj){console.log(prop);}//打印//a//b//c问题8:10解析:当x被全局初始化后,它就变成了window对象的一个??属性(非严格模式)。看看下面的代码:varx=10;//全局作用域varfoo={x:90,getX:function(){returnthis.x;}};foo.getX();//打印90letxGetter=foo.getX;xGetter();//打印10我们可以断言:window.x===10;//truethis总是指向调用该方法的对象。因此,在指向foo对象的foo.getx()的情况下,返回值90。在xGetter()中,this指向窗口对象,并返回窗口中x的值,即10。要获取foo.x的值,请通过将this的值绑定到foo对象使用Function.prototype.bind。让getFooX=foo.getX.bind(foo);getFooX();//90就是这样!如果您的所有答案都正确,那就太好了。我们都从犯错误中学习。这就是了解其背后的“原因”。代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。原文:https://dev.to/aman_singh/so-...干货交流系列文章总结如下。我觉得订个Star挺好的。欢迎加群,互相学习。https://github.com/qq44924588...我是小智,公众号《大招天下》的作者,前端技术爱好者。我会经常分享自己学习看到的干货,在进步的路上互相鼓励!关注公众号,后台回复福利,就能看到福利,你懂的。