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

面试小册子:面试官常问的十道JavaScript难题

时间:2023-03-29 12:22:03 HTML

1.可变性JavaScript中有七种基本数据类型(string、number、boolean、undefined、symbol、bigint和null),它们是不可变的。这意味着一旦分配了一个值,我们就不能修改它们,我们能做的就是将它重新分配给一个不同的值(不同的内存指针)。另一方面,其他数据类型如Object和Function是可变的,这意味着我们可以修改同一内存指针中的值。//Q1lettext='abcde'text[1]='z'console.log(text)//abcde字符串是不可变的,所以一旦赋值就不能更改为不同的值,你可以做什么是重新分配它。请记住,更改值与重新分配给另一个值不同。//Q2constarr=[1,2,3]arr.length=0console.log(arr)//[]将arr.length赋值为0等同于清空数组,所以此时数组会变空观点。//Q3constarr=[1,2,3,4]arr[100]=undefinedconsole.log(arr,arr.length)//[1,2,3,4,empty×96,undefined]101因为数组占用是连续的内存位置,所以当我们将索引100分配给一个值(包括未定义)时,JavaScript会保留从索引0到索引100的内存,这意味着数组现在的长度为101。2.var和boost//Q4varvariable=10;(()=>{variable2=100console.log(variable)console.log(variable2)variable=20varvariable2=50console.log(variable)})();console.log(variable)varvariable=30console.log(variable2)//10//100//20//20//ReferenceError:variable2isnotdefinedvar是函数作用域的变量,而let和const是块级的域变量,只能提升var,这意味着变量声明总是移到顶部。由于提升,您甚至可以在使用var关键字声明变量之前分配、调用或使用变量。let和const无法提升,因为它启用了TDZ(临时死区),这意味着变量在声明之前不可访问。在上面的示例中,variable2在函数内部声明,var关键字使变量仅在函数范围内可用。因此,当函数外部的任何内容尝试使用或调用该变量时,将抛出referenceError。//Q5test()//没有错误functiontest(){cconsole.log('test')}test2()//errorvartest2=()=>console.log('test2')functionkeyworddeclarationFunctionscan提升函数语句,但箭头函数不能,即使它们是使用var声明的。3.意外的全局变量//Q6functionfoo(){leta=b=0;一个++;返回a;}foo();b类型;//数字类型;//undefinedconsole.log(a)//错误:ReferenceError:a未定义var是函数作用域,let是块作用域变量。虽然看起来a和b都是使用let(leta=b=0)声明的,但实际上变量b被声明为全局变量并分配给Window对象。换句话说,它类似于:functionfoo(){window.b=0;让a=b;一个++;}4.闭包//Q7constlength=4;常量fns=[];常量fns2=[];for(vari=0;iconsole.log(i));}for(leti=0;iconsole.log(i));}fns.forEach(fn=>fn());//4444fns2.forEach(fn=>fn());//0123闭包是对变量环境的一种保护,即使变量已经改变或被垃圾回收。在上面的问题中,区别在于变量声明,其中第一个循环使用var,第二个循环使用let。var是函数作用域变量,因此当在for循环块内声明时,var被视为全局变量而不是内部变量。另一方面,let是一个块作用域的变量,类似于Java和C++等其他语言中的变量声明。在这种情况下,闭包只发生在let变量中,每个推入fns2数组的函数都会记住变量的当前值,而不管变量将来是否发生变化。相反,fns不记住变量的当前值,它使用全局变量的未来或最终值。5.对象//Q8varobj1={n:1}varobj2=obj1obj2.n=2console.log(obj1)//{n:2}//Q9functionfoo(obj){obj.n=3obj.name='test'}foo(obj2)console,log(obj1)//{n:3,name:'test'}我们知道,对象变量只包含指向对象内存位置的指针,所以这里obj2和obj1指向同一个对象。这意味着如果我们更改obj2的任何值,obj1也会受到影响,因为它们本质上是同一个对象。同样,当我们在函数中将对象作为参数传递时,传递的参数只包含对象指针。因此,函数可以直接修改对象而不返回任何内容,这种技术称为引用传递。//Q10varfoo={n:1};变量栏=富;安慰。日志(富===栏);//真富。x=foo={n:2};安慰。log(foo)//{n:2}console.log(bar)//{n:1,x:{n:2}}console.log(foo===bar)//false因为对象变量只包含指向对象内存位置的指针,所以当我们声明varbar=foo时,foo和bar都指向同一个对象。在接下来的逻辑中,foo={n:2}首先运行,其中foo被分配给一个不同的对象,因此foo有一个指向不同对象的指针。与此同时,foo.x=foo正在运行,这里foo仍然包含旧指针,所以逻辑类似于:foo={n:2}bar.x=foosobar.x={n:2},最后foo的值是{n:2}而bar是{n:1,x:{n:2}}。6.this//Q11constobj={name:"test",prop:{name:"propname",print:function(){console.log(this.name)},},print:function(){控制台.log(this.name)}print2:()=>console.log(this.name,this)}obj.print()//testobj.prop.print()//propnameobj.print2()//undefined,windowglobalobject上面的例子展示了this关键字在一个对象中是如何工作的,这里this指的是执行函数中的执行上下文对象。但是,此作用域仅适用于普通函数声明,不适用于箭头函数。上面的例子展示了显式绑定,比如在object1.object2.object3.object4.print()中,打印函数会使用最新的对象object4作为thiscontext,如果this没有绑定到一个对象,它会回退到rootobject,它是调用obj.print2()时的Window全局对象。另一方面,你也必须理解对象上下文之前已经绑定的隐式绑定,所以下一个函数执行总是使用那个对象作为这个上下文。例如:当我们使用func.bind()时,它将返回一个新函数,其中用作新的执行上下文。7.强制转换//Q12console.log(1+"2"+"2");//122console.log(1++"2"+"2");//32console.log(1+-"1"+"2");//02console.log(+"1"+"1"+"2");//112console.log("A"-"B"+"2");//NaN2控制台。日志(“A”-“B”+2);//NaN"10,11"==[[[[10]],11]]//true(10,11==10,11)"[objectObject]"=={name:"test"}true转换是最棘手的JavaScript问题之一。总的来说有两个原则,第一个是如果用+操作符拼接2个操作数,会先用toString方法将两个操作数转换成字符串,再拼接。同时,其他运算符,如-、*或/将操作数更改为数字,或者如果无法将其强制转换为数字,则返回NAN。如果操作数包含一个对象或数组,那就更棘手了。任何对象的toString方法返回的都是“[objectObject]”,但是在数组中,toString方法返回的是逗号分隔的底层值。注意:==表示允许转换,而===则不允许。8.异步//Q13console.log(1);newPromise(resolve=>{console.log(2);returnsetTimeout(()=>{console.log(3);resolve();},0)})setTimeout(function(){console.log(4)},1000);setTimeout(function(){console.log(5)},0);console.log(6);//1//2//6//3//5//4在这里,你需要知道事件循环、宏任务和微任务队列是如何工作的。您可以在此处查看这篇文章,其中深入探讨了这些概念。通常,异步函数在用于执行的同步函数之后执行。//Q14asyncfunctionfoo(){return10;}console.log(foo())//Promise{:10}一旦一个函数被声明为异步,它总是返回一个Promise,不管内部逻辑是否是同步或异步。//Q15constdelay=async(item)=>newPromise(resolve=>setTimeout(()=>{console.log(item);resolve(item);},Math.random()*100))console.log(1)letarr=[3,4,5,6]arr.forEach(asyncitem=>awaitdelay(item)))console.log(2)forEach函数总是同步的,不管每个循环是同步的还是异步的,这意味着每个循环都不会等待另一个循环。如果想依次执行每个循环,互相等待,可以用forof代替。9.Function//Q16if(functionf(){}){console.log(f)}//error:ReferenceError:fisnotdefined在上面的例子中,满足if条件是因为函数声明被认为是一个true价值。但是,内部块不能访问函数声明,因为它们具有不同的块作用域。//Q17functionfoo(){return{name:2}}foo()//returnsundefined由于自动分号插入(ASI)机制,return语句将以分号结尾,分号以下的所有内容都不会运行。//Q18functionfoo(a,b,a){returna+b}console.log(foo(1,2,3))//3+2=5functionfoo2(a,b,c=a){returna+b+c}console.log(foo(1,2))//1+2+1=4functionfoo3(a=b,b){returna+b}console.log(foo3(1,2))//1+2=3console.log(foo3(undefined,2))//前3次执行报错很清楚,但是最后一次函数执行会报错,因为在声明之前使用了b,类似这个:设a=b;设b=2;10。原型//Q19functionPersion(){}Persion.prototype.walk=function(){returnthis}Persion.run=function(){returnthis}letuser=newPersion();letwalk=user.walk;console.log(walk())//窗口对象console.log(user.walk())//用户对象run=Persion.run;console.log(run());//窗口对象console.log(user.run());//TypeError:user.runisnotafunction原型是存在于每个变量中以从其父级继承属性的对象。例如,当你声明一个字符串变量时,该字符串变量有一个继承自String.prototype的原型,这就是为什么你可以在字符串变量中调用字符串方法,如string.replace()、string.substring()等。在上面的示例中,我们将walk函数分配给了Persion函数的原型,将run函数分配给了函数对象。这是两个不同的对象,每个使用new关键字的函数创建的对象都将继承函数原型的方法,而不是函数对象。但请记住,如果我们将函数分配给一个变量,例如letwalk=user.walk,该函数将忘记使用user作为执行上下文,而是返回到Window对象。原文:https://medium.com/@andreassu…