搜索
首页web前端js教程元素的内联事件处理函数的特殊作用域在各浏览器中存在差异_javascript技巧

标准参考

无。

问题描述

在一个元素的属性中绑定事件,实际上就创建了一个内联事件处理函数(如

...

),内联事件处理函数有其特殊的作用域链,并且各浏览器的实现细节也有差异。

造成的影响

如果在元素的内联事件处理函数中使用的变量或调用的方法不当,将导致脚本运行出错。

受影响的浏览器

所有浏览器

问题分析

1. 内联事件处理函数的作用域链

与其他函数不同,内联事件处理函数的作用域链从头部开始依次是:调用对象、该元素的 DOM 对象、该元素所属 FORM 的 DOM 对象(如果有)、document 对象、window 对象(全局对象)。

如以下代码:


相当于1


<script> document.getElementsByTagName("input")[0].onclick=function(){ with(document){ with(this<SUP>2.form)<SUP>3{ with(this<SUP>2){ alert(compatMode); } } } } </script>

以上两种写法的代码在所有浏览器中都将弹出 document.compatMode 的值。

将上述代码中的 'compatMode' 替换为 'method',则在各浏览器中都将弹出 'get',即 INPUT 元素所在表单对象的 method 属性值。

注:
1. 这段代码仅为说明问题而模拟各浏览器的行为,并非表示所有浏览器都是如此实现的。
2. 是使用 this 关键字还是直接使用这个 DOM 对象,在各浏览器中有差异,详情请看本文 2.1 中的内容。
3. 是否添加 FORM 对象到作用域链中,各浏览器在实现上也有差异,详情请看本文 2.2 中的内容。

2. 内联事件处理函数的作用域链在各浏览器中的差异

参考 WebKit 的源码:

void V8LazyEventListener::prepareListenerObject(ScriptExecutionContext* context)
{
  if (hasExistingListenerObject())
    return;

  v8::HandleScope handleScope;

  V8Proxy* proxy = V8Proxy::retrieve(context);
  if (!proxy)
    return;

  // Use the outer scope to hold context.
  v8::Local<:context> v8Context = worldContext().adjustedContext(proxy);
  // Bail out if we cannot get the context.
  if (v8Context.IsEmpty())
    return;

  v8::Context::Scope scope(v8Context);

  // FIXME: cache the wrapper function.

  // <strong>Nodes other than the document object, when executing inline event handlers push document, form, and the target node on the scope chain.</strong>
  // We do this by using 'with' statement.
  // See chrome/fast/forms/form-action.html
  //   chrome/fast/forms/selected-index-value.html
  //   base/fast/overflow/onscroll-layer-self-destruct.html
  //
  // Don't use new lines so that lines in the modified handler
  // have the same numbers as in the original code.
  String code = "(function (evt) {" \
      "with (<strong>this</strong>.ownerDocument ? <strong>this</strong>.ownerDocument : {}) {" \
      "with (<em><strong>this</strong>.form</em> ? <strong>this</strong>.form : {}) {" \
      "with (<strong>this</strong>) {" \
      "return (function(evt){";
  code.append(m_code);
  // Insert '\n' otherwise //-style comments could break the handler.
  code.append( "\n}).call(this, evt);}}}})");
  v8::Handle<:string> codeExternalString = v8ExternalString(code);
  v8::Handle<:script> script = V8Proxy::compileScript(codeExternalString, m_sourceURL, m_lineNumber);
  if (!script.IsEmpty()) {
    v8::Local<:value> value = proxy->runScript(script, false);
    if (!value.IsEmpty()) {
      ASSERT(value->IsFunction());

      v8::Local<:function> wrappedFunction = v8::Local<:function>::Cast(value);

      // Change the toString function on the wrapper function to avoid it
      // returning the source for the actual wrapper function. Instead it
      // returns source for a clean wrapper function with the event
      // argument wrapping the event source code. The reason for this is
      // that some web sites use toString on event functions and eval the
      // source returned (sometimes a RegExp is applied as well) for some
      // other use. That fails miserably if the actual wrapper source is
      // returned.
      DEFINE_STATIC_LOCAL(v8::Persistent<:functiontemplate>, toStringTemplate, ());
      if (toStringTemplate.IsEmpty())
        toStringTemplate = v8::Persistent<:functiontemplate>::New(v8::FunctionTemplate::New(V8LazyEventListenerToString));
      v8::Local<:function> toStringFunction;
      if (!toStringTemplate.IsEmpty())
        toStringFunction = toStringTemplate->GetFunction();
      if (!toStringFunction.IsEmpty()) {
        String toStringResult = "function ";
        toStringResult.append(m_functionName);
        toStringResult.append("(");
        toStringResult.append(m_isSVGEvent ? "evt" : "event");
        toStringResult.append(") {\n ");
        toStringResult.append(m_code);
        toStringResult.append("\n}");
        wrappedFunction->SetHiddenValue(V8HiddenPropertyName::toStringString(), v8ExternalString(toStringResult));
        wrappedFunction->Set(v8::String::New("toString"), toStringFunction);
      }

      wrappedFunction->SetName(v8::String::New(fromWebCoreString(m_functionName), m_functionName.length()));

      setListenerObject(wrappedFunction);
    }
  }
}
</:function></:functiontemplate></:functiontemplate></:function></:function></:value></:script></:string></:context>

