JavaScript 命名冲突:现有代码如何强制对有问题的名称进行重命名

有时,提议的特性(方法、全局变量等)的名称与现有代码冲突,必须更改。本次分享将解释了这种情况是如何发生的,并列出了重命名的功能。

 

目   录:

 

  • 不断发展的 JavaScript:不要破坏网络!  
  1. 冲突来源:向内置原型添加方法  
  2. 术语:猴子补丁(monkey patch  )  
  3. 反对更改内置原型的原因  
  4. 必须更改名称的提议原型方法示例  
  5. 修改内置原型并不总是被认为是不好的风格  
  • 冲突来源:检查属性的存在  
  • 冲突来源:检查是否存在全局变量  
  • 冲突来源:通过 with 创建局部变量  
  1. JavaScript 的 with 语句  
  2. 由于与的冲突  
  3. 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 有一个管道操作符,我们甚至可以像方法一样使用它们。

 

如有相关前端方面的技术问题 ,欢迎加微信群,我会定期在群里给大家分享最新技术和解答问题 。