自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

十個(gè)常見的 JavaScript 問題

開發(fā) 前端
如今,JavaScript幾乎是所有現(xiàn)代web應(yīng)用程序的核心。這就是為什么JavaScript問題以及找出導(dǎo)致這些問題的錯(cuò)誤是web開發(fā)人員的首要任務(wù)。

如今,JavaScript幾乎是所有現(xiàn)代web應(yīng)用程序的核心。這就是為什么JavaScript問題以及找出導(dǎo)致這些問題的錯(cuò)誤是web開發(fā)人員的首要任務(wù)。

用于單頁應(yīng)用程序(SPA)開發(fā)、圖形和動畫以及服務(wù)器端JavaScript平臺的強(qiáng)大的基于JavaScript的庫和框架并不是什么新鮮事。JavaScript在web應(yīng)用程序開發(fā)的世界中確實(shí)變得無處不在,因此它是一項(xiàng)越來越重要的技能。

起初,JavaScript可能看起來很簡單。事實(shí)上,將基本的JavaScript功能構(gòu)建到網(wǎng)頁中對于任何有經(jīng)驗(yàn)的軟件開發(fā)人員來說都是一項(xiàng)相當(dāng)簡單的任務(wù),即使他們是JavaScript新手。

然而,這種語言比人們最初認(rèn)為的要微妙、有力和復(fù)雜得多。事實(shí)上,JavaScript的許多微妙之處導(dǎo)致了許多常見問題,這些問題阻礙了它的工作——我們在這里討論了其中的10個(gè)問題,在成為一名優(yōu)秀的JavaScript開發(fā)人員的過程中,需要注意和避免這些問題。

問題1:不正確的引用 this

隨著JavaScript編碼技術(shù)和設(shè)計(jì)模式多年來變得越來越復(fù)雜,回調(diào)和閉包中的自引用作用域也相應(yīng)增加,這是造成JavaScript問題的 "this/that 混亂 "的一個(gè)相當(dāng)普遍的來源。

考慮下面代碼:

Game.prototype.restart = function () {
this.clearLocalStorage();
this.timer = setTimeout(function() {
this.clearBoard(); // What is "this"?
}, 0);
};

執(zhí)行上述代碼會出現(xiàn)以下錯(cuò)誤:

Uncaught TypeError: undefined is not a function

上述錯(cuò)誤的原因是,當(dāng)調(diào)用 setTimeout()時(shí),實(shí)際上是在調(diào)用 window.setTimeout()。因此,傳遞給setTimeout()的匿名函數(shù)是在window對象的上下文中定義的,它沒有clearBoard()方法。

傳統(tǒng)的、符合老式瀏覽器的解決方案是將 this 引用保存在一個(gè)變量中,然后可以被閉包繼承,如下所示:

Game.prototype.restart = function () {
this.clearLocalStorage();
var self = this; // Save reference to 'this', while it's still this!
this.timer = setTimeout(function(){
self.clearBoard(); // Oh OK, I do know who 'self' is!
}, 0);
};

另外,在較新的瀏覽器中,可以使用bind()方法來傳入適當(dāng)?shù)囊茫?/span>

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'!
};

問題2:認(rèn)為存在塊級作用域

JavaScript開發(fā)者中常見的混亂來源(也是常見的錯(cuò)誤來源)是假設(shè)JavaScript為每個(gè)代碼塊創(chuàng)建一個(gè)新的作用域。盡管這在許多其他語言中是對的,但在JavaScript中卻不是??紤]一下下面的代碼:?

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

如果你猜測console.log()的調(diào)用會輸出 undefined 或者拋出一個(gè)錯(cuò)誤,那你就猜錯(cuò)了。答案是輸出10。為什么呢?

在大多數(shù)其他語言中,上面的代碼會導(dǎo)致一個(gè)錯(cuò)誤,因?yàn)樽兞縤的 "生命"(即使作用域)會被限制在for塊中。

