译者按: JavaScript语言设计太灵活,用起来不免要多加小心掉进坑里面。

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

如今网站几乎100%使用JavaScript。JavaScript看上去是一门十分简单的语言,然而事实并不如此。它有很多容易被弄错的细节,一不注意就导致BUG。

1. 错误的对this进行引用

在闭包或则回调中,this关键字的作用域很容易弄错。举个例子:

Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(function() {
this.clearBoard(); // 此处this指的是?
}, 0);
};

如果执行上面的代码,我们会看到报错:

Uncaught TypeError: undefined is not a function

出错的原因在于:当你调用setTimeout函数,你实际上调用的是window.setTimeout()。在setTimeout中传入的匿名函数是在window这个对象环境下,所以this是指向window,但是window并没有clearBoard方法。

如何解决呢?定义新的变量引用指向Game对象的this,然后就可以使用啦。

Game.prototype.restart = function () {
this.clearLocalStorage();
var self = this; // 将this指向的对象绑定到self
this.timer = setTimeout(function(){
self.clearBoard();
}, 0);
};

或则使用bind()函数:

Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this'
};
 
Game.prototype.reset = function(){
this.clearBoard(); // 此处this的引用正确
};

2. 和块作用域(block scope)有关的BUG

在大多数程序语言中,每一个函数块都有一个独立的新的作用域,但是在JavaScript中并不是。例如:

for (var i = 0; i < 10; i++) {
/* ... */
}
console.log(i); // 会输出什么呢?

通常在这种情况下,调用console.log()会输出undefined或则报错。不过呢,这里会输出10。在JavaScript中,即使for循环已经结束,变量i依然存在,并且记录最后的值。有些开发者会忘记这一点,然后导致许多bug。我们可以使用let而不是for来杜绝这一问题。

3. 内存泄漏

你需要监控内存使用量,因为泄露很难避免。内存泄露可能由于引用不存在的对象或则循环引用导致。

  • 如何避免:关注对象的可访问性(reachability)。
  • 可访问的对象:
    • 现有的call stack任何位置可以访问的对象
    • 全局对象

当一个对象可以通过引用访问到,那么会在内存中保存。浏览器的垃圾回收器仅仅会把那些不可访问的对象回收。

4. 混淆的相等判断

JavaScript自动将所有在布尔环境下的变量类型转换为布尔类型,但是可能导致bug。举例:

// 所有都是true
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0);
 
// 注意:下面两个也是
if ({}) // …
if ([]) // …

{}[]都是对象,他们都会被转换为true。为了防止bug出现,推荐使用===!==来做比较,因为不会隐式做类型转换。

5. 低效的DOM操作

在JavaScript中,你可以轻松操作DOM(添加、修改和删除),但是开发者往往很低效地去操作。这会导致bug出现,因为这些操作非常耗费计算资源。为了解决这个问题,推荐使用文档碎片(Document Fragment),如果你需要操作多个DOM元素。

广告: 你的线上代码真的没有BUG吗?欢迎免费使用Fundebug!我们可以帮助您第一时间发现BUG!

6. 在for循环中错误的定义函数

举例:

var elements = document.getElementsByTagName('input');
var n = elements.length; // 假设我们有10个元素
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("元素编号#" + i);
};
}

如果我们有10个元素,那么点击任何一个元素都会显示“元素编号#10”!因为在onclick被调用的时候,for循环已经结束,因此所有的i都是10。

解法:

var elements = document.getElementsByTagName('input');
var n = elements.length; // 假设有10个元素
var makeHandler = function(num) { // outer function
return function() { // inner function
console.log("元素编号##" + num);
};
};
for (var i = 0; i < n; i++) {
elements[i].onclick = makeHandler(i+1);
}

makeHandler在for循环执行的时候立即被调用,获取到当前的值i+1,并且存储在变量num中。makeHandler返回一个函数使用num变量,该函数被绑定到元素的点击事件。