从以上代码可以看出,WebKit 在向作用域链中添加对象时,使用了 'this' 关键字,并且通过判断 'this.form' 是否存在来决定是否添加 FORM 对象到作用域链中。

其他浏览器中也有类似的实现方式,但在各浏览器中,将目标对象(即绑定了此内联事件处理函数的对象)添加到作用域链中的方式有差异,判断并决定是否在作用域链中添加 FORM 对象的方法也不相同。

2.1. 各浏览器在生成这个特殊的作用域链时添加目标对象时使用的方法不同

各浏览器都会将内联事件处理函数所属的元素的 DOM 对象加入到作用域链中,但加入的方式却是不同的。

如以下代码:

<input type="button" value="hello" onclick="alert(value);">

在所有浏览器中,都将弹出 'hello'。

再修改代码以变更 INPUT 元素的内联事件处理函数的执行上下文:

<input type="button" value="hello" onclick="alert(value);">
<script>
var $target=document.getElementsByTagName("input")[0];
var o={
	onclick:$target.onclick,
	value:"Hi, I'm here!"
};
o.onclick();
</script>

在各浏览器中运行的结果如下:

IE Chrome Hi, I'm here!
Firefox Safari Opera hello

可见,各浏览器将内联事件处理函数所属的元素的 DOM 对象加入到作用域链中的方式是不同的。

在 IE Chrome 中的添加方式类似以下代码:

<input type="button" value="hello">
<script>
var $target=document.getElementsByTagName("input")[0];
$target.onclick=function(){
	with(document){
		with(<SPAN class=hl_2>this){
			alert(value);
		}
	}
}
</script>

而在 Firefox Safari Opera 中的添加方式则类似以下代码:

<input type="button" value="hello">
<script>
var $target=document.getElementsByTagName("input")[0];
$target.onclick=function(){
	with(document){
		with(<SPAN class=hl_3>$target){
			alert(value);
		}
	}
}
</script>

由于极少需要改变内联事件处理函数的执行上下文,这个差异造成的影响并不多见。

2.2. 各浏览器在生成这个特殊的作用域链时对于在何种情况下添加 FORM 对象有不同理解

各浏览器都会将内联事件处理函数所属的 FORM 对象加入到作用域链中,但如何判断该元素是否“属于”一个表单对象,各浏览器的处理方式则不相同。

如以下代码:


click
<script> document.method="document.method"; </script>

在各浏览器中,点击 SPAN 元素后弹出的信息如下:

IE Safari Opera get
Chrome Firefox document.method