但在JavaScript中,情況并非如此,即使在for循環(huán)完成后,變量i仍然在作用域內(nèi),在退出循環(huán)后仍保留其最后的值。(順便說一下,這種行為被稱為變量提升(variable hoisting)。

JavaScript中對塊級作用域的支持是通過let關(guān)鍵字實(shí)現(xiàn)的。Let關(guān)鍵字已經(jīng)被瀏覽器和Node.js等后端JavaScript引擎廣泛支持了多年。

問題3:創(chuàng)建內(nèi)存泄漏

如果沒有有意識地編寫代碼來避免內(nèi)存泄漏,那么內(nèi)存泄漏幾乎是不可避免的JavaScript問題。它們的發(fā)生方式有很多種,所以我們只重點(diǎn)介紹幾種比較常見的情況。

內(nèi)存泄漏實(shí)例1:對不存在的對象的懸空引用

考慮以下代碼:?


var theThing = null;
var replaceThing = function () {
var priorThing = theThing;
var unused = function () {
// 'unused'是'priorThing'被引用的唯一地方。
// 但'unused'從未被調(diào)用過
if (priorThing) {
console.log("hi");
}
};
theThing = {
longStr: new Array(1000000).join('*'), // 創(chuàng)建一個(gè)1MB的對象
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000); // 每秒鐘調(diào)用一次 "replaceThing"。

如果你運(yùn)行上述代碼并監(jiān)測內(nèi)存使用情況,你會發(fā)現(xiàn)你有一個(gè)明顯的內(nèi)存泄漏,每秒泄漏整整一兆字節(jié)!而即使是手動垃圾收集器(GC)也無濟(jì)于事。

因此,看起來我們每次調(diào)用 replaceThing 都會泄漏 longStr。但是為什么呢?

每個(gè)theThing對象包含它自己的1MB longStr對象。每一秒鐘,當(dāng)我們調(diào)用 replaceThing 時(shí),它都會在 priorThing 中保持對先前 theThing 對象的引用。

但是我們?nèi)匀徽J(rèn)為這不會是一個(gè)問題,因?yàn)槊看瓮ㄟ^,先前引用的priorThing將被取消引用(當(dāng)priorThing通過priorThing = theThing;被重置時(shí))。

而且,只在 replaceThing 的主體和unused的函數(shù)中被引用,而事實(shí)上,從未被使用。

因此,我們又一次想知道為什么這里會有內(nèi)存泄漏。

為了理解發(fā)生了什么,我們需要更好地理解JavaScript的內(nèi)部工作。實(shí)現(xiàn)閉包的典型方式是,每個(gè)函數(shù)對象都有一個(gè)鏈接到代表其詞法作用域的字典式對象。

如果在replaceThing里面定義的兩個(gè)函數(shù)實(shí)際上都使用了priorThing,那么它們都得到了相同的對象就很重要,即使priorThing被反復(fù)賦值,所以兩個(gè)函數(shù)都共享相同的詞法環(huán)境。

但是一旦一個(gè)變量被任何閉包使用,它就會在該作用域內(nèi)所有閉包共享的詞法環(huán)境中結(jié)束。而這個(gè)小小的細(xì)微差別正是導(dǎo)致這個(gè)可怕的內(nèi)存泄露的原因。

內(nèi)存泄漏實(shí)例2:循環(huán)引用

考慮下面代碼:?

function addClickHandler(element) {
element.click = function onClick(e) {
alert("Clicked the " + element.nodeName)
}
}

這里,onClick有一個(gè)閉包,保持對element的引用(通過element.nodeName)。通過將onClick分配給element.click,循環(huán)引用被創(chuàng)建;即: element → onClick → element → onClick → element...

有趣的是,即使 element 被從dom中移除,上面的循環(huán)自引用也會阻止 element 和onClick被收集,因此會出現(xiàn)內(nèi)存泄漏。

避免內(nèi)存泄漏:要點(diǎn)

JavaScript的內(nèi)存管理(尤其是垃圾回收)主要是基于對象可達(dá)性的概念。

以下對象被認(rèn)為是可達(dá)的,被稱為 "根":

  • 從當(dāng)前調(diào)用堆棧的任何地方引用的對象(即當(dāng)前被調(diào)用的函數(shù)中的所有局部變量和參數(shù),以及閉包作用域內(nèi)的所有變量)
  • 所有全局變量