7. 通过原型错误地继承

开发者如果没能正确理解继承的原理,那么就可能写出有bug的代码:

BaseObject = function(name) {
if(typeof name !== "undefined") {
this.name = name;
} else {
this.name = 'default'
}
};
var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');
 
console.log(firstObj.name); // -> 输出'default'
console.log(secondObj.name); // -> 输出'unique'

但是,如果我们做如下操作:

delete secondObj.name;

那么:

console.log(secondObj.name); // -> 输出'undefined'

而我们实际上想要的结果是打印默认的name。

BaseObject = function (name) {
if(typeof name !== "undefined") {
this.name = name;
}
};
 
BaseObject.prototype.name = 'default';

每一个BaseObject都继承name属性,并且默认值为default。此时如果secondObjname属性被删除掉,通过原型链查找会返回正确的默认值。

var thirdObj = new BaseObject('unique');
console.log(thirdObj.name); // -> 输出'unique'
 
delete thirdObj.name;
console.log(thirdObj.name); // -> 输出'default'

8. 实例方法中的无效引用

我们来实现一个简单的构造函数用来创建对象:

var MyObject = function() {}
 
MyObject.prototype.whoAmI = function() {
console.log(this === window ? "window" : "MyObj");
};
 
var obj = new MyObject();

为了使用方便,我们定义变量whoAmI来引用obj.whoAmI

var whoAmI = obj.whoAmI;

打印出来看看:

console.log(whoAmI);

控制台会输出:

function () {
console.log(this === window ? "window" : "MyObj");
}

现在我们来对比一下两者调用的区别:

obj.whoAmI(); // 输出"MyObj" (和期望一致)
whoAmI(); // 输出"window" (竟然输出了window)

当我们把obj.whoAmI赋值给whoAmI的时候,这个新的变量whoAmI是定义在全局下,因此this指向全局的window,而不是MyObj。如果我们真的要获取对MyObj的函数的引用,需要在其作用域下。

var MyObject = function() {}
 
MyObject.prototype.whoAmI = function() {
console.log(this === window ? "window" : "MyObj");
};
 
var obj = new MyObject();
obj.w = obj.whoAmI; // 任然在obj的作用域
 
obj.whoAmI(); // 输出"MyObj"
obj.w(); // 输出"MyObj"

9. setTimeout/setInterval函数第一个参数误用字符串

如果你将一个字符串作为setTimeout/setTimeInterval,它会被传给函数构造函数并构建一个新的函数。该操作流程很慢而且低效,并导致bug出现。

var hello = function(){
console.log("hello, fundebug !");
}
setTimeout("hello", 1000);

一个好的替代方法就是传入函数作为参数:

setInterval(logTime, 1000); // 将logTime函数传入
 
setTimeout(function() { // 传入一个匿名函数
logMessage(msgValue);
}, 1000);

10. 未能成功使用strict mode

使用strict model会增加很多限制条件来加强安全和防止某些错误的出现,如果不使用strict mode,你就相当于少了一个得力的助手帮你避免错误:

  • 更加容易debug
  • 避免不小心定义了不该定义的全局变量
  • 避免this隐式转换
  • 避免属性名字或则参数值的重复使用
  • eval()更加安全
  • 无效地使用delete会自动抛出错误

版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/15/top_10_bugs_and_fixing_method/