可见:

  • IE Safari Opera 将 FORM 对象加入到了内联事件处理函数的作用域链中,是否加入 FORM 对象看起来是由这个元素是否是一个 FORM 的子孙级元素来决定的。因此在这些浏览器中,函数内的变量 'method' 最终得到的是 FORM 的 'method' 的值。
  • Chrome Firefox 没有将 FORM 对象加入到内联事件处理函数的作用域链中,判断是否加入 FORM 对象是看该函数绑定的目标对象的 'form' 属性是否存在。从上文中的 WebKit 的源码中可以看到 Chrome 正是使用了 'this.form' 来判断,只有目标元素是一个 FORM 的子孙级元素并且该目标元素是一个表单元素时,'form' 属性才会存在。本例中的 SPAN 元素并不是表单元素,因此变量 'method' 最终得到的是 'document.method' 的值。

如果将以上代码中的 SPAN 元素更换为 INPUT 元素或其他表单元素,则在所有浏览器中的表现将一致。

3. 由于内联事件处理函数的这种特殊的作用域链而产生问题的实例

3.1. 在元素的内联事件处理函数中访问的变量意外的与该该函数作用域链中非全局对象的其他对象的属性重名时出现的问题

当一个内联事件处理函数中访问的变量意外的与该函数作用域链中非全局对象(window)的其他对象的属性重名,将导致该变量的实际值不是预期值。

假设有以下代码:

<button onclick="onsearch()"> click here </button>
<script>
function onsearch(){
	alert("Click!");
}
</script>

作者本意为点击按钮即弹出“Click!”信息,但 WebKit 引擎浏览器的 HTMLElement 对象都有一个名为 onsearch 的事件监听器,这将导致上述代码在 Chrome Safari 中不能按照预期执行。本例中由于该监听器未定义(为 null),因此将报 “Uncaught TypeError: object is not a function” 的错误。

附:在上述代码中,追加以下代码确认 'onsearch' 的位置:

<script>
var o=document.getElementsByTagName("button")[0];
if("onsearch" in o)alert("当前对象有 onsearch 属性。");
if(o.hasOwnProperty("onsearch"))alert("onsearch 属性是当前对象私有。");
</script>

3.2. 在表单内的子孙级非表单元素的内联事件处理函数中试图调用表单的属性或方法时出现的问题

假设有以下代码:


... click

作者本意为点击 A 元素后调用 FORM 的 'submit' 方法,但 Chrome Firefox 并未将 FORM 对象加入到该内联事件处理函数的作用域链中,因此以上代码在 Chrome Firefox 中并不能正常运行。

解决方案

1. 尽量不要使用内联事件处理函数,使用 DOM 标准的事件注册方式为该元素注册事件处理函数,如:

<button> click here </button>
<script>
function onsearch(){
	alert("Click!");
}
function bind($target,eventName,onEvent){
	$target.addEventListener?$target.addEventListener(eventName,onEvent,false):$target.attachEvent("on"+eventName,onEvent);
}
bind(document.getElementsByTagName("button")[0],"click",onsearch);
</script>

2. 必须使用内联事件处理函数时,要保证该函数内试图访问的变量是位于全局作用域内的,而不会因该函数独特的作用域链而引用到非预期的对象。最简单的办法是使用前缀,如 'my_onsearch'。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
在JavaFX中,有哪些不同的路径元素?在JavaFX中,有哪些不同的路径元素?Aug 28, 2023 pm 12:53 PM

javafx.scene.shape包提供了一些类,您可以使用它们绘制各种2D形状,但这些只是原始形状,如直线、圆形、多边形和椭圆形等等...因此,如果您想绘制复杂的自定义形状,您需要使用Path类。Path类Path类使用此表示形状的几何轮廓您可以绘制自定义路径。为了绘制自定义路径,JavaFX提供了各种路径元素,所有这些都可以作为javafx.scene.shape包中的类使用。LineTo-该类表示路径元素line。它可以帮助您从当前坐标到指定(新)坐标绘制一条直线。HlineTo-这是表

C++程序:向数组中添加一个元素C++程序:向数组中添加一个元素Aug 25, 2023 pm 10:29 PM