只要對象可以通過引用或引用鏈從任何一個(gè)根部訪問,它們就會被保留在內(nèi)存中。

瀏覽器中有一個(gè)垃圾收集器,它可以清理被無法到達(dá)的對象所占用的內(nèi)存;換句話說,當(dāng)且僅當(dāng)GC認(rèn)為對象無法到達(dá)時(shí),才會將其從內(nèi)存中刪除。不幸的是,很容易出現(xiàn)不再使用的 "僵尸 "對象,但GC仍然認(rèn)為它們是 "可達(dá)的"。

問題4:雙等號的困惑

JavaScript 的一個(gè)便利之處在于,它會自動將布爾上下文中引用的任何值強(qiáng)制為布爾值。

但在有些情況下,這可能會讓人困惑,因?yàn)樗芊奖?。例如,下面的一些情況對許多JavaScript開發(fā)者來說是很麻煩的。?

// 下面結(jié)果都是 'true'
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0);

// 下面也都成立
if ({}) // ...
if ([]) // ...

關(guān)于最后兩個(gè),盡管是空的(大家可能會覺得他們是 false),{}和[]實(shí)際上都是對象,任何對象在JavaScript中都會被強(qiáng)制為布爾值 "true",這與ECMA-262規(guī)范一致。

正如這些例子所表明的,類型強(qiáng)制的規(guī)則有時(shí)非常清楚。因此,除非明確需要類型強(qiáng)制,否則最好使用===和!==(而不是==和!=),以避免強(qiáng)制類型轉(zhuǎn)換的帶來非預(yù)期的副作用。(== 和 != 會自動進(jìn)行類型轉(zhuǎn)換,而 === 和 !== 則相反)

另外需要注意的是:將NaN與任何東西(甚至是NaN)進(jìn)行比較時(shí)結(jié)果都是 false。

因此,不能使用雙等運(yùn)算符(==, ==, !=, !==)來確定一個(gè)值是否是NaN。如果需要,可以使用內(nèi)置的全局 isNaN()函數(shù)。?

console.log(NaN == NaN);    // False
console.log(NaN === NaN); // False
console.log(isNaN(NaN)); // True

問題5:低效的DOM操作

使用 JavaScript 操作DOM(即添加、修改和刪除元素)是相對容易,但操作效率卻不怎么樣。

比如,每次添加一系列DOM元素。添加一個(gè)DOM元素是一個(gè)昂貴的操作。連續(xù)添加多個(gè)DOM元素的代碼是低效的。

當(dāng)需要添加多個(gè)DOM元素時(shí),一個(gè)有效的替代方法是使用 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));

除了這種方法固有的效率提高外,創(chuàng)建附加的DOM元素是很昂貴的,而在分離的情況下創(chuàng)建和修改它們,然后再將它們附加上,就會產(chǎn)生更好的性能。

問題6:在循環(huán)內(nèi)錯(cuò)誤使用函數(shù)定義

考慮下面代碼:?

var elements = document.getElementsByTagName('input');
var n = elements.length; // Assume we have 10 elements for this example
for (var i = 0; i < n; i++) {
elements[i].onclick = function() {
console.log("This is element #" + i);
};
}

根據(jù)上面的代碼,如果有10個(gè) input 元素,點(diǎn)擊任何一個(gè)都會顯示 "This is element #10"。
這是因?yàn)?,?dāng)任何一個(gè)元素的onclick被調(diào)用時(shí),上面的for循環(huán)已經(jīng)結(jié)束,i的值已經(jīng)是10了(對于所有的元素)。

我們可以像下面這樣來解決這個(gè)問題:?


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(i+1);
}

makeHandler 是一個(gè)外部函數(shù),并返回一個(gè)內(nèi)部函數(shù),這樣就會形成一個(gè)閉包,num 就會調(diào)用時(shí)傳進(jìn)來的的當(dāng)時(shí)值,這樣在點(diǎn)擊元素時(shí),就能顯示正確的序號。

問題7:未能正確利用原型繼承

考慮下面代碼:

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

