【CSDN 編者按】
從1995年開始,本文作者Dr.Axel Rauschmayer就專門從事JAVAScript和Web開發(fā),已經(jīng)有30多年了。2010年,他獲得慕尼黑大學(xué)信息學(xué)博士學(xué)位。自2011年以來,他一直在2ality.com寫博客,并寫了幾本關(guān)于JavaScript的書,比如《JavaScript for impatient programmers》、《Deep JavaScript: Theory and techniques》等。今天這篇文章就來自于他的博客,介紹了在JavaScript命名沖突時,現(xiàn)有代碼如何強(qiáng)制對提議的功能進(jìn)行重命名。
整理 | 章雨銘 責(zé)編 | 張紅月
出品 | CSDN(ID:CSDNnews)
不斷發(fā)展的JavaScript:不要破壞web!
JavaScript的一個發(fā)展核心原則就是"不要破壞Web":在將新特性添加到語言中后,所有現(xiàn)有代碼都必須能夠繼續(xù)運(yùn)行。
這樣有一個壞處,就是不能從語言中刪除現(xiàn)有的quirks。但這樣做益處多多,比如舊的代碼可以繼續(xù)運(yùn)行,而且升級到新的ECMAScript版本很簡便等等。
在為新特征(如方法名稱)選擇名稱時,需要進(jìn)行一個重要的測試,即在瀏覽器的nightly版本(早期預(yù)發(fā)布版本)中添加該特征,并檢查是否有任何網(wǎng)站出現(xiàn)錯誤。
接下來將介紹過去案例中的的四個沖突源,當(dāng)產(chǎn)生這四種沖突時,就必須重命名特征。
沖突源1:向內(nèi)置原型添加方法
在JavaScript中,我們可以通過改變其原型來為內(nèi)置值添加方法:
// Creating a new Array methodArray.prototype.myArrayMethod = function {return this.join('-');};assert.equal(['a', 'b', 'c'].myArrayMethod, 'a-b-c');// Creating a new string methodString.prototype.myStringMethod = function {return '¡' + this + '!';};assert.equal('Hola'.myStringMethod, '¡Hola!');
神奇的是,語言可以通過這種方式改變。這種運(yùn)行時的修改被稱為猴子補(bǔ)丁(monkey patch)。
什么是猴子補(bǔ)丁?
如果我們給內(nèi)置原型添加方法,我們就是在運(yùn)行時修改一個軟件系統(tǒng)。這樣的修改被稱為猴子補(bǔ)丁。簡單來說,對其含義有兩種可能的解釋。
這個叫法起源于Zope框架,人們在修正Zope的Bug的時候經(jīng)常在程序后面追加更新部分,這些被稱作是“雜牌軍補(bǔ)丁(guerilla patch)”,后來guerilla就漸漸的寫成了gorllia((猩猩),再后來就寫了monkey(猴子),所以猴子補(bǔ)丁的叫法是這么莫名其妙的得來的。
另一種說法是,它指的是搞亂(monkeying about)代碼。
反對改變內(nèi)置原型的原因
對任何類型的全局命名,都會存在名稱沖突的風(fēng)險。如果有解決沖突的機(jī)制,就能規(guī)避風(fēng)險。例如:
-
全局模塊是通過裸模塊指定器或URLs來識別的。前者之間的名稱沖突可以通過npm注冊表來解決。后者之間的名稱沖突可以通過域名注冊處來解決。
-
可以通過將符號添加到JavaScript中,以避免方法之間的名稱沖突。例如,任何對象都可以通過添加一個鍵為.NET的方法而成為可迭代的。由于每個符號都是唯一的,所以這個鍵永遠(yuǎn)不會與任何其他屬性鍵.
Symbol.iterator發(fā)生沖突。
然而,帶有字符串鍵的方法會導(dǎo)致名稱沖突:
-
不同的庫可能會對他們添加到
.Array.prototype的方法使用相同的名字。 -
如果一個名字已經(jīng)被某個庫使用了,那么這個名稱就不能用于命名JavaScript標(biāo)準(zhǔn)庫的一個新特性。
具有諷刺意味的是,謹(jǐn)慎地添加一個方法可能會適得其反:
if (!Array.prototype.libraryMethod) {Array.prototype.libraryMethod = function { /*...*/ };}
我們會檢查一個方法是否已經(jīng)存在。如果沒有,我們就添加它。
如果我們要實(shí)現(xiàn)一個polyfill(模擬原生Web平臺功能),將新的JavaScript方法添加到不支持它的引擎中,那么這個技術(shù)就能發(fā)揮作用。(順便說一下,這是修改內(nèi)置原型的一個合法用例。也許是唯一的一個)。
然而,如果我們對一個普通庫的方法使用這種技術(shù),然后JavaScript獲取具有相同名稱的方法,那么這兩種實(shí)現(xiàn)的工作方式就不一樣了,并且使用庫方法的所有代碼在使用內(nèi)置方法時都會中斷。
必須更改名稱的原型方法示例
-
ES6的方法最初是與JavaScript框架MooTools(錯誤報告)
.String.prototype.includes.contains全局添加的方法相沖突。 -
ES2016的方法最初是與MooTools(錯誤報告 )
.Array.prototype.includes.contains添加的方法相沖突。 -
ES2019的方法最初是和MooTools(錯誤報告, 博客文章)
.Array.prototype.flat.flatten相沖突。
修改內(nèi)置原型并不總是糟糕的
你可能會對MooTools的創(chuàng)建者的粗心大意感到疑惑。但是,向內(nèi)置原型添加方法并不總是糟糕的。在ES3(1999年12月)和ES5(2009年12月)之間,JavaScript是一種停滯不前的語言。MooTools和Prototype等框架改進(jìn)了它。這些方法的缺點(diǎn)只有在JavaScript的標(biāo)準(zhǔn)庫再次增加之后才會凸顯出來。
沖突源2:檢查一個屬性的存在
ES2022的方法最初是.NET的。因?yàn)橐韵聨鞕z查屬性以確定對象是否是一個html集合(而不是一個數(shù)組),所以它必須被重新命名:Magic360、YUI 2、YUI 3.Array.prototype.at.item.item
沖突源3:檢查全局變量是否存在
自ES2020以來,我們可以通過globalThis訪問全局對象。Node.js一直使用該名稱來實(shí)現(xiàn)此目的。最初的計劃是為所有平臺標(biāo)準(zhǔn)化該名稱.global
然而,以下模式經(jīng)常被用來確定當(dāng)前平臺:
if (typeof global !== 'undefined') {// We are not running on Node.js}
如果瀏覽器也有一個名為.global的全局變量,這種模式(以及類似的模式)就會失效。因此,標(biāo)準(zhǔn)化的名稱被改為.globalglobalThis。
沖突源4:通過創(chuàng)建局部變量with語句
JavaScript的聲明with語句
長期以來,人們一直不鼓勵使用JavaScript的with語句,甚至在ES5中引入的嚴(yán)格模式中也被定為非法。在其他地方,嚴(yán)格模式在ECMAScript模塊中是活躍的。
該語句將一個對象的屬性變成局部變量:with
cons t myObject = {ownProperty: 'yes',};with (myObject) {// Own properties become local variablesassert.equal(ownProperty, 'yes');// Inherited properties become local variables, tooassert.equal(typeof toString, 'function');}
由with語句引起的沖突
框架Ext.js使用的代碼與下面的片段有些相似點(diǎn):
function myFunc(values) {with (values) {console.log(values); // (A)}}myFunc([]); // (B)
當(dāng)ES6方法被添加到JavaScript中時,如果用Array(B行)來調(diào)用它,它就會失效。該語句將Array的所有屬性變成了局部變量。其中一個是繼承的屬性。因此,A行中的語句已記錄,不再是參數(shù)(錯誤報告1,錯誤報告2)
Array.prototype.valuesmyFuncwithvalues.valuesArray.prototype.valuesvalue
Unscopables:防止with導(dǎo)致的沖突
公共符號Symbol.unscopables允許對象隱藏語句中的某些屬性。它只在標(biāo)準(zhǔn)庫中使用一次,對于Array.prototype:with
assert.deepEqual(Array.prototype[Symbol.unscopables],{__proto__: ,at: true,copyWithin: true,entries: true,fill: true,find: true,findIndex: true,flat: true,flatMap: true,includes: true,keys: true,values: true,});
結(jié)論
以上提出了JavaScript結(jié)構(gòu)與現(xiàn)有代碼發(fā)生名稱沖突的四種方式:
-
向內(nèi)置原型添加方法
-
檢查屬性是否存在
-
檢查全局變量是否存在
-
創(chuàng)建局部變量with
沖突的某些來源很難預(yù)測,但存在以下一些一般規(guī)則:
-
不要更改全局?jǐn)?shù)據(jù)。
-
避免檢查是否存在全局?jǐn)?shù)據(jù)。
-
請注意,內(nèi)置值將來可能會獲得其他屬性(自己的或繼承的屬性)。
對于庫來說,為JavaScript值提供功能的最安全方法是通過函數(shù)。如果JavaScript得到一個pipe operator,我們也可以像方法一樣使用它們。
參考資料:
https://2ality.com/2022/03/naming-conflicts.html
—END—






