【问题标题】:Why JS function name conflicts with element ID?为什么 JS 函数名与元素 ID 冲突?
【发布时间】:2012-02-06 09:44:53
【问题描述】:

我有两个几乎相同的简单 JS 小提琴在选择更改时调用一个函数。在这两种情况下,函数名称都与选择 ID 相同,但由于某种原因,第一个小提琴工作正常,第二个失败并出现 JavaScript 错误 is not a function

http://jsfiddle.net/AZkfy/7/ - 在 FF9 (Linux)、Chromium 16 (Linux)、IE8 (Windows) 中运行良好:

<script>
    function border(border) { alert(border); }
</script>

<select id='border' name='border' onchange='border(this.value)'>
    <option value='foo'>foo</option>
    <option value='bar'>bar</option>
</select>

http://jsfiddle.net/cYVzk/ - 在 FF9 (Linux)、Chromium 16 (Linux)、IE8 (Windows) 中失败:

<script>
    function border(border) { alert(border); }
</script>

<form>
<select id='border' name='border' onchange='border(this.value)'>
    <option value='foo'>foo</option>
    <option value='bar'>bar</option>
</select>
</form>

首先我不明白为什么第一个工作正常,而第二个却失败了。

第二 - 是否有关于冲突的 JS 函数名称和元素 ID 的任何 JS 规范或限制?

【问题讨论】:

  • 对我来说,第二个有效,第一个失败(在 chrome 中测试)。能否请您添加您的浏览器?
  • 这对我有用jsfiddle.net/ZdhRe

标签: javascript html


【解决方案1】:

这是源自 JavaScript 1.0 到 1.3 的遗留范围链问题,当时编程语言与我们现在称为 DOM API(当时的“动态 HTML”)之间没有区别。

如果您的表单控件(此处:select 元素)是表单的一部分(form 元素的后代),则表示form 元素的 Form 对象在控件的事件处理程序属性值中的范围代码链(second-next 是表单控件对象本身,next 是该代码的变量对象)。

JavaScript™ 由 Brendan Eich(当时在 Netscape 工作)设计为一种易于初学者使用的编程语言,并且可以很好地处理 HTML 文档(作为 Sun 的 Java 的补充;因此这个名字总是令人困惑)。因为在早期,语言和(Netscape)DOM API 是一体的,这种(过度)简化也适用于 DOM API:A Form object has the names of the controls contained in the form that it represents as the names of its properties that refer to the corresponding form control objects。 IOW,你可以写

myForm.border

这是符合标准 (W3C DOM Level 2 HTML) 的专有简写,但同样向后兼容

document.forms["myForm"].elements["border"]

现在,如果您在表单控件的事件处理程序属性值中使用表单控件的名称​​在表单中,例如

<form …>
  <… name="border" onchange='border(this.value)' …>
</form>

这就像你写了半专有的一样

<form …>
  <… name="border" onchange='this.form.border(this.value)' …>
</form>

或符合标准的

<form …>
  <… name="border" onchange='this.form.elements["border"](this.value)' …>
</form>

因为潜在的全局 border() 函数是最后出现的 ECMAScript 全局对象的属性, Form 对象之后(在 W3C 中实现 HTMLFormElement 接口的对象DOM),在作用域链中。

但是,这里border 引用的表单控件对象是不可调用的(没有实现ECMAScript 内部的[[Call]] 方法或实现它以便在调用时抛出异常)。因此,如果您尝试使用 border(this.value) 调用该对象,则会引发 TypeError 异常,您应该在脚本控制台中看到该异常(例如 Chromium 16.0.912.77 开发人员工具中的“TypeError:border is not a function” [开发者内部版本 118311 Linux])。

Microsoft,Netscape 在 1990 年代的竞争对手,不得不为 MSHTML DOM 复制该功能,以便为 Netscape 编写的代码也可以在 Internet Explorer (3.0) 和 JScript (1.0) 中运行。出于完全相同的原因,微软的竞争对手将其复制到他们的 DOM 实现中。它成为准标准(现在称为“DOM Level 0”)的一部分。

随后出现了 DOM Level 2 HTML 规范,这是对当时现有 DOM 实现的通用特性进行标准化和扩展的持续努力。自 2003-01-09 以来的 W3C 建议,其 ECMAScript Language Binding 指定 HTMLCollections 的项目可以通过其名称 ID 使用括号属性访问器语法访问[],相当于调用实现HTMLCollection接口的对象的namedItem() method