10个JavaScript常见BUG及修复方法的更多相关文章

  1. IE常见bug及其修复方法

        一.双边距浮动的bug 1.1一段无错的代码把一个居左浮动(float:left)的元素放置进一个容器盒(box) 2.1在浮动元素上使用了左边界(margin-left)来令它和容器的左边产 ...

  2. JavaScript中常见的10个BUG及其修复方法

    如今网站几乎100%使用JavaScript.JavaScript看上去是一门十分简单的语言,然而事实并不如此.它有很多容易被弄错的细节,一不注意就导致BUG. 1. 错误的对this进行引用 在闭包 ...

  3. IE6浏览器常见的bug及其修复方法

    IE6不支持min-height,解决办法使用css hack: .target { min-height: 100px; height: auto !important; height: 100px ...

  4. javascript常见操作数组的方法

    在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "obj ...

  5. 常见web漏洞修复方法

    方法如下: 漏洞修复.(输入过滤,输出转义) 1.在连接数据库时,在接收参数后进行转义,$id = mysql_real_escape_string($id); 2.在网页源码中在接收参数后可用htm ...

  6. 常见浏览器bug(针对IE6及更低版本)及其修复方法

    常见bug及其修复方法有以下几种 1.双外边距浮动bug 双外边距浮动bug在IE6及更低版本中常见.所谓双外边距浮动bug是指使任何浮动元素上的外边距加倍.(见下图) 只要将元素的display属性 ...

  7. JavaScript 调试常见报错以及修复方法

    (看到一篇调试JS很有用的文章,收藏一下) JavaScript 调试是一场噩梦:首先给出的错误非常难以理解,其次给出的行号不总有帮助.有个查找错误含义,及修复措施的列表,是不是很有用? 以下是奇怪的 ...

  8. iOS开发中常见bug!(内附解答方法)

    序言 你是否曾经修复了一个 bug ,随后又发现了一个跟刚修复 bug 有关的 bug ,又或是修复 bug 的方式引起了另一个 bug ? 然而这些问题是绝佳的学习机会.所以我们怎样尽可能多地从修复 ...

  9. javascript常见的20个问题与解决方法

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

随机推荐

  1. vue单页应用前进刷新后退不刷新方案探讨

    引言 前端webapp应用为了追求类似于native模式的细致体验,总是在不断的在向native的体验靠拢:比如本文即将要说到的功能,native由于是多页应用,新页面可以启用一个的新的webview ...

  2. 脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.前言 新一代HTTP/2 协议的主要目的是为了提高网页性能(有关HTTP/2的介绍,请见<从HTTP/0.9到HTTP/2:一文读 ...

  3. C++ 基础知识回顾总结

    一.前言 为啥要写这篇博客?答:之前学习的C和C++相关的知识,早就被自己忘到一边去了.但是,随着音视频的学习的不断深入,和C/C++打交道的次数越来越多,看代码是没问题的,但是真到自己操刀去写一些代 ...

  4. FragmentTabHost用法

    FragmentTabHost组成 Tabhost,TabWidget,切换的内容容器FrameLayout 层级关系 ----FragmentTabHost |-----TabWidget |--- ...

  5. 跟繁琐的命令行说拜拜!Gerapy分布式爬虫管理框架来袭!

    背景 用 Python 做过爬虫的小伙伴可能接触过 Scrapy,GitHub:https://github.com/scrapy/scrapy.Scrapy 的确是一个非常强大的爬虫框架,爬取效率高 ...

  6. 迷宫-BFS

    迷宫问题 Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Submit Status Descript ...

  7. IntelliJ IDEA 使用前常用设置

    0.设置位置 以下设置基于IntelliJ IDEA 2018.3.2 版本. IDEA 的设置一般都在 File 下的 Settings... 里进行设置的. 1.设置字体字号行间距 2.设置背景图 ...

  8. Metasploit Framework(2)Exploit模块、Payload使用

    文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 Exploit模块分为主动和被动(Active.Pas ...

  9. [Postman]定制Postman(4)

    自定义请求方法 您可以在Postman中自定义请求方法以满足特定要求.创建自己的请求方法后,您将能够发送/保存它们. 此功能允许您保存/删除自定义方法,还可以删除默认方法.单击请求方法下拉区域,键入方 ...

  10. getComputedStyle与currentStyle获取样式

    转载自:https://segmentfault.com/a/1190000007477785 CSS的样式分为三类: 内嵌样式:是写在标签里面的,内嵌样式只对所在的标签有效内部样式:是写在HTML里 ...