如何投稿小说到各大网站,网站新闻列表页设计,仿站是什么,广东建设集团在众多的编程语言中#xff0c; JavaScript 给大部分的人的第一印象是人畜无害#xff0c;看起来就简单的#xff0c;对稍微有点儿开发经验的人来说#xff0c;在网页中写个JavaScript功能也相当简单。但是当你真的得了解了JavaScript之后就会发现#xff0c;它比我们想象…在众多的编程语言中 JavaScript 给大部分的人的第一印象是人畜无害看起来就简单的对稍微有点儿开发经验的人来说在网页中写个JavaScript功能也相当简单。但是当你真的得了解了JavaScript之后就会发现它比我们想象中强大复杂的多。
也是因为 JavaScript 的复杂和细致让他很容易就出问题。我们就今天就准备讨论一下在前端开发中的10个常见的问题。
时至今日JavaScript 几乎成为了所有现代 Web 应用的核心。而把JavaScript研究明白找到JavaScript出问题的原因也就成了一名合格前端的首要任务。
基于 JavaScript 的强大库和框架用于单页应用程序SPA开发、图形和动画、以及服务器端 JavaScript 平台这些并不是什么新鲜事儿。JavaScript 已经成为 Web 应用程序开发领域中无处不在的语言因此掌握 JavaScript 成为了一个日益重要的技能。
一、不正确的引用this
随着JavaScript的日益发展编码技术和设计模式越来越复杂回调和闭包中的自引用作用域也相应增加。在我们日常开发中一定的会遇到this/that 混乱 的问题。
考虑下面代码 Game.prototype.restart function () { this.clearLocalStorage(); this.timer setTimeout(function() { this.clearBoard(); // What is this? }, 0);};
执行上述代码会出现以下错误:
Uncaught TypeError: undefined is not a function
上述错误的原因是当调用 setTimeout()时实际上是在调用 window.setTimeout()。因此传递给setTimeout()的匿名函数是在window对象的上下文中定义的它没有clearBoard()方法。
传统的、符合老式浏览器的解决方案是将 this 引用保存在一个变量中然后可以被闭包继承如下所示
Game.prototype.restart function () { this.clearLocalStorage(); var self this; // Save reference to this, while its still this! this.timer setTimeout(function(){ self.clearBoard(); // Oh OK, I do know who self is! }, 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(); // Ahhh, back in the context of the right this!}; 二认为存在块级作用域 JavaScript开发者中常见的混乱来源也是常见的错误来源是假设JavaScript为每个代码块创建一个新的作用域。尽管这在许多其他语言中是对的但在JavaScript中却不是。
考虑一下下面的代码
for (var i 0; i 10; i) { /* ... */}console.log(i); // 输出什么
如果你猜测console.log()的调用会输出 undefined 或者抛出一个错误那你就猜错了。答案是输出10。为什么呢
在大多数其他语言中上面的代码会导致一个错误因为变量i的 生命即使作用域会被限制在for块中。但在JavaScript中情况并非如此即使在for循环完成后变量i仍然在作用域内在退出循环后仍保留其最后的值。(顺便说一下这种行为被称为变量提升variable hoisting。
JavaScript中对块级作用域的支持是通过let关键字实现的。Let关键字已经被浏览器和Node.js等后端JavaScript引擎广泛支持了多年。
三创建内存泄漏
如果没有有意识地编写代码来避免内存泄漏那么内存泄漏几乎是不可避免的JavaScript问题。它们的发生方式有很多种所以我们只重点介绍几种比较常见的情况。
内存泄漏实例:对不存在的对象的悬空引用
考虑以下代码:
var theThing null;var replaceThing function () { var priorThing theThing; var unused function () { // unused是priorThing被引用的唯一地方。 // 但unused从未被调用过 if (priorThing) { console.log(hi); } }; theThing { longStr: new Array(1000000).join(*), // 创建一个1MB的对象 someMethod: function () { console.log(someMessage); } };};setInterval(replaceThing, 1000); // 每秒钟调用一次 replaceThing。如果你运行上述代码并监测内存使用情况你会发现你有一个明显的内存泄漏每秒泄漏整整一兆字节而即使是手动垃圾收集器GC也无济于事。因此看起来我们每次调用 replaceThing 都会泄漏 longStr。但是为什么呢
每个theThing对象包含它自己的1MB longStr对象。每一秒钟当我们调用 replaceThing 时它都会在 priorThing 中保持对先前 theThing 对象的引用。
但是我们仍然认为这不会是一个问题因为每次通过先前引用的priorThing将被取消引用当priorThing通过priorThing theThing;被重置时。而且只在 replaceThing 的主体和unused的函数中被引用而事实上从未被使用。
因此我们又一次想知道为什么这里会有内存泄漏。
为了理解发生了什么我们需要更好地理解JavaScript的内部工作。实现闭包的典型方式是每个函数对象都有一个链接到代表其词法作用域的字典式对象。如果在replaceThing里面定义的两个函数实际上都使用了priorThing那么它们都得到了相同的对象就很重要即使priorThing被反复赋值所以两个函数都共享相同的词法环境。但是一旦一个变量被任何闭包使用它就会在该作用域内所有闭包共享的词法环境中结束。而这个小小的细微差别正是导致这个可怕的内存泄露的原因。
避免内存泄漏:要点
JavaScript的内存管理尤其是垃圾回收主要是基于对象可达性的概念。
以下对象被认为是可达的被称为 根:
● 从当前调用堆栈的任何地方引用的对象即当前被调用的函数中的所有局部变量和参数以及闭包作用域内的所有变量
● 所有全局变量
只要对象可以通过引用或引用链从任何一个根部访问它们就会被保留在内存中。
浏览器中有一个垃圾收集器它可以清理被无法到达的对象所占用的内存换句话说当且仅当GC认为对象无法到达时才会将其从内存中删除。不幸的是很容易出现不再使用的 僵尸 对象但GC仍然认为它们是 可达的。 四双等号的困惑
JavaScript 的一个便利之处在于它会自动将布尔上下文中引用的任何值强制为布尔值。但在有些情况下这可能会让人困惑因为它很方便。例如下面的一些情况对许多JavaScript开发者来说是很麻烦的。
// 下面结果都是 trueconsole.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)); // True 五低效的DOM操作 使用 JavaScript 操作DOM即添加、修改和删除元素是相对容易但操作效率却不怎么样。
比如每次添加一系列DOM元素。添加一个DOM元素是一个昂贵的操作。连续添加多个DOM元素的代码是低效的。
当需要添加多个DOM元素时一个有效的替代方法是使用 document fragments来代替从而提高效率和性能。
var div document.getElementsByTagName(my_div);
var fragment document.createDocumentFragment();
for (var e 0; e elems.length; e) { // elems previously set to list of elements fragment.appendChild(elems[e]);}div.appendChild(fragment.cloneNode(true));
除了这种方法固有的效率提高外创建附加的DOM元素是很昂贵的而在分离的情况下创建和修改它们然后再将它们附加上就会产生更好的性能。 六在循环内错误使用函数定义
考虑下面代码
var elements document.getElementsByTagName(input);var n elements.length; // Assume we have 10 elements for this examplefor (var i 0; i n; i) { elements[i].onclick function() { console.log(This is element # i); };}
根据上面的代码如果有10个 input 元素点击任何一个都会显示 This is element #10。这是因为当任何一个元素的onclick被调用时上面的for循环已经结束i的值已经是10了对于所有的元素。
我们可以像下面这样来解决这个问题
var elements document.getElementsByTagName(input);var n elements.length; var makeHandler function(num) { return function() { console.log(This is element # num); };};for (var i 0; i n; i) { elements[i].onclick makeHandler(i1);}
makeHandler 是一个外部函数并返回一个内部函数这样就会形成一个闭包num 就会调用时传进来的的当时值这样在点击元素时就能显示正确的序号。
七未能正确利用原型继承
考虑下面代码
BaseObject function(name) { if (typeof name ! undefined) { this.name name; } else { this.name default }};
上面代码比较简单就是提供了一个名字就使用它否则返回 default:
var firstObj new BaseObject();var secondObj new BaseObject(unique);
console.log(firstObj.name); // - defaultconsole.log(secondObj.name); // - unique
但是如果这么做呢: delete secondObj.name;
会得到
console.log(secondObj.name); // undefined
当使用 delete 删除该属性时就会返回一个 undefined那么如果我们也想返回 default 要怎么做呢利用原型继承如下所示:
BaseObject function (name) { if(typeof name ! undefined) { this.name name; }};
BaseObject.prototype.name default;
BaseObject 从它的原型对象中继承了name 属性值为 default。因此如果构造函数在没有 name 的情况下被调用name 将默认为 default。同样如果 name 属性从BaseObject的一个实例中被移除那么会找到原型链的 name其值仍然是default。所以
var thirdObj new BaseObject(unique);console.log(thirdObj.name); // - Results in unique
delete thirdObj.name;console.log(thirdObj.name); // - Results in 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);
为了确保没有问题我们把 whoAmI 打印出来看一下 console.log(whoAmI);
输出
function () { console.log(this window ? window : MyObj);}
看起来没啥问题。
接着看看当我们调用obj.whoAmI() 和 whoAmI() 的区别。
obj.whoAmI(); // Outputs MyObj (as expected)whoAmI(); // Outputs window (uh-oh!)
什么地方出错了当我们进行赋值时 var whoAmI obj.whoAmI,新的变量whoAmI被定义在全局命名空间。结果this的值是 window而不是 MyObject 的 obj 实例!
因此如果我们真的需要为一个对象的现有方法创建一个引用我们需要确保在该对象的名字空间内进行以保留 this值。一种方法是这样做:
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(); // Outputs MyObj (as expected)obj.w(); // Outputs MyObj (as expected) 九为 setTimeout 或 setInterval 提供一个字符串作为第一个参数
首先需要知道的是为 setTimeout 或 setInterval 提供一个字符串作为第一个参数这本身并不是一个错误。它是完全合法的JavaScript代码。这里的问题更多的是性能和效率的问题。很少有人解释的是如果你把字符串作为setTimeout或setInterval的第一个参数它将被传递给函数构造器被转换成一个新函数。这个过程可能很慢效率也很低而且很少有必要。
将一个字符串作为这些方法的第一个参数的替代方法是传入一个函数
setInterval(logTime(), 1000);setTimeout(logMessage( msgValue ), 1000);
更好的选择是传入一个函数作为初始参数:
setInterval(logTime, 1000);
setTimeout(function() { logMessage(msgValue); }, 1000); 十未使用 严格模式 严格模式即在JavaScript源文件的开头包括 use strict是一种自愿在运行时对JavaScript代码执行更严格的解析和错误处理的方式同时也使它更安全。
但是不使用严格模式本身并不是一个 错误但它的使用越来越受到鼓励不使用也越来越被认为是不好的形式。
以下是严格模式的一些主要好处
● 使得调试更容易。原本会被忽略或无感知的代码错误现在会产生错误或抛出异常提醒我们更快地发现代码库中的JavaScript问题并引导更快地找到其来源。
● 防止意外的全局变量。在没有严格模式的情况下给一个未声明的变量赋值会自动创建一个具有该名称的全局变量。这是最常见的JavaScript错误之一。在严格模式下试图这样做会产生一个错误。
● 消除this 强迫性。在没有严格模式的情况下对 null 或 undefined 的 this 值的引用会自动被强制到全局。在严格模式下引用null或undefined的this值会产生错误。
● 不允许重复的属性名或参数值。严格模式在检测到一个对象中的重复命名的属性例如var object {foo: bar, foo: baz};或一个函数的重复命名的参数例如function foo(val1, val2, val1){}时抛出一个错误从而捕捉到你的代码中几乎肯定是一个错误否则你可能会浪费很多时间去追踪。
● 使得eval()更加安全。eval()在严格模式和非严格模式下的行为方式有一些不同。最重要的是在严格模式下在eval()语句中声明的变量和函数不会在包含的范围内创建。(在非严格模式下它们是在包含域中创建的这也可能是JavaScript问题的一个常见来源)。
● 在无效使用delete的情况下抛出错误。delete 操作符用于从对象中删除属性不能用于对象的非可配置属性。当试图删除一个不可配置的属性时非严格的代码将无声地失败而严格模式在这种情况下将抛出一个错误。
代码部署后可能存在的BUG没法实时知道事后为了解决这些BUG花了大量的时间进行log 调试这边顺便给大家推荐一个好用的BUG监控工具。