上面代碼比較簡單,就是提供了一個(gè)名字,就使用它,否則返回 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'

當(dāng)使用 delete 刪除該屬性時(shí),就會返回一個(gè) undefined,那么如果我們也想返回 default 要怎么做呢?利用原型繼承,如下所示:


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

BaseObject.prototype.name = 'default';

BaseObject 從它的原型對象中繼承了name 屬性,值為 default。因此,如果構(gòu)造函數(shù)在沒有 name 的情況下被調(diào)用,name 將默認(rèn)為 default。同樣,如果 name 屬性從BaseObject的一個(gè)實(shí)例中被移除,那么會找到原型鏈的 name,,其值仍然是default。所以'?

var thirdObj = new BaseObject('unique');
console.log(thirdObj.name); // -> Results in 'unique'

delete thirdObj.name;
console.log(thirdObj.name); // -> Results in 'default'

問題8:為實(shí)例方法創(chuàng)建錯(cuò)誤的引用

考慮下面代碼:?

var MyObject = function() {}

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

var obj = new MyObject();

現(xiàn)在,為了操作方便,我們創(chuàng)建一個(gè)對whoAmI方法的引用,這樣通過whoAmI()而不是更長的obj.whoAmI()來調(diào)用。?

var whoAmI = obj.whoAmI;

為了確保沒有問題,我們把 whoAmI 打印出來看一下:

console.log(whoAmI);

輸出:

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

Ok,看起來沒啥問題。

接著,看看當(dāng)我們調(diào)用obj.whoAmI() 和 whoAmI() 的區(qū)別。

obj.whoAmI();  // Outputs "MyObj" (as expected)
whoAmI(); // Outputs "window" (uh-oh!)

什么地方出錯(cuò)了?當(dāng)我們進(jìn)行賦值時(shí) var whoAmI = obj.whoAmI,新的變量whoAmI被定義在全局命名空間。

結(jié)果,this的值是 window,而不是 MyObject 的 obj 實(shí)例!

因此,如果我們真的需要為一個(gè)對象的現(xiàn)有方法創(chuàng)建一個(gè)引用,我們需要確保在該對象的名字空間內(nèi)進(jìn)行,以保留 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) // Outputs "MyObj" (as expected)

問題9:為 setTimeout 或 setInterval 提供一個(gè)字符串作為第一個(gè)參數(shù)

首先,需要知道的是為 setTimeout 或 setInterval 提供一個(gè)字符串作為第一個(gè)參數(shù),這本身并不是一個(gè)錯(cuò)誤。它是完全合法的JavaScript代碼。這里的問題更多的是性能和效率的問題。

很少有人解釋的是,如果你把字符串作為setTimeout或setInterval的第一個(gè)參數(shù),它將被傳遞給函數(shù)構(gòu)造器,被轉(zhuǎn)換成一個(gè)新函數(shù)。這個(gè)過程可能很慢,效率也很低,而且很少有必要。

將一個(gè)字符串作為這些方法的第一個(gè)參數(shù)的替代方法是傳入一個(gè)函數(shù)。?

setInterval("logTime()", 1000);
setTimeout("logMessage('" + msgValue + "')", 1000);

更好的選擇是傳入一個(gè)函數(shù)作為初始參數(shù):


setInterval(logTime, 1000);

setTimeout(function() {
logMessage(msgValue);
}, 1000);

問題10:未使用 "嚴(yán)格模式"

"嚴(yán)格模式"(即在JavaScript源文件的開頭包括 "use strict";)是一種自愿在運(yùn)行時(shí)對JavaScript代碼執(zhí)行更嚴(yán)格的解析和錯(cuò)誤處理的方式,同時(shí)也使它更安全。

但是,不使用嚴(yán)格模式本身并不是一個(gè) "錯(cuò)誤",但它的使用越來越受到鼓勵,不使用也越來越被認(rèn)為是不好的形式。

