無(wú)。
在一個(gè)元素的屬性中綁定事件,實(shí)際上就創(chuàng)建了一個(gè)內(nèi)聯(lián)事件處理函數(shù)(如<h1 onclick="alert(this);"...>...</h1>),內(nèi)聯(lián)事件處理函數(shù)有其特殊的作用域鏈,并且各瀏覽器的實(shí)現(xiàn)細(xì)節(jié)也有差異。
如果在元素的內(nèi)聯(lián)事件處理函數(shù)中使用的變量或調(diào)用的方法不當(dāng),將導(dǎo)致腳本運(yùn)行出錯(cuò)。
| 所有瀏覽器 |
|---|
與其他函數(shù)不同,內(nèi)聯(lián)事件處理函數(shù)的作用域鏈從頭部開(kāi)始依次是:調(diào)用對(duì)象、該元素的 DOM 對(duì)象、該元素所屬 FORM 的 DOM 對(duì)象(如果有)、document 對(duì)象、window 對(duì)象(全局對(duì)象)。
如以下代碼:
<form action="." method="get"> <input type="button" value="compatMode" onclick="alert(compatMode);"></form>
相當(dāng)于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>以上兩種寫(xiě)法的代碼在所有瀏覽器中都將彈出 document.compatMode 的值。
將上述代碼中的 'compatMode' 替換為 'method',則在各瀏覽器中都將彈出 'get',即 INPUT 元素所在表單對(duì)象的 method 屬性值。
注:
1. 這段代碼僅為說(shuō)明問(wèn)題而模擬各瀏覽器的行為,并非表示所有瀏覽器都是如此實(shí)現(xiàn)的。
2. 是使用 this 關(guān)鍵字還是直接使用這個(gè) DOM 對(duì)象,在各瀏覽器中有差異,詳情請(qǐng)看本文 2.1 中的內(nèi)容。
3. 是否添加 FORM 對(duì)象到作用域鏈中,各瀏覽器在實(shí)現(xiàn)上也有差異,詳情請(qǐng)看本文 2.2 中的內(nèi)容。
參考 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 在向作用域鏈中添加對(duì)象時(shí),使用了 'this' 關(guān)鍵字,并且通過(guò)判斷 'this.form' 是否存在來(lái)決定是否添加 FORM 對(duì)象到作用域鏈中。
其他瀏覽器中也有類似的實(shí)現(xiàn)方式,但在各瀏覽器中,將目標(biāo)對(duì)象(即綁定了此內(nèi)聯(lián)事件處理函數(shù)的對(duì)象)添加到作用域鏈中的方式有差異,判斷并決定是否在作用域鏈中添加 FORM 對(duì)象的方法也不相同。
各瀏覽器都會(huì)將內(nèi)聯(lián)事件處理函數(shù)所屬的元素的 DOM 對(duì)象加入到作用域鏈中,但加入的方式卻是不同的。
如以下代碼:
<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>在各瀏覽器中運(yùn)行的結(jié)果如下:
| IE Chrome | Hi, I'm here! |
|---|---|
| Firefox Safari Opera | hello |
可見(jiàn),各瀏覽器將內(nèi)聯(lián)事件處理函數(shù)所屬的元素的 DOM 對(duì)象加入到作用域鏈中的方式是不同的。
在 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í)行上下文,這個(gè)差異造成的影響并不多見(jiàn)。
各瀏覽器都會(huì)將內(nèi)聯(lián)事件處理函數(shù)所屬的 FORM 對(duì)象加入到作用域鏈中,但如何判斷該元素是否“屬于”一個(gè)表單對(duì)象,各瀏覽器的處理方式則不相同。
如以下代碼:
<form action="." method="get"> <div> <span onclick="alert(method);">click</span> </div></form><script>document.method="document.method";</script>
在各瀏覽器中,點(diǎn)擊 SPAN 元素后彈出的信息如下:
| IE Safari Opera | get |
|---|---|
| Chrome Firefox | document.method |
可見(jiàn):
如果將以上代碼中的 SPAN 元素更換為 INPUT 元素或其他表單元素,則在所有瀏覽器中的表現(xiàn)將一致。
當(dāng)一個(gè)內(nèi)聯(lián)事件處理函數(shù)中訪問(wèn)的變量意外的與該函數(shù)作用域鏈中非全局對(duì)象(window)的其他對(duì)象的屬性重名,將導(dǎo)致該變量的實(shí)際值不是預(yù)期值。
假設(shè)有以下代碼:
<button onclick="onsearch()"> click here </button><script>function onsearch(){ alert("Click!");}</script>作者本意為點(diǎn)擊按鈕即彈出“Click!”信息,但 WebKit 引擎瀏覽器的 HTMLElement 對(duì)象都有一個(gè)名為 onsearch 的事件監(jiān)聽(tīng)器,這將導(dǎo)致上述代碼在 Chrome Safari 中不能按照預(yù)期執(zhí)行。本例中由于該監(jiān)聽(tīng)器未定義(為 null),因此將報(bào) “Uncaught TypeError: object is not a function” 的錯(cuò)誤。
附:在上述代碼中,追加以下代碼確認(rèn) 'onsearch' 的位置:
<script>var o=document.getElementsByTagName("button")[0];if("onsearch" in o)alert("當(dāng)前對(duì)象有 onsearch 屬性。");if(o.hasOwnProperty("onsearch"))alert("onsearch 屬性是當(dāng)前對(duì)象私有。");</script>假設(shè)有以下代碼:
<form action="xxx" method="get"> ... <a href="#" onclick="submit();">click</a></form>
作者本意為點(diǎn)擊 A 元素后調(diào)用 FORM 的 'submit' 方法,但 Chrome Firefox 并未將 FORM 對(duì)象加入到該內(nèi)聯(lián)事件處理函數(shù)的作用域鏈中,因此以上代碼在 Chrome Firefox 中并不能正常運(yùn)行。
1. 盡量不要使用內(nèi)聯(lián)事件處理函數(shù),使用 DOM 標(biāo)準(zhǔn)的事件注冊(cè)方式為該元素注冊(cè)事件處理函數(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í),要保證該函數(shù)內(nèi)試圖訪問(wèn)的變量是位于全局作用域內(nèi)的,而不會(huì)因該函數(shù)獨(dú)特的作用域鏈而引用到非預(yù)期的對(duì)象。最簡(jiǎn)單的辦法是使用前綴,如 'my_onsearch'。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注