元素的內(nèi)聯(lián)事件處理函數(shù)的特殊作用域在各瀏覽器中存在差異
來源:易賢網(wǎng) 閱讀:838 次 日期:2015-04-22 10:36:08
溫馨提示:易賢網(wǎng)小編為您整理了“元素的內(nèi)聯(lián)事件處理函數(shù)的特殊作用域在各瀏覽器中存在差異”,方便廣大網(wǎng)友查閱!

問題描述

在一個元素的屬性中綁定事件,實際上就創(chuàng)建了一個內(nèi)聯(lián)事件處理函數(shù)(如<h1 onclick="alert(this);"...>...</h1>),內(nèi)聯(lián)事件處理函數(shù)有其特殊的作用域鏈,并且各瀏覽器的實現(xiàn)細節(jié)也有差異。

造成的影響

如果在元素的內(nèi)聯(lián)事件處理函數(shù)中使用的變量或調(diào)用的方法不當,將導致腳本運行出錯。

受影響的瀏覽器

所有瀏覽器

問題分析

1. 內(nèi)聯(lián)事件處理函數(shù)的作用域鏈

與其他函數(shù)不同,內(nèi)聯(lián)事件處理函數(shù)的作用域鏈從頭部開始依次是:調(diào)用對象、該元素的 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(this2.form)3{

with(this2){

alert(compatMode);

}

}

}

}

</script>

以上兩種寫法的代碼在所有瀏覽器中都將彈出 document.compatMode 的值。

將上述代碼中的 'compatMode' 替換為 'method',則在各瀏覽器中都將彈出 'get',即 INPUT 元素所在表單對象的 method 屬性值。

注:

1. 這段代碼僅為說明問題而模擬各瀏覽器的行為,并非表示所有瀏覽器都是如此實現(xiàn)的。

2. 是使用 this 關鍵字還是直接使用這個 DOM 對象,在各瀏覽器中有差異,詳情請看本文 2.1 中的內(nèi)容。

3. 是否添加 FORM 對象到作用域鏈中,各瀏覽器在實現(xiàn)上也有差異,詳情請看本文 2.2 中的內(nèi)容。

2. 內(nèi)聯(lián)事件處理函數(shù)的作用域鏈在各瀏覽器中的差異

參考 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.

// Nodes other than the document object, when executing inline event handlers push document, form, and the target node on the scope chain.

// 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 (this.ownerDocument ? this.ownerDocument : {}) {" \

"with (this.form ? this.form : {}) {" \

"with (this) {" \

"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 對象到作用域鏈中。

其他瀏覽器中也有類似的實現(xiàn)方式,但在各瀏覽器中,將目標對象(即綁定了此內(nèi)聯(lián)事件處理函數(shù)的對象)添加到作用域鏈中的方式有差異,判斷并決定是否在作用域鏈中添加 FORM 對象的方法也不相同。

2.1. 各瀏覽器在生成這個特殊的作用域鏈時添加目標對象時使用的方法不同

各瀏覽器都會將內(nèi)聯(lián)事件處理函數(shù)所屬的元素的 DOM 對象加入到作用域鏈中,但加入的方式卻是不同的。

如以下代碼:

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

在所有瀏覽器中,都將彈出 'hello'。

再修改代碼以變更 INPUT 元素的內(nèi)聯(lián)事件處理函數(shù)的執(zhí)行上下文:

<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>

在各瀏覽器中運行的結(jié)果如下:

IE ChromeHi, I'm here!

Firefox Safari Operahello

可見,各瀏覽器將內(nèi)聯(lián)事件處理函數(shù)所屬的元素的 DOM 對象加入到作用域鏈中的方式是不同的。

在 IE Chrome 中的添加方式類似以下代碼:

<input type="button" value="hello">

<script>

var $target=document.getElementsByTagName("input")[0];

