• JavaScript命名冲突不可避免?冲突源有哪些


    【CSDN 编者按】从1995年开始,本文作者Dr.Axel Rauschmayer就专门从事JavaScript和Web开发,已经有30多年了。2010年,他获得慕尼黑大学信息学博士学位。自2011年以来,他一直在2ality.com写博客,并写了几本关于JavaScript的书,比如《JavaScript for impatient programmers》、《Deep JavaScript: Theory and techniques》等。今天这篇文章就来自于他的博客,介绍了在JavaScript命名冲突时,现有代码如何强制对提议的功能进行重命名。

    整理 | 章雨铭 责编 | 张红月
    出品 | CSDN(ID:CSDNnews)

    不断发展的JavaScript:不要破坏web!

    JavaScript的一个发展核心原则就是"不要破坏Web":在将新特性添加到语言中后,所有现有代码都必须能够继续运行。

    这样有一个坏处,就是不能从语言中删除现有的quirks。但这样做益处多多,比如旧的代码可以继续运行,而且升级到新的ECMAScript版本很简便等等。

    在为新特征(如方法名称)选择名称时,需要进行一个重要的测试,即在浏览器的nightly版本(早期预发布版本)中添加该特征,并检查是否有任何网站出现错误。

    接下来将介绍过去案例中的的四个冲突源,当产生这四种冲突时,就必须重命名特征。

    冲突源1:向内置原型添加方法

    在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!'
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    神奇的是,语言可以通过这种方式改变。这种运行时的修改被称为猴子补丁(monkey patch)。

    什么是猴子补丁?

    如果我们给内置原型添加方法,我们就是在运行时修改一个软件系统。这样的修改被称为猴子补丁。简单来说,对其含义有两种可能的解释。

    这个叫法起源于Zope框架,人们在修正Zope的Bug的时候经常在程序后面追加更新部分,这些被称作是“杂牌军补丁(guerilla patch)”,后来guerilla就渐渐的写成了gorllia((猩猩),再后来就写了monkey(猴子),所以猴子补丁的叫法是这么莫名其妙的得来的。

    另一种说法是,它指的是搞乱(monkeying about)代码。

    反对改变内置原型的原因

    对任何类型的全局命名,都会存在名称冲突的风险。如果有解决冲突的机制,就能规避风险。例如:

    • 全局模块是通过裸模块指定器或URLs来识别的。前者之间的名称冲突可以通过npm注册表来解决。后者之间的名称冲突可以通过域名注册处来解决。
    • 可以通过将符号添加到JavaScript中,以避免方法之间的名称冲突。例如,任何对象都可以通过添加一个键为.NET的方法而成为可迭代的。由于每个符号都是唯一的,所以这个键永远不会与任何其他属性键.Symbol.iterator发生冲突。

    然而,带有字符串键的方法会导致名称冲突:

    • 不同的库可能会对他们添加到.Array.prototype的方法使用相同的名字。
    • 如果一个名字已经被某个库使用了,那么这个名称就不能用于命名JavaScript标准库的一个新特性。

    具有讽刺意味的是,谨慎地添加一个方法可能会适得其反:

    if (!Array.prototype.libraryMethod) {
      Array.prototype.libraryMethod = function () { /*...*/ };
    }
    
    • 1
    • 2
    • 3

    我们会检查一个方法是否已经存在。如果没有,我们就添加它。

    如果我们要实现一个polyfill(模拟原生Web平台功能),将新的JavaScript方法添加到不支持它的引擎中,那么这个技术就能发挥作用。(顺便说一下,这是修改内置原型的一个合法用例。也许是唯一的一个)。

    然而,如果我们对一个普通库的方法使用这种技术,然后JavaScript获取具有相同名称的方法,那么这两种实现的工作方式就不一样了,并且使用库方法的所有代码在使用内置方法时都会中断。

    必须更改名称的原型方法示例

    ES6的方法最初是与JavaScript框架MooTools(错误报告).String.prototype.includes().contains()全局添加的方法相冲突。

    ES2016的方法最初是与MooTools(错误报告 ).Array.prototype.includes().contains()添加的方法相冲突。

    ES2019的方法最初是和MooTools(错误报告博客文章).Array.prototype.flat().flatten()相冲突。

    修改内置原型并不总是糟糕的

    你可能会对MooTools的创建者的粗心大意感到疑惑。但是,向内置原型添加方法并不总是糟糕的。在ES3(1999年12月)和ES5(2009年12月)之间,JavaScript是一种停滞不前的语言。MooTools和Prototype等框架改进了它。这些方法的缺点只有在JavaScript的标准库再次增加之后才会凸显出来。

    冲突源2:检查一个属性的存在

    ES2022的方法最初是.NET的。因为以下库检查属性以确定对象是否是一个HTML集合(而不是一个数组),所以它必须被重新命名:Magic360YUI 2YUI 3.Array.prototype.at().item().item

    冲突源3:检查全局变量是否存在

    自ES2020以来,我们可以通过globalThis访问全局对象。Node.js一直使用该名称来实现此目的。最初的计划是为所有平台标准化该名称.global

    然而,以下模式经常被用来确定当前平台:

    if (typeof global !== 'undefined') {
      // We are not running on Node.js
    }
    
    • 1
    • 2
    • 3

    如果浏览器也有一个名为.global的全局变量,这种模式(以及类似的模式)就会失效。因此,标准化的名称被改为.globalglobalThis。

    冲突源4:通过创建局部变量with语句

    JavaScript的声明with语句

    长期以来,人们一直不鼓励使用JavaScript的with语句,甚至在ES5中引入的严格模式中也被定为非法。在其他地方,严格模式在ECMAScript模块中是活跃的。

    该语句将一个对象的属性变成局部变量:with

    cons t 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'
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    由with语句引起的冲突

    框架Ext.js使用的代码与下面的片段有些相似点:

    function myFunc(values) {
      with (values) {
        console.log(values); // (A)
      }
    }
    myFunc([]); // (B)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当ES6方法被添加到JavaScript中时,如果用Array(B行)来调用它,它就会失效。该语句将Array的所有属性变成了局部变量。其中一个是继承的属性。因此,A行中的语句已记录,不再是参数(错误报告1错误报告2

    Array.prototype.values()myFunc()withvalues.valuesArray.prototype.valuesvalue

    Unscopables:防止with导致的冲突

    公共符号Symbol.unscopables允许对象隐藏语句中的某些属性。它只在标准库中使用一次,对于Array.prototype:with

    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,
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结论

    以上提出了JavaScript结构与现有代码发生名称冲突的四种方式:

    • 向内置原型添加方法

    • 检查属性是否存在

    • 检查全局变量是否存在

    • 创建局部变量with

    冲突的某些来源很难预测,但存在以下一些一般规则:

    • 不要更改全局数据。
    • 避免检查是否存在全局数据。

    请注意,内置值将来可能会获得其他属性(自己的或继承的属性)。

    对于库来说,为JavaScript值提供功能的最安全方法是通过函数。如果JavaScript得到一个pipe operator,我们也可以像方法一样使用它们。

    参考资料:
    https://2ality.com/2022/03/naming-conflicts.html

  • 相关阅读:
    C++数组类的自实现,使其可以保存学生成绩,并进行降序排列
    攻防世界流量分析1
    剑指 Offer 68 - II. 二叉树的最近公共祖先
    Revit中柱与梁不能连接问题和“柱断墙梁”功能介绍
    【AI开发:音频】二、GPT-SoVITS使用方法和过程中出现的问题(GPU版)
    Error: Cannot find module ‘webpack/lib/RuleSet‘
    Spring Boot 可以同时处理多少请求?
    笔记 | 嵌入式系统概论
    JetBrains官博:将从IntelliJ平台移除Log4j的依赖
    python自动化测试全栈之ApiFox批量运行脚本生成报告
  • 原文地址:https://blog.csdn.net/csdnnews/article/details/123531910