JavaScript 命名发生冲突时,如何对现有代码进行强制重命名

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

1.jpg

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

目 录

  • 不断发展的 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 method
Array.prototype.myArrayMethod = function () {
  return this.join('-');
};
assert.equal(
  ['a', 'b', 'c'].myArrayMethod(), 'a-b-c'
);

// Creating a new string method
String.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 稍后获得了一个同名的方法,那么这两种实现的工作方式不同,并且所有使用库方法的代码在使用内置方法时都会中断。

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 有一个管道操作符,我们甚至可以像方法一样使用它们。

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容