JavaScript 命名發生衝突時,如何對現有程式碼進行強制重新命名
JavaScript 命名衝突:現有程式碼如何強制對有問題的名稱進行重新命名
有時,提議的特性(方法、全域性變數等)的名稱與現有程式碼衝突,必須更改。本次分享將解釋了這種情況是如何發生的,並列出了重新命名的功能。
目 錄:
- 不斷髮展的 JavaScript:不要破壞網路!
- 衝突來源:向內建原型新增方法
- 術語:猴子補丁(monkey patch )
- 反對更改內建原型的原因
- 必須更改名稱的提議原型方法示例
- 修改內建原型並不總是被認為是不好的風格
- 衝突來源:檢查屬性的存在
- 衝突來源:檢查是否存在全域性變數
- 衝突來源:通過 with 建立區域性變數
- JavaScript 的 with 語句
- 由於與的衝突
- Unscopables:防止由 with 引起的衝突
- 結論
1、不斷髮展的 JavaScript:不要破壞網路!
發展 JavaScript 的一個核心原則是不要“破壞網路”:在語言中新增新功能後,所有現有程式碼必須繼續工作。
缺點是無法從語言中刪除現有的怪癖。但好處是相當大的:舊程式碼繼續工作,升級到新的 ECMAScript 版本很簡單,等等。
有關此主題的更多資訊,請參閱“針對不耐煩的程式設計師的 JavaScript”中的“不斷髮展的 JavaScript:不要破壞網路”部分。
當為新功能(例如方法名稱)選擇名稱時,一項重要的測試是將該功能新增到瀏覽器的夜間版本(早期預釋出)中,並檢查是否有任何網站出現錯誤。接下來的部分介紹了四種衝突來源,過去就是這種情況,並且必須重新命名功能。
2、衝突來源:向內建原型新增方法
在 JavaScript 中,我們可以通過改變它們的原型來為內建值新增方法:
// 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!');
可以以這種方式更改語言是令人著迷的。這種執行時修改稱為猴子補丁。下一小節將解釋該術語。然後我們將看看這種修改的缺點。
2.1、術語:猴子補丁
如果我們向內建原型新增方法,我們就是在執行時修改軟體系統。這種修改稱為猴子補丁。我儘量避免使用行話,包括這個術語,但瞭解它是件好事。它的含義有兩種可能的解釋(引用百科):
-
它來自“較早的術語guerrilla patch,它指的是在執行時偷偷改變程式碼 - 並且可能與其他此類補丁不相容。游擊隊這個詞,與大猩猩(或幾乎如此)諧音,變成了猴子,可能是為了讓補丁聽起來不那麼嚇人。”
-
它“指的是用程式碼‘胡鬧’(弄亂它)。”
2.2、反對改變內建原型的原因
對於任何型別的全域性名稱空間,總是存在名稱衝突的風險。當有解決衝突的機制時,這種風險就會消失——例如:
-
全域性模組通過裸模組說明符或 URL標識。通過 npm 登錄檔防止前者之間的名稱衝突。後者之間的名稱衝突可以通過域名註冊來防止。
-
符號被新增到 JavaScript 以避免方法之間的名稱衝突。例如,任何物件都可以通過新增鍵為 的方法變得可迭代Symbol.iterator。由於每個符號都是唯一的,因此該鍵永遠不會與任何其他屬性鍵發生衝突。
但是,帶有字串鍵的方法可能會導致名稱衝突:
-
不同的庫可能對它們新增到的方法使用相同的名稱Array.prototype。
-
如果某個名稱已被任何地方的庫使用,則它不能再用於 JavaScript 標準庫的新功能。在幾種情況下,這是一個問題。它們將在下一節中描述。
具有諷刺意味的是,小心新增方法會使事情變得更糟——例如:
if (!Array.prototype.libraryMethod) { Array.prototype.libraryMethod = function () { /*...*/ };}
在這裡,我們檢查一個方法是否已經存在。如果沒有,我們新增它。如果我們正在實現一個向不支援它的引擎新增新的 JavaScript 方法的polyfill ,這種技術就可以工作。(順便說一句,這是修改內建原型的合法用例。也許是唯一的用例。)
但是,如果我們將這種技術用於普通的庫方法,而 JavaScript 稍後獲得了一個同名的方法,那麼這兩種實現的工作方式不同,並且所有使用庫方法的程式碼在使用內建方法時都會中斷。
2.3、必須更改名稱的提議原型方法的示例
-
ES6 方法String.prototype.includes()最初是,它與JavaScript 框架 MooTools.contains()全域性新增的方法發生衝突(錯誤報告)。
-
ES2016 方法Array.prototype.includes()最初.contains()與 MooTools 新增的方法發生衝突(錯誤報告)。
-
ES2019 方法Array.prototype.flat()最初.flatten()與 MooTools 發生衝突(錯誤報告,部落格文章)。
2.4、修改內建原型並不總是被認為是不好的風格
您可能想知道:MooTools 的建立者怎麼會如此粗心?然而,向內建原型新增方法並不總是被認為是不好的風格。在 ES3(1999 年 12 月)和 ES5(2009 年 12 月)之間,JavaScript 是一種停滯不前的語言。
MooTools 和 Prototype 等框架對其進行了改進。在 JavaScript 的標準庫再次增長之後,他們方法的缺點才變得明顯。
3、衝突來源:檢查屬性是否存在
ES2022 方法Array.prototype.at()最初是.item(). 它必須重新命名,因為以下庫檢查屬性.item以確定物件是否是 HTML 集合(而不是陣列):Magic360、YUI 2、YUI 3(提案中的相關部分)。
4、衝突來源:檢查是否存在全域性變數
從 ES2020 開始,我們可以通過globalThis. Node.js 一直global為此使用這個名稱。最初的計劃是為所有平臺標準化該名稱。
但是,經常使用以下模式來確定當前平臺:
if (typeof global !== 'undefined') { // We are not running on Node.js}
如果瀏覽器也有一個名為global. 因此,標準化名稱改為globalThis。
5、衝突來源:通過with #建立區域性變數
5.1、JavaScript的with宣告
長期以來一直不鼓勵使用JavaScript 的with語句,甚至在 ECMAScript 5 中引入的嚴格模式中也被認定為非法。在其他地方,嚴格模式在 ECMAScript 模組中處於活動狀態。
該with語句將物件的屬性轉換為區域性變數:
const myObject = { ownProperty: 'yes',};
with (myObject) { // Own properties become local variables assert.equal( ownProperty, 'yes' );
// Inherited properties become local variables, too assert.equal( typeof toString, 'function' );}
5.2、由於with #引起的衝突
Ext.js 框架使用的程式碼與以下片段大致相似:
function myFunc(values) { with (values) { console.log(values); // (A) }}myFunc([]); // (B)
當 ES6 方法Array.prototype.values()被新增到 JavaScript 中時,myFunc()如果使用 Array 呼叫它就會中斷(B 行):該with語句將 Array 的所有屬性都values轉換為區域性變數。其中之一是繼承財產.values。因此,記錄 A 行中的語句Array.prototype.values,不再是引數values(錯誤報告 1,錯誤報告 2)。
5.3、Unscopables:防止由with
公共符號Symbol.unscopables允許物件從with語句中隱藏一些屬性。它在標準庫中只使用一次,用於Array.prototype:
assert.deepEqual( Array.prototype[Symbol.unscopables], { __proto__: null, at: true, copyWithin: true, entries: true, fill: true, find: true, findIndex: true, flat: true, flatMap: true, includes: true, keys: true, values: true, });
unscopables 列表包括values在其旁邊或之後引入的方法。
結論 :
我們已經看到提議的 JavaScript 構造與現有程式碼發生名稱衝突的四種方式:
-
向內建原型新增方法
-
檢查屬性是否存在
-
檢查全域性變數是否存在
-
通過建立區域性變數with
-
一些衝突來源難以預測,但存在一些一般規則:
-
不要更改全域性資料。
-
避免檢查全域性資料是否存在。
-
請注意,內建值將來可能會獲得其他屬性(自己的或繼承的)。
庫為 JavaScript 值提供功能的最安全方法是通過函式。如果 JavaScript 有一個管道操作符,我們甚至可以像方法一樣使用它們。
如有相關前端方面的技術問題 ,歡迎加微信群,我會定期在群裡給大家分享最新技術和解答問題 。