序文
在本日,JavaScript已成为了网页编辑的中心。尤其是过去的几年,互联网见证了在SPA开辟、图形处置惩罚、交互等方面大批JS库的涌现。
假如首次打交道,很多人会以为js很简朴。确切,关于很多有履历的工程师,或许以至是初学者而言,完成基础的js功用险些毫无停滞。然则JS的实在功用却比很多人设想的要越发多样、庞杂。JavaScript的很多细节划定会让你的网页涌现很多意想不到的bug,搞懂这些bug,关于成为一名有履历的JS开辟者很主要。
罕见毛病一:关于this关键词的不准确援用
我曾听一名喜剧演员说过:
“我从未在这里,由于我不清楚这里是那边,是除了那边以外的处所吗?”
这句话或多或少地暗喻了在js开辟中开辟者关于this关键字的应用误区。This指代的是什么?它和一样平常英语口语中的this是一个意义吗?
跟着近年js编程不断地庞杂化,功用多样化,关于一个顺序组织的内部指引、援用也逐步变多起来
下面让我们一同来看这一段代码:
Game.prototype.restart = function () { this.clearLocalStorage();
this.timer = setTimeout(function(){ this.clearBoard(); }, 0);
};
运转上面的代码将会涌现以下毛病:
Uncaught TypeError: undefined is not a function
这是为何?this的挪用和它地点的环境密切相关。之所以会涌现上面的毛病,是由于当你在挪用 setTimeout()函数的时刻, 你现实挪用的是window.setTimeout(). 因而,在 setTimeout() 定义的函数现实上是在window背景下定义的,而window中并没有 clearBoard() 这个函数要领。
下面供应两种解决方案。第一种比较简朴直接的要领就是,把this存储到一个变量当中,如许他就可以在差别的环境背景中被继承下来:
Game.prototype.restart = function () { this.clearLocalStorage();
var self = this;
this.timer = setTimeout(function(){ self.clearBoard();}, 0); };
第二种要领就是用bind()的要领,不过这个相比上一种要庞杂一些,关于不熟悉bind()的同砚可以在微软官方检察它的应用要领:https://msdn.microsoft.com/zh-cn/library/ff841995
Game.prototype.restart = function () { this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this), 0); };
Game.prototype.reset = function(){ this.clearBoard();};
上面的例子中,两个this均指代的是Game.prototype。
罕见毛病二:传统编程言语的性命周期误区
另一种易犯的毛病,就是带着其他编程言语的头脑,以为在JS中,也存在性命周期这么一说。请看下面的代码:
for (var i = 0; i < 10; i++) { /* ... */ } console.log(i);
假如你以为在运转console.log() 时肯定会报出 undefined 毛病,那末你就大错特错了。我会通知你实在它会返回 10吗。
固然,在很多其他言语当中,碰到如许的代码,肯定会报错。由于i显著已逾越了它的性命周期。在for中定义的变量在轮回终了后,它的性命也就终了了。然则在js中,i的性命还会继承。这类征象叫做 variable hoisting。
而假如我们想要完成和其他言语一样的在特定逻辑模块中具有性命周期的变量,可以用let关键字。
罕见毛病三:内存泄漏
内存泄漏在js变成中险些是一个没法防备的题目。假如不是迥殊仔细的话,在末了的搜检过程当中,肯定会涌现种种内存泄漏题目。下面我们就来举例申明一下:
var theThing = null;
var replaceThing = function () {
var priorThing = theThing;
var unused = function () {
if (priorThing) { console.log("hi"); }
};
theThing = { longStr: new Array(1000000).join('*'), //
someMethod: function () { console.log(someMessage); }
};
};
setInterval(replaceThing, 1000);
假如运转上面的代码,你会发明你已造成了大批的内存泄漏,每秒泄漏1M的内存,明显光靠GC(渣滓接纳器)是没法协助你的了。由上面的代码来看,似乎是longstr在每次replaceThing挪用的时刻都没有取得接纳。这是为何呢?
每一个theThing组织都含有一个longstr组织列表。每一秒当我们挪用 replaceThing, 它就会把当前的指向通报给 priorThing. 然则到这里我们也会看到并没有什么题目,由于 priorThing 每回也是先解开上次函数的指向才会接收新的赋值。而且一切的这一切都是发作在 replaceThing 函数体当中,按常理来讲当函数体终了以后,函数中的当地变量也将会被GC接纳,也就不会涌现内存泄漏的题目了,然则为何会涌现上面的毛病呢?
这是由于longstr的定义是在一个闭包中举行的,而它又被其他的闭包所援用,js划定,在闭包中引入闭包外部的变量时,当闭包终了时此对象没法被渣滓接纳(GC)。关于在JS中的内存泄漏题目可以检察http://javascript.info/tutorial/memory-leaks#memory-management-in-java…
罕见毛病四:比较运算符
JavaScript中一个比较便利的处所,就是它可以给每一个在比较运算的结果变量强行转化成布尔范例。然则从另一方面来斟酌,有时刻它也会为我们带来很多不轻易,下面的这些例子就是一些一向搅扰很多顺序员的代码实例:
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0); // And these do too!
if ({}) // ...
if ([]) // ...
末了两行的代码虽然前提推断为空(常常会被人误以为转化为false),然则实在不管是{ }照样[ ]都是一个实体类,而任何的类实在都邑转化为true。就像这些例子所展现的那样,实在有些范例强迫转化异常隐约。因而很多时刻我们更情愿用 === 和 !== 来替换== 和 !=, 以此来防备发作强迫范例转化。. ===和!== 的用法和之前的== 和 != 一样,只不过他们不会发作范例强迫转换。别的须要注重的一点是,当任何值与 NaN 比较的时刻,以至包含他本身,结果都是false。因而我们不能用简朴的比较字符来决议一个值是不是为 NaN 。我们可以用内置的 isNaN() 函数来分辨:
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
罕见毛病五:低效的DOM操纵
js中的DOM基础操纵异常简朴,然则怎样能有效地举行这些操纵一向是一个困难。这其中最典范的题目就是批量增添DOM元素。增添一个DOM元素是一步消费很大的操纵。而批量增添对体系的花消更是不菲。一个比较好的批量增添的方法就是应用 document fragments :
var div = document.getElementsByTagName("my_div");
var fragment = document.createDocumentFragment();
for (var e = 0; e < elems.length; e++) { fragment.appendChild(elems[e]); } div.appendChild(fragment.cloneNode(true));
直接增加DOM元素是一个异常高贵的操纵。然则假如是先把要增加的元素悉数建立出来,再把它们悉数增加上去就会高效很多。
罕见毛病六:在for轮回中的不准确函数挪用
请人人看以下代码:
var elements = document.getElementsByTagName('input');
var n = elements.length;
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("This is element #" + i); }; }
运转以上代码,假如页面上有10个按钮的话,点击每一个按钮都邑弹出 “This is element #10”! 。这和我们本来预期的并不一样。这是由于当点击事宜被触发的时刻,for轮回早已实行终了,i的值也已从0变成了。
我们可以经由过程下面这段代码来完成真正准确的结果:
var elements = document.getElementsByTagName('input');
var n = elements.length;
var makeHandler = function(num) { // outer function
return function() {
console.log("This is element #" + num); }; };
for (var i = 0; i < n; i++)
{ elements[i].onclick = makeHandler(i+1); }
在这个版本的代码中, makeHandler 在每回轮回的时刻都邑被马上实行,把i+1通报给变量num。表面的函数返回内里的函数,而点击事宜函数便被设置为内里的函数。如许每一个触发函数就都可以是用准确的i值了。
罕见毛病七:原型继承题目
很大一部分的js开辟者都不能完整控制原型的继承题目。下面具一个例子来讲明:
BaseObject = function(name) {
if(typeof name !== "undefined")
{ this.name = name; }
else
{ this.name = 'default' } };
这段代码看起来很简朴。假如你有name值,则应用它。假如没有,则应用 ‘default’:
var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');
console.log(firstObj.name); // -> 结果是'default'
console.log(secondObj.name); // -> 结果是 'unique'
然则假如我们实行delete语句呢:
delete secondObj.name;
我们会取得:
console.log(secondObj.name); // -> 结果是 'undefined'
然则假如可以从新回到 ‘default’状况不是更好么? 实在要想到达如许的结果很简朴,假如我们可以应用原型继承的话:
BaseObject = function (name)
{ if(typeof name !== "undefined")
{ this.name = name; } };
BaseObject.prototype.name = 'default';
在这个版本中, BaseObject 继承了原型中的name 属性, 被设置为了 ‘default’.。这时候,假如组织函数被挪用时没有参数,则会自动设置为 default。相同地,假如name 属性被从BaseObject移出,体系将会自动寻觅原型链,而且取得 ‘default’值:
var thirdObj = new BaseObject('unique');
console.log(thirdObj.name);
delete thirdObj.name;
console.log(thirdObj.name); // -> 结果是 'default'
罕见毛病八:为实例要领建立毛病的指引
我们来看下面一段代码:
var MyObject = function() {}
MyObject.prototype.whoAmI = function() {
console.log(this === 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" (as expected)
whoAmI(); // 输出 "window" (uh-oh!)
那边出错了呢?
道理实在和上面的第二个罕见毛病一样,当我们实行 var whoAmI = obj.whoAmI;的时刻,新的变量 whoAmI 是在全局环境下定义的。因而它的this 是指window, 而不是obj!
准确的编码体式格局应该是:
var MyObject = function() {}
MyObject.prototype.whoAmI = function() {
console.log(this === window ? "window" : "MyObj"); };
var obj = new MyObject();
obj.w = obj.whoAmI; // still in the obj namespace obj.whoAmI(); // 输出 "MyObj" (as expected)
obj.w(); // 输出 "MyObj" (as expected)
罕见毛病九:用字符串作为setTimeout 或许 setInterval的第一个参数
起首我们要声明,用字符串作为这两个函数的第一个参数并没有什么语法上的毛病。然则实在这是一个异常低效的做法。由于从体系的角度来讲,当你用字符串的时刻,它会被传进组织函数,而且从新挪用另一个函数。如许会拖慢顺序的进度。
setInterval("logTime()", 1000);
setTimeout("logMessage('" + msgValue + "')", 1000);
另一种要领是直接将函数作为参数通报进去:
setInterval(logTime, 1000);
setTimeout(function() {
logMessage(msgValue); }, 1000);
罕见毛病十:疏忽 “strict mode”的作用
“strict mode” 是一种越发严厉的代码搜检机制,而且会让你的代码越发平安。固然,不挑选这个形式并不意味着是一个毛病,然则应用这个形式可以确保你的代码越发准确无误。
下面我们总结几条“strict mode”的上风:
让Debug越发轻易:在一般形式下很多毛病都邑被无视掉,“strict mode”形式会让Debug极致越发严谨。
防备默许的全局变量:在一般形式下,给一个为经由声明的变量定名将会将这个变量自动设置为全局变量。在strict形式下,我们作废了这个默许机制。
作废this的默许转换:在一般形式下,给this关键字指引到null或许undefined会让它自动转换为全局。在strict形式下,我们作废了这个默许机制。
防备反复的变量声明和参数声明:在strict形式下举行反复的变量声明会被抱错,如 (e.g., var object = {foo: “bar”, foo: “baz”};) 同时,在函数声明中反复应用同一个参数称号也会报错,如 (e.g., function foo(val1, val2, val1){}),
让eval()函数越发平安。
当碰到无效的delete指令的预先报错:delete指令不能对类中未有的属性实行,在一般状况下这类状况只是默默地无视掉,而在strict形式是会报错的。
结语
正如和其他的手艺言语一样,你对JavaScript相识的的越深,晓得它是怎样运作,为何如许运作,你才会熟练地控制而且应用这门言语。相反地,假如你缺乏对JS形式的认知的话,你就会碰上很多的题目。相识JS的一些细节上的语法或许功用将会有助于你进步编程的效力,削减变成中碰到的题目。
原文地点:http://www.toptal.com/javascript/10-most-common-javascript-mistakes
译文地点:http://1ke.co/course/136?utm_source=segment&utm_medium=1&utm_c…