目次
标准参考
问题描述
造成的影响
受影响的浏览器
问题分析
1. 内联事件处理函数的作用域链
2. 内联事件处理函数的作用域链在各浏览器中的差异
2.1. 各浏览器在生成这个特殊的作用域链时添加目标对象时使用的方法不同
2.2. 各浏览器在生成这个特殊的作用域链时对于在何种情况下添加 FORM 对象有不同理解
3. 由于内联事件处理函数的这种特殊的作用域链而产生问题的实例
3.1. 在元素的内联事件处理函数中访问的变量意外的与该该函数作用域链中非全局对象的其他对象的属性重名时出现的问题
3.2. 在表单内的子孙级非表单元素的内联事件处理函数中试图调用表单的属性或方法时出现的问题
解决方案
ホームページ ウェブフロントエンド jsチュートリアル 要素のインライン イベント ハンドラーの特殊スコープはブラウザ間で異なります_JavaScript ヒント

要素のインライン イベント ハンドラーの特殊スコープはブラウザ間で異なります_JavaScript ヒント

May 16, 2016 pm 06:12 PM
要素

标准参考

无。

问题描述

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

...

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

造成的影响

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

受影响的浏览器

所有浏览器

问题分析

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

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

如以下代码:

<form action="." method="get">
	<input type="button" value="compatMode" onclick="alert(compatMode);">
</form>
ログイン後にコピー

相当于1

<form action="." method="get">
	<input type="button" value="compatMode">
</form>
<script>
document.getElementsByTagName("input")[0].onclick=function(){
	with(document){
		with(this<sup>2</sup>.form)<sup>3</sup>{
			with(this<sup>2</sup>){
				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<v8::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<v8::String> codeExternalString = v8ExternalString(code);
  v8::Handle<v8::Script> script = V8Proxy::compileScript(codeExternalString, m_sourceURL, m_lineNumber);
  if (!script.IsEmpty()) {
    v8::Local<v8::Value> value = proxy->runScript(script, false);
    if (!value.IsEmpty()) {
      ASSERT(value->IsFunction());

      v8::Local<v8::Function> wrappedFunction = v8::Local<v8::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<v8::FunctionTemplate>, toStringTemplate, ());
      if (toStringTemplate.IsEmpty())
        toStringTemplate = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New(V8LazyEventListenerToString));
      v8::Local<v8::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);
    }
  }
}
ログイン後にコピー

从以上代码可以看出,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</span>){
			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</span>){
			alert(value);
		}
	}
}
</script>
ログイン後にコピー

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

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

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

如以下代码:

<form action="." method="get">
	<div>
		<span onclick="alert(method);">click</span>
	</div>
</form>
<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. 在表单内的子孙级非表单元素的内联事件处理函数中试图调用表单的属性或方法时出现的问题

假设有以下代码:

<form action="xxx" method="get">
	...
	<a href="#" onclick="submit();">click</a>
</form>
ログイン後にコピー

作者本意为点击 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 までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

JavaFX では、さまざまなパス要素とは何ですか? JavaFX では、さまざまなパス要素とは何ですか? Aug 28, 2023 pm 12:53 PM

javafx.scene.shape パッケージには、さまざまな 2D 形状を描画できるいくつかのクラスが用意されていますが、これらは線、円、多角形、楕円などの原始的な形状にすぎません。複雑なカスタム形状を描画したい場合は、次のものが必要です。 Path クラスを使用します。パス クラス パス クラス 形状を表すこの幾何学的アウトラインを使用して、カスタム パスを描画できます。カスタム パスを描画するために、JavaFX にはさまざまなパス要素が用意されており、それらはすべて javafx.scene.shape パッケージ内のクラスとして利用できます。 LineTo - このクラスはパス要素の行を表します。現在の座標から指定された (新しい) 座標まで直線を引くのに役立ちます。 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

CSS トランジション効果: 要素のスライド効果を実現する方法 CSS トランジション効果: 要素のスライド効果を実現する方法 Nov 21, 2023 pm 01:16 PM

