十個(gè)開(kāi)發(fā)人員面臨的最常見(jiàn)的JavaScript問(wèn)題總結(jié)
今天,JavaScript 是幾乎所有現(xiàn)代 Web 應(yīng)用的核心。這就是為什么JavaScript問(wèn)題,以及找到導(dǎo)致這些問(wèn)題的錯(cuò)誤,是 Web 發(fā)者的首要任務(wù)。
用于單頁(yè)應(yīng)用程序(SPA)開(kāi)發(fā)、圖形和動(dòng)畫(huà)以及服務(wù)器端JavaScript平臺(tái)的強(qiáng)大的基于JavaScript的庫(kù)和框架已不是什么新鮮事。在 Web 應(yīng)用程序開(kāi)發(fā)的世界里,JavaScript確實(shí)已經(jīng)無(wú)處不在,因此是一項(xiàng)越來(lái)越重要的技能,需要掌握。
起初,JavaScript 看起來(lái)很簡(jiǎn)單。事實(shí)上,對(duì)于任何有經(jīng)驗(yàn)的前端開(kāi)發(fā)人員來(lái)說(shuō),在網(wǎng)頁(yè)中建立基本的JavaScript功能是一項(xiàng)相當(dāng)簡(jiǎn)單的任務(wù),即使他們是JavaScript新手。然而,這種語(yǔ)言比人們最初認(rèn)為的要細(xì)致、強(qiáng)大和復(fù)雜得多。事實(shí)上,JavaScript的許多微妙之處導(dǎo)致了許多常見(jiàn)的問(wèn)題,這些問(wèn)題使它無(wú)法工作--我們?cè)谶@里討論了其中的10個(gè)問(wèn)題--在尋求成為JavaScript開(kāi)發(fā)大師的過(guò)程中,這些問(wèn)題是需要注意和避免的。
問(wèn)題1:不正確的引用 this
隨著JavaScript編碼技術(shù)和設(shè)計(jì)模式多年來(lái)變得越來(lái)越復(fù)雜,回調(diào)和閉包中的自引用作用域也相應(yīng)增加,這是造成JavaScript問(wèn)題的 "this/that 混亂 "的一個(gè)相當(dāng)普遍的來(lái)源。
考慮下面代碼:
Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // What is "this"? }, 0); };
執(zhí)行上述代碼會(huì)出現(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
對(duì)象的上下文中定義的,它沒(méi)有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()
方法來(lái)傳入適當(dāng)?shù)囊茫?/p>
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'! };
問(wèn)題2:認(rèn)為存在塊級(jí)作用域
JavaScript開(kāi)發(fā)者中常見(jiàn)的混亂來(lái)源(也是常見(jiàn)的錯(cuò)誤來(lái)源)是假設(shè)JavaScript為每個(gè)代碼塊創(chuàng)建一個(gè)新的作用域。盡管這在許多其他語(yǔ)言中是對(duì)的,但在JavaScript中卻不是??紤]一下下面的代碼:
for (var i = 0; i < 10; i++) { /* ... */ } console.log(i); // 輸出什么?
如果你猜測(cè)console.log()
的調(diào)用會(huì)輸出 undefined
或者拋出一個(gè)錯(cuò)誤,那你就猜錯(cuò)了。答案是輸出10
。為什么呢?
在大多數(shù)其他語(yǔ)言中,上面的代碼會(huì)導(dǎo)致一個(gè)錯(cuò)誤,因?yàn)樽兞?code>i的 "生命"(即使作用域)會(huì)被限制在for
塊中。但在JavaScript中,情況并非如此,即使在for循環(huán)完成后,變量i
仍然在作用域內(nèi),在退出循環(huán)后仍保留其最后的值。(順便說(shuō)一下,這種行為被稱(chēng)為變量提升(variable hoisting)。
JavaScript中對(duì)塊級(jí)作用域的支持是通過(guò)let
關(guān)鍵字實(shí)現(xiàn)的。Let
關(guān)鍵字已經(jīng)被瀏覽器和Node.js等后端JavaScript引擎廣泛支持了多年。
問(wèn)題3:創(chuàng)建內(nèi)存泄漏
如果沒(méi)有有意識(shí)地編寫(xiě)代碼來(lái)避免內(nèi)存泄漏,那么內(nèi)存泄漏幾乎是不可避免的JavaScript問(wèn)題。它們的發(fā)生方式有很多種,所以我們只重點(diǎn)介紹幾種比較常見(jiàn)的情況。
內(nèi)存泄漏實(shí)例1:對(duì)不存在的對(duì)象的懸空引用
考慮以下代碼:
var theThing = null; var replaceThing = function () { var priorThing = theThing; var unused = function () { // 'unused'是'priorThing'被引用的唯一地方。 // 但'unused'從未被調(diào)用過(guò) if (priorThing) { console.log("hi"); } }; theThing = { longStr: new Array(1000000).join('*'), // 創(chuàng)建一個(gè)1MB的對(duì)象 someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000); // 每秒鐘調(diào)用一次 "replaceThing"。
如果你運(yùn)行上述代碼并監(jiān)測(cè)內(nèi)存使用情況,你會(huì)發(fā)現(xiàn)你有一個(gè)明顯的內(nèi)存泄漏,每秒泄漏整整一兆字節(jié)!而即使是手動(dòng)垃圾收集器(GC)也無(wú)濟(jì)于事。因此,看起來(lái)我們每次調(diào)用 replaceThing
都會(huì)泄漏 longStr
。但是為什么呢?
每個(gè)theThing
對(duì)象包含它自己的1MB longStr
對(duì)象。每一秒鐘,當(dāng)我們調(diào)用 replaceThing
時(shí),它都會(huì)在 priorThing
中保持對(duì)先前 theThing
對(duì)象的引用。
但是我們?nèi)匀徽J(rèn)為這不會(huì)是一個(gè)問(wèn)題,因?yàn)槊看瓮ㄟ^(guò),先前引用的priorThing
將被取消引用(當(dāng)priorThing
通過(guò)priorThing = theThing;
被重置時(shí))。而且,只在 replaceThing
的主體和unused
的函數(shù)中被引用,而事實(shí)上,從未被使用。
因此,我們又一次想知道為什么這里會(huì)有內(nèi)存泄漏。
為了理解發(fā)生了什么,我們需要更好地理解JavaScript的內(nèi)部工作。實(shí)現(xiàn)閉包的典型方式是,每個(gè)函數(shù)對(duì)象都有一個(gè)鏈接到代表其詞法作用域的字典式對(duì)象。如果在replaceThing
里面定義的兩個(gè)函數(shù)實(shí)際上都使用了priorThing,
那么它們都得到了相同的對(duì)象就很重要,即使priorThing
被反復(fù)賦值,所以兩個(gè)函數(shù)都共享相同的詞法環(huán)境。但是一旦一個(gè)變量被任何閉包使用,它就會(huì)在該作用域內(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è)閉包,保持對(duì)element
的引用(通過(guò)element.nodeName
)。通過(guò)將onClick
分配給element.click
,循環(huán)引用被創(chuàng)建;即: element
→ onClick
→ element
→ onClick
→ element
...
有趣的是,即使 element
被從DOM中移除,上面的循環(huán)自引用也會(huì)阻止 element 和onClick
被收集,因此會(huì)出現(xiàn)內(nèi)存泄漏。
避免內(nèi)存泄漏:要點(diǎn)
JavaScript的內(nèi)存管理(尤其是垃圾回收)主要是基于對(duì)象可達(dá)性的概念。
以下對(duì)象被認(rèn)為是可達(dá)的,被稱(chēng)為 "根":
- 從當(dāng)前調(diào)用堆棧的任何地方引用的對(duì)象(即當(dāng)前被調(diào)用的函數(shù)中的所有局部變量和參數(shù),以及閉包作用域內(nèi)的所有變量)
- 所有全局變量
只要對(duì)象可以通過(guò)引用或引用鏈從任何一個(gè)根部訪問(wèn),它們就會(huì)被保留在內(nèi)存中。
瀏覽器中有一個(gè)垃圾收集器,它可以清理被無(wú)法到達(dá)的對(duì)象所占用的內(nèi)存;換句話說(shuō),當(dāng)且僅當(dāng)GC認(rèn)為對(duì)象無(wú)法到達(dá)時(shí),才會(huì)將其從內(nèi)存中刪除。不幸的是,很容易出現(xiàn)不再使用的 "僵尸 "對(duì)象,但GC仍然認(rèn)為它們是 "可達(dá)的"。
問(wèn)題4:雙等號(hào)的困惑
JavaScript 的一個(gè)便利之處在于,它會(huì)自動(dòng)將布爾上下文中引用的任何值強(qiáng)制為布爾值。但在有些情況下,這可能會(huì)讓人困惑,因?yàn)樗芊奖?。例如,下面的一些情況對(duì)許多JavaScript開(kāi)發(fā)者來(lái)說(shuō)是很麻煩的。
// 下面結(jié)果都是 'true' console.log(false == '0'); console.log(null == undefined); console.log(" \t\r\n" == 0); console.log('' == 0); // 下面也都成立 if ({}) // ... if ([]) // ...
關(guān)于最后兩個(gè),盡管是空的(大家可能會(huì)覺(jué)得他們是 false),{}
和[]
實(shí)際上都是對(duì)象,任何對(duì)象在JavaScript中都會(huì)被強(qiáng)制為布爾值 "true"
,這與ECMA-262規(guī)范一致。
正如這些例子所表明的,類(lèi)型強(qiáng)制的規(guī)則有時(shí)非常清楚。因此,除非明確需要類(lèi)型強(qiáng)制,否則最好使用===
和!==
(而不是==
和!=
),以避免強(qiáng)制類(lèi)型轉(zhuǎn)換的帶來(lái)非預(yù)期的副作用。(==
和 !=
會(huì)自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)換,而 ===
和 !==
則相反)
另外需要注意的是:將NaN
與任何東西(甚至是NaN
)進(jìn)行比較時(shí)結(jié)果都是 false
。因此,不能使用雙等運(yùn)算符(==
, ==
, !=
, !==
)來(lái)確定一個(gè)值是否是NaN
。如果需要,可以使用內(nèi)置的全局 isNaN()
函數(shù)。
console.log(NaN == NaN); // False console.log(NaN === NaN); // False console.log(isNaN(NaN)); // True
問(wèn)題5:低效的DOM操作
使用 JavaScript 操作DOM(即添加、修改和刪除元素)是相對(duì)容易,但操作效率卻不怎么樣。
比如,每次添加一系列DOM元素。添加一個(gè)DOM元素是一個(gè)昂貴的操作。連續(xù)添加多個(gè)DOM元素的代碼是低效的。
當(dāng)需要添加多個(gè)DOM元素時(shí),一個(gè)有效的替代方法是使用 document fragments
來(lái)代替,從而提高效率和性能。
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)建和修改它們,然后再將它們附加上,就會(huì)產(chǎn)生更好的性能。
問(wè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è)都會(huì)顯示 "This is element #10"
。 這是因?yàn)?,?dāng)任何一個(gè)元素的onclick
被調(diào)用時(shí),上面的for
循環(huán)已經(jīng)結(jié)束,i
的值已經(jīng)是10
了(對(duì)于所有的元素)。
我們可以像下面這樣來(lái)解決這個(gè)問(wèn)題:
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ù),這樣就會(huì)形成一個(gè)閉包,num
就會(huì)調(diào)用時(shí)傳進(jìn)來(lái)的的當(dāng)時(shí)值,這樣在點(diǎn)擊元素時(shí),就能顯示正確的序號(hào)。
問(wèn)題7:未能正確利用原型繼承
考慮下面代碼:
BaseObject = function(name) { if (typeof name !== "undefined") { this.name = name; } else { this.name = 'default' } };
上面代碼比較簡(jiǎn)單,就是提供了一個(gè)名字,就使用它,否則返回 default
:
var firstObj = new BaseObject(); var secondObj = new BaseObject('unique'); console.log(firstObj.name); // -> 'default' console.log(secondObj.name); // -> 'unique'
但是,如果這么做呢:
delete secondObj.name;
會(huì)得到:
console.log(secondObj.name); // 'undefined'
當(dāng)使用 delete
刪除該屬性時(shí),就會(huì)返回一個(gè) undefined
,那么如果我們也想返回 default
要怎么做呢?利用原型繼承,如下所示:
BaseObject = function (name) { if(typeof name !== "undefined") { this.name = name; } }; BaseObject.prototype.name = 'default';
BaseObject
從它的原型對(duì)象中繼承了name
屬性,值為 default
。因此,如果構(gòu)造函數(shù)在沒(méi)有 name
的情況下被調(diào)用,name
將默認(rèn)為 default
。同樣,如果 name
屬性從BaseObject
的一個(gè)實(shí)例中被移除,那么會(huì)找到原型鏈的 name
,,其值仍然是default
。所以'
var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // -> Results in 'unique' delete thirdObj.name; console.log(thirdObj.name); // -> Results in 'default'
問(wèn)題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è)對(duì)whoAmI
方法的引用,這樣通過(guò)whoAmI()
而不是更長(zhǎng)的obj.whoAmI()
來(lái)調(diào)用。
var whoAmI = obj.whoAmI;
為了確保沒(méi)有問(wèn)題,我們把 whoAmI
打印出來(lái)看一下:
console.log(whoAmI);
輸出:
function () { console.log(this === window ? "window" : "MyObj"); }
Ok,看起來(lái)沒(méi)啥問(wèn)題。
接著,看看當(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è)對(duì)象的現(xiàn)有方法創(chuàng)建一個(gè)引用,我們需要確保在該對(duì)象的名字空間內(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)
問(wèn)題9:為 setTimeout 或 setInterval 提供一個(gè)字符串作為第一個(gè)參數(shù)
首先,需要知道的是為 setTimeout
或 setInterval
提供一個(gè)字符串作為第一個(gè)參數(shù),這本身并不是一個(gè)錯(cuò)誤。它是完全合法的JavaScript代碼。這里的問(wèn)題更多的是性能和效率的問(wèn)題。很少有人解釋的是,如果你把字符串作為setTimeout
或setInterval
的第一個(gè)參數(shù),它將被傳遞給函數(shù)構(gòu)造器,被轉(zhuǎn)換成一個(gè)新函數(shù)。這個(gè)過(guò)程可能很慢,效率也很低,而且很少有必要。
將一個(gè)字符串作為這些方法的第一個(gè)參數(shù)的替代方法是傳入一個(gè)函數(shù)。
setInterval("logTime()", 1000); setTimeout("logMessage('" + msgValue + "')", 1000);
更好的選擇是傳入一個(gè)函數(shù)作為初始參數(shù):
setInterval(logTime, 1000); setTimeout(function() { logMessage(msgValue); }, 1000);
問(wèn)題10:未使用 "嚴(yán)格模式"
"嚴(yán)格模式"(即在JavaScript源文件的開(kāi)頭包括 "use strict"
;)是一種自愿在運(yùn)行時(shí)對(duì)JavaScript代碼執(zhí)行更嚴(yán)格的解析和錯(cuò)誤處理的方式,同時(shí)也使它更安全。
但是,不使用嚴(yán)格模式本身并不是一個(gè) "錯(cuò)誤",但它的使用越來(lái)越受到鼓勵(lì),不使用也越來(lái)越被認(rèn)為是不好的形式。
以下是嚴(yán)格模式的一些主要好處:
- 使得調(diào)試更容易。原本會(huì)被忽略或無(wú)感知的代碼錯(cuò)誤,現(xiàn)在會(huì)產(chǎn)生錯(cuò)誤或拋出異常,提醒我們更快地發(fā)現(xiàn)代碼庫(kù)中的JavaScript問(wèn)題,并引導(dǎo)更快地找到其來(lái)源。
- 防止意外的全局變量。在沒(méi)有嚴(yán)格模式的情況下,給一個(gè)未聲明的變量賦值會(huì)自動(dòng)創(chuàng)建一個(gè)具有該名稱(chēng)的全局變量。這是最常見(jiàn)的JavaScript錯(cuò)誤之一。在嚴(yán)格模式下,試圖這樣做會(huì)產(chǎn)生一個(gè)錯(cuò)誤。
- 消除this 強(qiáng)迫性。在沒(méi)有嚴(yán)格模式的情況下,對(duì)
null
或undefined
的this
值的引用會(huì)自動(dòng)被強(qiáng)制到全局。在嚴(yán)格模式下,引用null
或undefined
的this
值會(huì)產(chǎn)生錯(cuò)誤。 - 不允許重復(fù)的屬性名或參數(shù)值。嚴(yán)格模式在檢測(cè)到一個(gè)對(duì)象中的重復(fù)命名的屬性(例如,
var object = {foo: "bar", foo: "baz"};
)或一個(gè)函數(shù)的重復(fù)命名的參數(shù)(例如,function foo(val1, val2, val1){}
)時(shí)拋出一個(gè)錯(cuò)誤,從而捕捉到你的代碼中幾乎肯定是一個(gè)錯(cuò)誤,否則你可能會(huì)浪費(fèi)很多時(shí)間去追蹤。 - 使得
eval()
更加安全。eval()
在嚴(yán)格模式和非嚴(yán)格模式下的行為方式有一些不同。最重要的是,在嚴(yán)格模式下,在eval()
語(yǔ)句中聲明的變量和函數(shù)不會(huì)在包含的范圍內(nèi)創(chuàng)建。(在非嚴(yán)格模式下,它們是在包含域中創(chuàng)建的,這也可能是JavaScript問(wèn)題的一個(gè)常見(jiàn)來(lái)源)。 - 在無(wú)效使用
delete的
情況下拋出錯(cuò)誤。delete
操作符(用于從對(duì)象中刪除屬性)不能用于對(duì)象的非可配置屬性。當(dāng)試圖刪除一個(gè)不可配置的屬性時(shí),非嚴(yán)格的代碼將無(wú)聲地失敗,而嚴(yán)格模式在這種情況下將拋出一個(gè)錯(cuò)誤。
以上就是十個(gè)開(kāi)發(fā)人員面臨的最常見(jiàn)的JavaScript問(wèn)題總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript問(wèn)題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于JavaScript實(shí)現(xiàn)貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了基于JavaScript實(shí)現(xiàn)貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03手機(jī)端點(diǎn)擊圖片放大特效PhotoSwipe.js插件實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了手機(jī)端點(diǎn)擊圖片放大特效PhotoSwipe.js插件實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08JavaScript實(shí)現(xiàn)滑塊驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)滑塊驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Event altKey,ctrlKey,shiftKey屬性解析
本篇文章主要是對(duì)Event altKey,ctrlKey,shiftKey屬性解析了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12js實(shí)現(xiàn)從數(shù)組里隨機(jī)獲取元素
這篇文章主要介紹了js實(shí)現(xiàn)從數(shù)組里隨機(jī)獲取元素的方法,以及個(gè)人封裝的js代碼分享,十分的實(shí)用,這里推薦給小伙伴們2015-01-01javaScript中一些常見(jiàn)的數(shù)據(jù)類(lèi)型檢查校驗(yàn)
最近在面試的時(shí)候又被問(wèn)到JS中檢查校驗(yàn)數(shù)據(jù)類(lèi)型的方法,所以這篇文章主要給大家介紹了關(guān)于javaScript中一些常見(jiàn)的數(shù)據(jù)類(lèi)型檢查校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2022-05-05JavaScript開(kāi)發(fā)者必備的10個(gè)Sublime Text插件
Sublime Text幾乎是任何開(kāi)發(fā)者在其工具箱的必備應(yīng)用程序,這篇文章主要介紹了JavaScript開(kāi)發(fā)者必備的10個(gè)Sublime Text插件,感興趣的小伙伴們可以參考一下2016-02-02JavaScript MutationObserver實(shí)例講解
MutationObserver用來(lái)監(jiān)視DOM變動(dòng)。DOM的任何變動(dòng),比如節(jié)點(diǎn)增減、屬性的變動(dòng)、文本內(nèi)容的變動(dòng)都會(huì)觸發(fā)MutationObserver事件,它與事件有一個(gè)本質(zhì)不同:事件是同步觸發(fā),MutationObserver則是異步觸發(fā),DOM的變動(dòng)并不會(huì)馬上觸發(fā),而是要等到當(dāng)前所有DOM操作都結(jié)束才觸發(fā)2022-12-12