作者: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将被发送到任务队列
