JavaScript中的10个小错误
如今,JavaScript已经成为网页编辑的核心。尤其是在过去的几年里,互联网见证了SPA开发、图形处理和交互中大量JS库的出现。
第一次和人打交道,很多人会觉得js很简单。事实上,对于许多有经验的工程师,甚至初学者来说,基本js功能的实现几乎没有障碍。但是JS的真正功能比很多人想象的更加多样和复杂。JavaScript的很多细节都会在你的网页上造成很多意想不到的bug。了解这些bug对于成为一名有经验的JS开发人员非常重要。
常见错误1:对该关键字的引用不正确。
我曾经听一个喜剧演员说过:
“我从来没有来过这里,因为我不知道它在哪里,难道是那里以外的地方吗?”
这句话或多或少暗示了开发人员在js开发中对这个关键词的误解。这是什么意思?在日常口语中和这个意思一样吗?
近年来,随着js编程功能的日益复杂和多样化,程序结构的内部指南和参考逐渐增加。
让我们一起来看看这段代码:
game . prototype . restart=function(){ this . ClearLocalstorage();this . timer=setTimeout(function(){ this . clearboard();}, 0);};运行上述代码将导致以下错误:
August typeerror : undefined不是函数。为什么呢?这种召唤与其所处的环境密切相关。出现上述错误的原因是,当您调用setTimeout()函数时,实际上是在调用window.setTimeout()。所以setTimeout()中定义的函数实际上是在window的后台定义的,window中没有clearBoard()函数方法。
提供了以下两种解决方案。第一个简单直接的方法是将它存储在一个变量中,这样它就可以在不同的环境中被继承:
game . prototype . restart=function(){ this . ClearLocalstorage();var self=这个;this . timer=setTimeout(function(){ self . clearboard();}, 0);};第二种方法是使用bind(),但这比前一种方法更复杂。
game . prototype . restart=function(){ this . ClearLocalstorage();this . timer=setTimeout(this . reset . bind(this),0);};game . prototype . reset=function(){ this . clearboard();};在上面的例子中,这两个都指的是游戏原型.
常见错误2:对传统编程语言的生命周期误解。
另一个容易犯的错误是认为JS和其他编程语言的思维是有生命周期的。请看下面的代码:
for(var I=0;i 10i ) { /*.*/} console . log(I);如果你认为当你运行console.log()时,你肯定会报告一个未定义的错误,那么你就大错特错了。我会告诉你它实际上会回到10吗?
当然,在许多其他语言中,如果您遇到这样的代码,您肯定会报告错误。因为我显然已经超越了它的生命周期。当在for中定义的变量的循环结束时,它的生命也就结束了。但是在js,我的生活还会继续。这种现象称为可变吊装。
如果我们想像其他语言一样在特定的逻辑模块中实现具有生命周期的变量,我们可以使用let关键字。
常见错误3:内存泄漏。
内存泄漏几乎是js转换中不可避免的问题。如果不十分小心,在最后的检查过程中,肯定会出现各种内存泄漏。下面我们举一个例子:
var theThing=nullvar replace thing=function(){ var priority=ThEring;var unused=function(){ if(priority){ console . log(' hi ');} };Thing={ longStr:新数组(1000000)。join('* '),//someMethod:函数(){ console . log(some message);} };};setInterval(replaceThing,1000);如果你运行上面的代码,你会发现你已经造成了大量的内存泄漏,每秒泄漏1M的内存。显然,单靠GC是帮不了你的。从上面的代码来看,似乎每次调用replaceThing都没有回收longstr。这是为什么?
每个Thing结构都包含一个longstr结构列表。每一秒当我们调用replaceThing时,它都会将当前点传递给priorThing。但是在这里我们会看到没有错,因为priorThing只有在解锁最后一个函数的点之后才会接受新的赋值。而这一切都发生在replaceThing函数体中。一般来说,当函数体完成后,函数中的局部变量也会被GC恢复,所以不会出现内存泄漏。但是为什么会出现上述错误呢?
这是因为longstr是在闭包中定义的,并且被其他闭包引用。js规定,当闭包之外的变量被引入到闭包中时,当闭包结束时,对象不能被垃圾收集(GC)。
常见错误4:比较运算符。
JavaScript的便利之一是,它可以强制比较操作中的每个结果变量转换为布尔类型。但另一方面,有时也会给我们带来很多不便。以下示例是困扰许多程序员的一些代码示例:
console . log(false==' 0 ');console.log(null==未定义);console . log(' \ t \ r \ n '==0);console . log('===0);//这些也是!If ({}) //.if ([]) //.虽然最后两行代码的条件判断为空(经常被误认为转换为假),但实际上{}或者[]都不是实体类,任何类实际上都会转换为真。正如这些例子所显示的,事实上,一些类型的强制转换非常模糊。因此,很多时候我们更喜欢用===和!==而不是==和!=,以避免强制类型转换。===还有!==和前面的==和的用法!=相同,只是它们没有类型转换。还有一点需要注意的是,当任何一个值与NaN,甚至是他自己进行比较时,结果都是假的。因此,我们不能简单地比较字符来确定一个值是否是NaN。我们可以使用内置的isNaN()函数来识别:
console . log(NAn==NAn);//false console . log(NAn===NAn);//false console . log(ISnan(NAn));//真常见错误DOM操作效率低。
js中基本的DOM操作非常简单,但是如何有效地进行这些操作一直是一个难题。最典型的问题是批量添加DOM元素。添加一个DOM元素是一个昂贵的操作。批量增加会使系统成本更高。批量增加的更好方法是使用文档片段:
var div=document . getelementsbytagname(' my _ div ');var fragment=document . createdocumentfragment();for(var e=0;e元素长度;e){ fragment . appendchild(elems[e]);} div . appendchild(fragment . clonenode(true));直接添加DOM元素是一个非常昂贵的操作。但是如果你先创建所有要添加的元素,然后全部添加,效率会高很多。
常见错误for循环中的函数调用不正确。
请查看以下代码:
var elements=document . getelementsbytagname(' input ');var n=elements.lengthfor(var I=0;I n;i ) {元素[i]。onclick=function() { console.log('这是元素# ' I ');};}运行上面的代码。如果页面上有10个按钮,点击每个按钮都会弹出“这是元素#10”!这不是我们所期望的。这是因为当click事件被触发时,for循环已经被执行,I的值已经从0更改。
我们可以通过下面的代码达到真正正确的效果:
var elements=document . getelementsbytagname(' input ');var n=elements.lengthvar MakeHandler=function(num){//outer function return function(){ console . log('这是元素# ' num ');};};for(var I=0;I n;i ) {元素[i]。onclick=MakeHandler(I ^ 1);}在这个版本的代码中,makeHandler将在每个循环中立即执行,将I 1传递给变量num。外部函数返回内部函数,点击事件函数设置为内部函数。这样,每个触发函数都可以使用正确的I值。
常见错误7:原型继承。
大多数js开发人员不能完全掌握原型的继承。下面是一个例子来说明:
BaseObject=函数(名称){ if(typeof name!==' undefined '){ this . name=name;} else { this.name=' default ' } }这段代码看起来非常简单。如果您有名称值,请使用它。如果不是,请使用“默认”:
var first Obj=new baseObject();var secondObj=new base object(' unique ');console . log(FirstObj . name);//-结果是“default”console . log(secondobj . name);//-结果是“唯一的”,但是如果我们执行delete语句呢?
删除secondObj.name我们会得到:
console . log(secondobj . name);//-结果是‘未定义’,但如果我们能回到‘默认’不是更好吗?其实要达到这种效果很简单,如果可以用原型继承的话。
BaseObject=函数(名称){ if(typeof name!==' undefined '){ this . name=name;} };baseObject . prototype . name=' default ';在此版本中,BaseObject继承原型中的名称属性,并设置为“默认”。此时,如果在没有参数的情况下调用构造函数,它将被自动设置为默认值。同样,如果名称属性从基本对象中移除,系统将自动找到原型链并获得“默认”值:
var thirdObj=new base object(' unique ');console . log(Thirdobj . name);删除thirdObj.nameconsole . log(Thirdobj . name);//-结果是“默认”。常见错误8:为实例方法创建错误的指导。
让我们看看下面的代码:
var MyObject=function(){ } MyObject . prototype . whoami=function(){ console . log(这===window?window ' : ' MyObj ');};var obj=new myObject();现在,为了方便起见,我们创建了一个新的变量来指导whoAmI方法,这样我们就可以直接使用whoAmI()来代替较长的obj.whoAmI():
var whoAmI=obj.whoAmI接下来,为了确保一切按照我们的预测进行,我们可以打印出whoAmI :
console . log(WhoAMi);结果是:
function(){ console . log(this===window?window ' : ' MyObj ');}没有错误!
但是现在让我们看看两种参考方法:
obj . whoami();//输出‘MyObj’(如预期)whoAmI();//输出‘窗口’(呃-哦!)哪里出了问题?
其实原理和上面第二个常见错误是一样的,当我们执行var whoAmI=obj.whoAmI,新变量whoAmI是在全局环境中定义的。因此,这意味着窗口,而不是obj!
正确的编码方法应该是:
var MyObject=function(){ } MyObject . prototype . whoami=function(){ console . log(这===window?window ' : ' MyObj ');};var obj=new myObject();obj . w=obj . whoami;//仍然在obj命名空间obj.whoAmI()中;//输出‘MyObj’(如预期)obj . w();//输出‘MyObj’(如预期)常见错误IX:使用字符串作为setTimeout或setInterval的第一个参数。
首先,我们应该声明使用string作为这两个函数的第一个参数没有语法错误。但实际上,这是一种非常低效的做法。因为从系统的角度来看,当你使用一个字符串时,它会被传递到构造函数中,并再次调用另一个函数。这将减缓计划的进度。
setInterval('logTime()',1000);setTimeout(' log message(' msgValue ' ')',1000);另一种方法是将函数直接作为参数传递:
setInterval(logTime,1000);setTimeout(函数(){ log message(MSgVaLue);}, 1000);常见错误10:忽略“严格模式”的效果。
严格模式是一种更严格的代码检查机制,将使您的代码更加安全。当然,不选择这种模式并不意味着它是一个错误,但是使用这种模式可以确保您的代码更加准确。
让我们总结一下“严格模式”的几个优点:
1.让Debug更容易:很多错误在正常模式下会被忽略,“严格模式”模式会让Debug变得极其严谨。
2.防止默认全局变量:在正常模式下,命名一个声明的变量会自动将该变量设置为全局变量。在严格模式下,我们取消了这个默认机制。
3.取消此的默认转换:在正常模式下,将此关键字定向为null或undefined将自动将其转换为全局。在严格模式下,我们取消了这个默认机制。
4.防止重复的变量声明和参数声明:严格模式下重复的变量声明将被认为是错误的,例如(例如,varobject={foo:' bar ',foo : ' baz ' };同时,在函数声明中重复使用相同的参数名称也会报告错误,例如(例如,function foo (val 1,val 2,val 1) {})。
5.使eval()函数更安全。
6.当遇到无效的删除指令时,之后会报告一个错误:不能对不在类中的属性执行删除指令。在正常情况下,这种情况只会被默默地忽略,而在严格模式下会报告一个错误。
像其他技术语言一样,对JavaScript了解得越深,知道它是如何工作的,知道它为什么工作,就越能更好地掌握和使用它。相反,如果缺乏JS模式的意识,就会遇到很多问题。了解JS的一些细节的语法或功能,有助于提高编程效率,减少转换过程中遇到的问题。
版权声明:JavaScript中的10个小错误是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。