数组是一种线性顺序数据结构,用于在连续的内存位置中保存同质数据。与其他数据结构一样,数组也必须具备以某种有效方式插入、删除、遍历和更新元素的功能。在C++中,我们的数组是静态的。C++中还提供了一些动态数组结构。对于静态数组,该数组内可能存储Z个元素。到目前为止,我们已经有n个元素了。在本文中,我们将了解如何在C++中在数组末尾插入元素(也称为追加元素)。通过示例理解概念‘this’关键字的使用方式如下GivenarrayA=[10,14,65,85,96,12,35,74,69]Afterin

html5不支持哪些元素html5不支持哪些元素Aug 11, 2023 pm 01:25 PM

html5不支持的元素有纯表现性元素、基于框架的元素、应用程序元素、可替换元素和旧的表单元素。详细介绍:1、纯表现性的元素,如font、center、s、u等,这些元素通常被用于控制文本样式和布局;2、基于框架的元素,如frame、frameset和noframes,这些元素在过去用于创建网页布局和分割窗口;3、应用程序相关的元素,如applet和isinde等等。

jquery怎么移除 元素jquery怎么移除 元素Feb 17, 2023 am 09:49 AM

jquery移除元素的方法:1、通过jQuery remove()方法删除被选元素及其子元素,语法是“$("#div1").remove();”;2、通过jQuery empty()方法删除被选元素的子元素,语法是“$("#div1").empty();”。

如何使用HTML和CSS实现一个具有固定导航菜单的布局如何使用HTML和CSS实现一个具有固定导航菜单的布局Oct 26, 2023 am 11:02 AM

如何使用HTML和CSS实现一个具有固定导航菜单的布局在现代网页设计中,固定导航菜单是常见的布局之一。它可以使导航菜单始终保持在页面顶部或侧边,使用户可以方便地浏览网页内容。本文将介绍如何使用HTML和CSS实现一个具有固定导航菜单的布局,并提供具体的代码示例。首先,需要创建一个HTML结构来呈现网页的内容和导航菜单。以下是一个简单的示例

Python程序用于测试列表中的所有元素是否最大间隔为KPython程序用于测试列表中的所有元素是否最大间隔为KAug 28, 2023 pm 05:25 PM

在许多编程场景中,我们都会遇到需要确定列表中的所有元素是否最多相距K个位置的情况。这个问题出现在各个领域,例如数据分析、序列处理和算法挑战。能够测试和验证这些条件对于确保我们程序的完整性和正确性至关重要。在本文中,我们将探索一个Python程序来有效地解决这个问题。我们将讨论这个概念,提出解决问题的分步方法,并提供工作代码实现。读完本文后,您将清楚地了解如何使用Python检查列表中的元素是否最多相距K个位置。理解问题在深入研究解决方案之前,让我们首先详细了解问题陈述。给定一个元素列表,我们需要

Vue3中的ref函数详解:直接访问组件元素的应用Vue3中的ref函数详解:直接访问组件元素的应用Jun 18, 2023 am 11:51 AM

在Vue3中,ref函数是非常有用的,在开发过程中提供了很方便的操作方式。它允许直接访问Vue组件元素并对其进行操作。ref函数是一个创建一个被响应式地绑定的对象的函数。可以在Vue组件中使用它来引用一个元素或子组件,并从父组件操作这些元素或子组件。ref函数返回一个响应式的对象,并通过该对象暴露指定元素或子组件的引用。因此,可以通过该对象直接访问元素或子组

如何在Java中删除ArrayList的所有元素?如何在Java中删除ArrayList的所有元素?Sep 20, 2023 pm 02:21 PM

List接口扩展了Collection接口并存储元素序列。List接口提供了两种方法来有效地在列表中的任意点插入和删除多个元素。与集合不同,列表允许重复元素,如果列表中允许空值,则允许多个空值。List提供了add、remove方法来添加/删除元素。为了清除列表或从列表中删除所有元素,我们可以使用List的clear()方法。我们也可以使用removeAll()方法来达到与clear()方法相同的效果。在本文中,我们将通过相应的示例介绍clear()和removeAll()方法。语法-clear

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
2 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能