以下是嚴(yán)格模式的一些主要好處:

  • 使得調(diào)試更容易。原本會被忽略或無感知的代碼錯(cuò)誤,現(xiàn)在會產(chǎn)生錯(cuò)誤或拋出異常,提醒我們更快地發(fā)現(xiàn)代碼庫中的JavaScript問題,并引導(dǎo)更快地找到其來源。
  • 防止意外的全局變量。在沒有嚴(yán)格模式的情況下,給一個(gè)未聲明的變量賦值會自動創(chuàng)建一個(gè)具有該名稱的全局變量。這是最常見的JavaScript錯(cuò)誤之一。在嚴(yán)格模式下,試圖這樣做會產(chǎn)生一個(gè)錯(cuò)誤。
  • 消除this 強(qiáng)迫性。在沒有嚴(yán)格模式的情況下,對 null 或 undefined 的 this 值的引用會自動被強(qiáng)制到全局。在嚴(yán)格模式下,引用null或undefined的this值會產(chǎn)生錯(cuò)誤。
  • 不允許重復(fù)的屬性名或參數(shù)值。嚴(yán)格模式在檢測到一個(gè)對象中的重復(fù)命名的屬性(例如,var object = {foo: "bar", foo: "baz"};)或一個(gè)函數(shù)的重復(fù)命名的參數(shù)(例如,function foo(val1, val2, val1){})時(shí)拋出一個(gè)錯(cuò)誤,從而捕捉到你的代碼中幾乎肯定是一個(gè)錯(cuò)誤,否則你可能會浪費(fèi)很多時(shí)間去追蹤。
  • 使得eval()更加安全。eval()在嚴(yán)格模式和非嚴(yán)格模式下的行為方式有一些不同。最重要的是,在嚴(yán)格模式下,在eval()語句中聲明的變量和函數(shù)不會在包含的范圍內(nèi)創(chuàng)建。(在非嚴(yán)格模式下,它們是在包含域中創(chuàng)建的,這也可能是JavaScript問題的一個(gè)常見來源)。
  • 在無效使用delete的情況下拋出錯(cuò)誤。delete 操作符(用于從對象中刪除屬性)不能用于對象的非可配置屬性。當(dāng)試圖刪除一個(gè)不可配置的屬性時(shí),非嚴(yán)格的代碼將無聲地失敗,而嚴(yán)格模式在這種情況下將拋出一個(gè)錯(cuò)誤。

寫在最后

以上就是我今天跟你分享的10個(gè)JavaScript中最常見的問題,不知道這10個(gè)問題中有沒有你不知道?如果有的話,請認(rèn)真學(xué)習(xí),如沒有的話,請當(dāng)作復(fù)習(xí)。

另外就是如果你覺得從今天的文章中學(xué)習(xí)到了新知識并且也覺得有用的話,請記得點(diǎn)贊我,關(guān)系我,并將這篇文章與你的好朋友一起來分享它。

最后,感謝你的閱讀,編程快樂!

責(zé)任編輯:華軒 來源: web前端開發(fā)
相關(guān)推薦

2024-09-24 07:57:55

SQL錯(cuò)誤??EXPLAIN?

2023-05-28 22:48:29

程序員編程

2011-09-14 09:58:18

云計(jì)算

2022-07-31 23:54:24

Linux操作系統(tǒng)

2023-10-16 07:55:15

JavaScript對象技巧

2022-07-31 23:53:37

Linux操作系統(tǒng)設(shè)備

2023-12-22 16:48:00

Kubernetes容器集群

2011-06-09 16:44:28

SEO

2010-03-04 16:09:09

2024-03-04 16:32:02

JavaScript運(yùn)算符

2023-09-06 07:22:48

控制臺UI工具

2025-03-18 12:00:00

閉包JavaScript前端

2023-07-14 14:25:00

Python語言錯(cuò)誤

2025-03-18 14:27:35

2023-02-10 16:36:30

機(jī)器學(xué)習(xí)評估指標(biāo)

2023-10-04 00:03:00

SQL數(shù)據(jù)庫

2023-08-03 16:14:06

JavaScriptAPI

2022-07-07 09:19:24

JavaScript代碼樣式規(guī)則

2023-06-14 15:51:48

JavaScript

2024-12-02 14:28:17

JavaScriptWeb開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號