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

JavaScript开发者最常面临的10个问题

时间:2023-03-27 17:24:42 JavaScript

微信搜索【大千世界】,第一时间与大家分享前端行业动态、学习路径等。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。如今,JavaScript几乎是每个现代Web应用程序的核心。这就是为什么JavaScript问题以及找到导致它们的错误是Web开发人员的首要任务。用于单页应用程序(SPA)开发、图形和动画以及服务器端JavaScript平台的强大的基于JavaScript的库和框架并不是什么新鲜事。JavaScript在Web应用程序开发的世界中真正变得无处不在,因此掌握这一技能变得越来越重要。乍一看,JavaScript似乎很简单。事实上,将基本的JavaScript功能构建到网页中对于任何有经验的前端开发人员来说都是一项相当简单的任务,即使他们是JavaScript的新手。然而,这种语言比最初认为的要微妙得多、功能强大且复杂得多。事实上,JavaScript的许多微妙之处会导致许多阻碍其工作的常见问题——我们在这里讨论了其中的10个——在你成为JavaScript开发大师的过程中需要它们。小心避免。问题#1:错误引用this随着JavaScript编码技术和设计模式多年来变得越来越复杂,回调和闭包中的自引用范围也相应增加,这就是造成JavaScript问题“this/thatA相当普遍的混淆来源”。考虑以下代码:Game.prototype.restart=function(){this.clearLocalStorage();this.timer=setTimeout(function(){this.clearBoard();//“this”是什么?},0);};执行以上代码会出现如下错误:UncaughtTypeError:undefinedisnotafunction出现以上错误的原因是调用setTimeout()时,实际上是在调用window.setTimeout()。因此,传递给setTimeout()的匿名函数是在窗口对象的上下文中定义的,它没有clearBoard()方法。传统的、兼容旧浏览器的解决方案是将this引用保存在一个变量中,然后它可以被闭包继承,就像这样:Game.prototype.restart=function(){this.clearLocalStorage();变种自我=这个;//保存对'this'的引用,同时它仍然是this!这。timer=setTimeout(function(){self.clearBoard();//哦好吧,我知道“self”是谁!},0);};或者,在较新的浏览器中,您可以使用bind()方法传入适当的引用:Game.prototype.restart=function(){this.clearLocalStorage();this.timer=setTimeout(this.reset.bind(this),0);//绑定到'this'};Game.prototype.reset=function(){this.clearBoard();//啊哈,回到正确的“this”的上下文中!};问题2:认为存在块级作用域JavaScript开发人员中一个常见的混淆来源(也是一个常见的错误来源)是假设JavaScript为每个代码块创建了一个新的作用域。虽然在许多其他语言中都是如此,但在JavaScript中却并非如此。考虑以下代码:for(vari=0;i<10;i++){/*...*/}console.log(i);//它输出什么?如果您猜到调用console.log()会输出undefined或抛出错误,那么您猜错了。答案是输出10,为什么?在大多数其他语言中,上述代码会导致错误,因为变量i的“生命”(甚至范围)将被限制在for块中。但在JavaScript中,情况并非如此,即使在for循环完成后,变量i仍在范围内,在退出循环后保留其最后一个值。(顺便说一句,这种行为被称为变量提升。在JavaScript中对块级作用域的支持是通过let关键字实现的。Let关键字已被浏览器和后端JavaScript(如Node.js引擎)广泛支持很多年了。问题#3:造成内存泄漏内存泄漏几乎是不可避免的JavaScript问题,如果没有有意识地编写代码来避免它们。它们发生的方式有很多种,所以我们只强调一些更常见的情况。内存泄漏示例1:对不存在的对象的悬挂引用考虑以下代码:地方'priorThing'被引用。//但'unused'永远不会被调用if(priorThing){console.log("hi");}};theThing={longStr:newArray(1000000).join('*'),//创建一个1MB的对象someMethod:function(){console.log(someMessage);}};};setInterval(replaceTh荷兰国际集团,1000);//每秒调用“replaceThing”。如果你运行上面的代码并监视内存使用情况,你会发现你有明显的内存泄漏,每秒泄漏整整一兆字节!甚至手动垃圾收集器(GC)也无能为力。所以,似乎每次我们对replaceThing的调用都会泄漏longStr。但为什么?每个theThing对象都包含自己的1MBlongStr对象。每一秒,当我们调用replaceThing时,它都会在priorThing中保留对先前theThing对象的引用。但我们仍然认为这不会成为问题,因为每次通过时,先前引用的priorThing将被取消引用(当priorThing通过priorThing=theThing;重置时)。此外,它仅在replaceThing的主体和未使用的函数中引用,实际上从未使用过。所以我们又想知道为什么这里会出现内存泄漏。为了理解发生了什么,我们需要更好地理解JavaScript的内部工作原理。闭包的典型实现方式是每个函数对象都有一个指向表示其词法范围的类字典对象的链接。如果在replaceThing中定义的两个函数实际上都使用了priorThing,那么重要的是它们都获得相同的对象,即使priorThing被重复赋值,所以两个函数共享相同的词法环境。但是一旦一个变量被任何闭包使用,它最终会进入该范围内所有闭包共享的词法环境中。而这种细微差别就是导致这种可怕的内存泄漏的原因。内存泄漏示例2:循环引用考虑以下代码:functionaddClickHandler(element){element.click=functiononClick(e){alert("Clickedthe"+element.nodeName)}}对元素的引用(通过element.nodeName)。通过将onClick分配给element.click,创建了一个循环引用;即:element→onClick→element→onClick→element...有趣的是,上面的循环自引用阻止了element即使它从DOM中移除并且onClick被收集,所以存在内存泄漏。避免内存泄漏:重要的JavaScript内存管理(尤其是垃圾回收)主要基于对象可达性的概念。以下对象被认为是可达的,被称为“根”:从当前调用堆栈的任何地方引用的对象(即当前调用函数中的所有局部变量和参数,以及闭包范围内的所有变量)所有全局变量都保存在只要对象可以通过引用或引用链从根访问,内存就可以访问。浏览器中有一个垃圾收集器,用于清理不可达对象占用的内存;换句话说,当且仅当GC认为对象不可达时,对象才会从内存中删除。不幸的是,很容易有不再使用的“僵尸”对象,但GC仍然认为它们是“可达的”。问题4:双等号混淆JavaScript的便利之一是它自动将布尔上下文中引用的任何值强制转换为布尔值。但在某些情况下,这既方便又容易造成混淆。例如,以下几种情况让很多JavaScript开发者感到头疼。//下面的结果都是'true'console.log(false=='0');console.log(null==undefined);console.log("\t\r\n"==0);console.log(''==0);//下面也成立if({})//...if([])//...关于最后两个,虽然是空的(你可能认为他们为false),{}和[]实际上是对象,任何对象在JavaScript中都会被强制转换为布尔值“true”,这与ECMA-262规范是一致的。正如这些示例所示,类型强制转换的规则有时非常清晰。因此,除非明确要求类型强制转换,否则最好使用===和!==(而不是==和!=)来避免强制转换的意外副作用。(==和!=自动转换,而===和!==做相反的事情。)另一个注意事项:将NaN与任何东西(甚至NaN)进行比较都会返回false。因此,双等号(==、==、!=、!==)不能用来判断一个值是否为NaN。如果需要,可以使用内置的全局isNaN()函数。console.log(NaN==NaN);//Falseconsole.log(NaN===NaN);//Falseconsole.log(isNaN(NaN));//TrueJavaScript问题5:低效的DOM操作使用JavaScript操作DOM(即添加、修改、删除元素)相对容易,但操作效率不是很好。例如,每次添加一系列DOM元素。添加DOM元素是一项昂贵的操作。在一行中添加多个DOM元素的代码效率低下。当需要添加多个DOM元素时,一个有效的替代方法是使用文档片段来代替,这样可以提高效率和性能。vardiv=document.getElementsByTagName("my_div");varfragment=document.createDocumentFragment();for(vare=0;e'默认'console.log(secondObj.name);//->'unique'但是,如果你这样做:deletesecondObj.name;你会得到:console.log(secondObj.name);//'undefined'使用delete删除属性时,会返回一个undefined,那么如果我们也想返回default怎么办呢?利用原型继承如下:BaseObject=function(name){if(typeofname!=="undefined"){this.name=name;}};BaseObject.prototype.name='默认';BaseObject派生自其原型对象继承name属性,取值为default。因此,如果调用的构造函数没有名字,名字会默认为default。同样,如果从BaseObject的实例中删除了名称属性,则会找到原型链的名称,并且其值仍将是默认值。所以'varthirdObj=newBaseObject('unique');console.log(thirdObj.name);//->结果为'unique'deletethirdObj.name;console.log(thirdObj.name);//->导致“默认”问题8:为实例方法创建错误引用考虑以下代码:varMyObject=function(){}MyObject.prototype.whoAmI=function(){console.log(this===window?"window":"MyObj");};varobj=newMyObject();现在,为了方便起见,我们创建了对whoAmI方法的引用,以便通过whoAmI()而不是更长的obj.whoAmI()调用它。varwhoAmI=obj.whoAmI;为了确保没有问题,让我们打印出whoAmI看看:console.log(whoAmI);output:function(){console.log(this===window?"window":"MyObj");}好的,看起来没问题。接下来,看看我们调用obj.whoAmI()和whoAmI()时的区别。obj.whoAmI();//输出“MyObj”(如预期的那样)whoAmI();//输出“窗口”(呃-哦!)出了什么问题?当我们分配varwhoAmI=obj.whoAmI时,新变量whoAmI被定义在全局命名空间中。结果,this的值是window,而不是MyObject的obj实例!因此,如果我们真的需要创建对对象现有方法的引用,我们需要确保在该对象的命名空间内进行,以保留this值。一种方法是这样做:varMyObject=function(){}MyObject.prototype.whoAmI=function(){console.log(this===window?"window":"MyObj");};varobj=newMyObject();obj.w=obj.whoAmI;//仍然在obj命名空间中obj.whoAmI();//输出“MyObj”(如预期的那样)obj.w();//输出“MyObj”(如预期)问题9:提供字符串作为setTimeout或setInterval的第一个参数首先,重要的是要知道提供字符串作为setTimeout或setInterval的第一个参数本身并不是错误。这是完全合法的JavaScript代码。这里的问题更多是性能和效率问题。很少有人解释的是,如果你将一个字符串作为第一个参数传递给setTimeout或setInterval,它将被传递给函数构造函数,它将被转换为一个新函数。这个过程可能很慢,效率低下,而且很少需要。将字符串作为第一个参数传递给这些方法的另一种方法是传递一个函数。setInterval("logTime()",1000);setTimeout("logMessage('"+msgValue+"')",1000);更好的选择是传入一个函数作为初始参数:setInterval(logTime,1000);setTimeout(function(){logMessage(msgValue);},1000);问题10:不使用“严格模式”“严格模式”(即在JavaScript源文件开头包含“usestrict”;)是一种自愿对JavaScript代码强制执行更严格的解析和错误处理的方法,同时也使它更安全。然而,不使用严格模式本身并不是“错误”,但越来越鼓励使用它,而不使用它越来越被认为是不好的形式。以下是严格模式的一些主要好处:使调试更容易。原本不会被注意到或未被注意到的代码错误现在会产生错误或抛出异常,从而更快地提醒我们代码库中的JavaScript问题,并更快地找到它们的源头。防止意外的全局变量。在没有严格模式的情况下,为未声明的变量赋值会自动创建一个具有该名称的全局变量。这是最常见的JavaScript错误之一。在严格模式下,尝试这样做会产生错误。消除这种强迫症。在没有严格模式的情况下,对null或undefinedthis值的引用会自动强制转换为全局。在严格模式下,引用null或undefinedthis值会产生错误。不允许重复的属性名称或参数值。严格模式检测对象中的重复命名属性(例如,varobject={foo:"bar",foo:"baz"};)或函数的重复命名参数(例如,functionfoo(val1,val2,val1){}),从而捕获几乎可以肯定是代码中的错误,否则您可能会浪费大量时间进行跟踪。使eval()更安全。eval()在严格模式和非严格模式下的行为方式存在一些差异。最重要的是,在严格模式下,在eval()语句中声明的变量和函数不会在包含范围内创建。(在非严格模式下,它们是在包含域中创建的,这也可能是JavaScript问题的常见来源)。在无效使用delete的情况下抛出错误。delete运算符(用于从对象中删除属性)不能用于对象的不可配置属性。尝试删除不可配置的属性时,非严格代码会静默失败,而在这种情况下,严格模式会抛出错误。代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。来源:https://www.toptal.com/javasc...交流有梦想,有干货,微信搜索【伟大的走向世界】关注这位凌晨还在洗碗的洗碗智慧。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。