CSS トランジション効果: 要素のスライド効果を実現する方法 はじめに: Web デザインでは、要素の動的な効果によりユーザー エクスペリエンスを向上させることができます。その中でも、スライド効果は一般的で人気のあるトランジション効果です。 CSS のトランジション プロパティを使用すると、要素のスライド アニメーション効果を簡単に実現できます。この記事では、CSS トランジション プロパティを使用して要素のスライド効果を実現する方法を紹介し、読者がよりよく理解して適用できるように具体的なコード例を示します。 1. CSS トランジション属性の概要 トランジション CSS トランジション属性 tra

html5でサポートされていない要素は何ですか html5でサポートされていない要素は何ですか Aug 11, 2023 pm 01:25 PM

HTML5 がサポートしていない要素は、純粋に表現力豊かな要素、フレームベースの要素、アプリケーション要素、置換可能な要素、および古いフォーム要素です。詳細な紹介: 1. フォント、センター、s、u などの純粋に表現力豊かな要素。これらの要素は通常、テキストのスタイルやレイアウトを制御するために使用されます。 2. フレームベースの要素 (frame、frameset、noframes など)。要素は、以前は Web ページ レイアウトの作成やウィンドウの分割に使用されていました; 3. アプレット、isinde などのアプリケーション関連の要素。

CSS 変換: 要素の回転効果を実現する方法 CSS 変換: 要素の回転効果を実現する方法 Nov 21, 2023 pm 06:36 PM

CSS 変換: 要素の回転効果を実現する方法には、特定のコード サンプルが必要です。Web デザインでは、アニメーション効果はユーザー エクスペリエンスを向上させ、ユーザーの注意を引くための重要な方法の 1 つであり、回転アニメーションは最も古典的なものの 1 つです。 CSS では、「transform」属性を使用して、回転を含む要素のさまざまな変形効果を実現できます。この記事では、CSS「transform」を使用して要素の回転効果を実現する方法と、具体的なコード例を詳しく紹介します。 1.CSSの「transf」の使い方

jqueryで要素を削除する方法 jqueryで要素を削除する方法 Feb 17, 2023 am 09:49 AM

jQuery を使用して要素を削除する方法: 1. jQuery の delete() メソッドを使用して、選択した要素とそのサブ要素を削除します。構文は "$("#div1").remove();"; 2. jQuery を使用して削除します。 empty() メソッド。選択した要素の子要素。構文は「$("#div1").empty();」です。

CSS を使用して要素の透明度のグラデーション効果を実現する方法 CSS を使用して要素の透明度のグラデーション効果を実現する方法 Nov 21, 2023 pm 01:38 PM

CSS を使用して要素の透明グラデーション効果を実現する方法 Web 開発では、Web ページ要素にトランジション効果を追加することは、ユーザー エクスペリエンスを向上させる重要な手段の 1 つです。透明度のグラデーション効果は、ページを滑らかにするだけでなく、要素の重要なコンテンツを強調表示することもできます。この記事では、CSS を使用して要素の透明度のグラデーション効果を実現する方法を紹介し、具体的なコード例を示します。 CSS トランジション属性の使用 要素の透明グラデーション効果を実現するには、CSS トランジション属性を使用する必要があります。 t

HTML と CSS を使用して固定ナビゲーション メニューを含むレイアウトを実装する方法 HTML と CSS を使用して固定ナビゲーション メニューを含むレイアウトを実装する方法 Oct 26, 2023 am 11:02 AM

HTML と CSS を使用して固定ナビゲーション メニューを含むレイアウトを実装する方法 最新の Web デザインでは、固定ナビゲーション メニューは一般的なレイアウトの 1 つです。ナビゲーション メニューを常にページの上部または横に配置できるため、ユーザーは Web コンテンツを便利に閲覧できます。この記事では、HTML と CSS を使用して固定ナビゲーション メニューのレイアウトを実装する方法を紹介し、具体的なコード例を示します。まず、Web ページのコンテンツとナビゲーション メニューを表示する HTML 構造を作成する必要があります。これが簡単な例です

See all articles