$target.onclick=function(){

with(document){

with(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($target){

alert(value);

}

}

}

</script>

由于極少需要改變內(nèi)聯(lián)事件處理函數(shù)的執(zhí)行上下文,這個差異造成的影響并不多見。

2.2. 各瀏覽器在生成這個特殊的作用域鏈時對于在何種情況下添加 FORM 對象有不同理解

各瀏覽器都會將內(nèi)聯(lián)事件處理函數(shù)所屬的 FORM 對象加入到作用域鏈中,但如何判斷該元素是否“屬于”一個表單對象,各瀏覽器的處理方式則不相同。

如以下代碼:

<form action="." method="get">

<div>

<span onclick="alert(method);">click</span>

</div>

</form>

<script>

document.method="document.method";

</script>

在各瀏覽器中,點擊 SPAN 元素后彈出的信息如下:

IE Safari Operaget

Chrome Firefoxdocument.method

可見:

IE Safari Opera 將 FORM 對象加入到了內(nèi)聯(lián)事件處理函數(shù)的作用域鏈中,是否加入 FORM 對象看起來是由這個元素是否是一個 FORM 的子孫級元素來決定的。因此在這些瀏覽器中,函數(shù)內(nèi)的變量 'method' 最終得到的是 FORM 的 'method' 的值。

Chrome Firefox 沒有將 FORM 對象加入到內(nèi)聯(lián)事件處理函數(shù)的作用域鏈中,判斷是否加入 FORM 對象是看該函數(shù)綁定的目標對象的 'form' 屬性是否存在。從上文中的 WebKit 的源碼中可以看到 Chrome 正是使用了 'this.form' 來判斷,只有目標元素是一個 FORM 的子孫級元素并且該目標元素是一個表單元素時,'form' 屬性才會存在。本例中的 SPAN 元素并不是表單元素,因此變量 'method' 最終得到的是 'document.method' 的值。

如果將以上代碼中的 SPAN 元素更換為 INPUT 元素或其他表單元素,則在所有瀏覽器中的表現(xiàn)將一致。

3. 由于內(nèi)聯(lián)事件處理函數(shù)的這種特殊的作用域鏈而產(chǎn)生問題的實例

3.1. 在元素的內(nèi)聯(lián)事件處理函數(shù)中訪問的變量意外的與該該函數(shù)作用域鏈中非全局對象的其他對象的屬性重名時出現(xiàn)的問題

當一個內(nèi)聯(lián)事件處理函數(shù)中訪問的變量意外的與該函數(shù)作用域鏈中非全局對象(window)的其他對象的屬性重名,將導致該變量的實際值不是預期值。

假設有以下代碼:

<button onclick="onsearch()"> click here </button>

<script>

function onsearch(){

alert("Click!");

}

</script>

作者本意為點擊按鈕即彈出“Click!”信息,但 WebKit 引擎瀏覽器的 HTMLElement 對象都有一個名為 onsearch 的事件監(jiān)聽器,這將導致上述代碼在 Chrome Safari 中不能按照預期執(zhí)行。本例中由于該監(jiān)聽器未定義(為 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. 在表單內(nèi)的子孫級非表單元素的內(nèi)聯(lián)事件處理函數(shù)中試圖調(diào)用表單的屬性或方法時出現(xiàn)的問題

假設有以下代碼:

<form action="xxx" method="get">

...

<a href="#" onclick="submit();">click</a>

</form>

作者本意為點擊 A 元素后調(diào)用 FORM 的 'submit' 方法,但 Chrome Firefox 并未將 FORM 對象加入到該內(nèi)聯(lián)事件處理函數(shù)的作用域鏈中,因此以上代碼在 Chrome Firefox 中并不能正常運行。

解決方案

1. 盡量不要使用內(nèi)聯(lián)事件處理函數(shù),使用 DOM 標準的事件注冊方式為該元素注冊事件處理函數(shù),如:

<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. 必須使用內(nèi)聯(lián)事件處理函數(shù)時,要保證該函數(shù)內(nèi)試圖訪問的變量是位于全局作用域內(nèi)的,而不會因該函數(shù)獨特的作用域鏈而引用到非預期的對象。最簡單的辦法是使用前綴,如 'my_onsearch'。

更多信息請查看IT技術(shù)專欄

更多信息請查看腳本欄目

2025國考·省考課程試聽報名

  • 報班類型
  • 姓名
  • 手機號
  • 驗證碼
關于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 加入群交流 | 手機站點 | 投訴建議
工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務許可證:(云)人服證字(2023)第0102001523號
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關注公眾號:hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)