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 有一个管道操作符,我们甚至可以像方法一样使用它们。
如有相关前端方面的技术问题 ,欢迎加微信群,我会定期在群里给大家分享最新技术和解答问题 。