form元素对象和表单中表单控件的元素对象分别是W3C DOM中HTMLCollections的项目HTMLDocument::formsHTMLFormElement::elements。但是为了在浏览器中向后兼容,

document.forms["myForm"].elements["myControl"]

需要等价于

document.myForm.myControl

因此,最迟随着 W3C DOM Level 2 HTML 接口的实现,这个特性也开始适用于 ID 为id 属性值)的元素(可以在例如铬)。

因此,16 年前 JavaScript™ 中引入的便利功能仍然像今天客户端 DOM 脚本中的错误一样困扰您。

如果您避免对用作用户定义函数标识符的表单控件和表单使用相同的名称或 ID,并且这些名称或 ID 已用于内置表单属性(如 actionsubmitreset),那么这不再是一个问题。此外,对函数及其参数之一使用相同的标识符是一个坏主意(除了混淆代码),这使得函数对象无法从函数内部访问(函数上下文的变量对象在其作用域链中排在第一位) )。

【讨论】:

  • @Christoph 不客气 :) 由于问题是关于 ID 的,我对此进行了进一步研究,并添加了 W3C DOM 2 HTML 如何将此问题与元素 ID 相关联的解释。还应该注意的是,表单对象在作用域链中位于 third-next。如果不是为了消除歧义,我们就不需要写this.value,只需要写value(!)。
  • 我刚刚在 2021 年 10 月使用 Chrome Version 94.0.4606.81 (Official Build) (64-bit) 遇到了这个问题!!! - 这让我发疯了一个小时。就我而言:` `
  • @rolinger:这个标记还有很多需要改进的地方。而且你不应该有一个全局自定义showPass 函数;它应该是用户定义对象的方法。但是如果你不能帮助按钮的命名(“btn-showPass”是一个替代方案)或showPass函数的定义,那么你可以使用onclick='(function () { return this; }()).showPass()',该函数返回对ECMAScript全局对象的引用.如果在多个地方需要,您可以在全局执行上下文中定义var _global = this;,然后改用_global.showPass();
  • 获取全局对象引用的内联调用仅在全局上下文未声明为以严格模式执行时才有效。我实际上不确定全局严格模式声明 ('use strict';) 是每个文件还是每个元素,或者分别在包含的脚本资源或 script 元素内容中使用时转移到全局 HTML 上下文。
【解决方案2】:

IE 自动在全局空间中为每个带有ID 的 DOM 元素保留一个var ID = domElement;。其他一些浏览器采用了这种行为。

始终尽量避免使用相同的 ID 和 varnames!或者,在 JS 中使用自己的命名空间来避免冲突。

编辑:

我不知道为什么你的一个例子失败了,而另一个例子有效。这可能是由包装&lt;form&gt; 引起的简单时间/执行顺序问题。

【讨论】:

  • 正如我在回答中提到的,我认为表单可能会延迟 var 被 dom 元素占用。因此,该函数首先分配给 var,然后被 dom 元素覆盖。
  • 换个说法,这与 MSHTML 使命名/ID 元素的对象通过作用域链中的全局宿主对象的属性可访问无关,也与时间无关。这是一个遗留范围链问题,在标准合规模式下也会出现(而名称/ID 问题在除 IE 之外的其他浏览器中不会出现)。
  • @Christoph 您所描述的内容在 HTML5 规范中有所提及:whatwg.org/specs/web-apps/current-work/multipage/… 但它是 likely to be removed in the future
  • @Mathias 我不知道,这也适用于某些元素的名称属性。所以问题确实是,为选项元素创建一个变量正在隐藏附加到全局对象的函数。
  • @Christoph 为 select 元素创建一个属性
【解决方案3】:

http://jsfiddle.net/x79ey/1/

在我看来,form 标记为内联事件处理程序创建了一个额外的范围,并且表单元素被定义为这个本地范围内的变量:

<form>
    <element id="foo"....
    <element onclick="foo is a local variable here"

在我的测试中没有全局自动定义变量,但这可能因浏览器/模式而异。

【讨论】:

    猜你喜欢
    • 2012-09-15
    • 1970-01-01
    • 2016-05-03
    • 2019-05-13
    • 2015-02-26
    • 1970-01-01
    • 1970-01-01
    • 2014-12-16
    相关资源
    最近更新 更多