DOM操做是JS中的一大重點,本次回顧DOM操做,總結如文。javascript
儘管是一篇總結,但我儘可能把內容說清,邏輯理順,我的認爲讀起來仍是容易接受。對許多細節和注意點進行標註,須要展開的內容,也附上拓展連接。css
但願能合各位看官的口味,廢了好幾天,但願求各位一讚,你看收藏夾那麼多灰,不介意一塊兒分點吧?剛開始寫點博文,但願給點鼓勵。html
配套腦圖: html5
本文面向HTML,不討論XML等文檔,下文中「文檔===HTML」。java
節點:前面提過,節點是DOM樹的組成單元。在JS看來,一個節點就是JS對象。下面用node
表示任意的節點。git
節點類型:並不是全部的節點都是同樣的,DOM規定文檔中有12種節點類型,分別用常量1 ~ 12
(有與之對應的常量名稱Node.XXX_NODE
)表示,能夠經過node.nodeType
屬性獲取節點的類型常量。web
如今,有些類型的節點已經棄用了,常見到的只有幾種類型的節點,包括:面試
元素節點: 類型常量爲Node.ELEMENT_NODE
或1
。最多見的一類節點,對應文檔中的元素。大部分DOM操做都是在元素節點層次的。編程
文本節點:類型常量爲Node.TEXT_NODE
或2
。對應文檔中的文本,任何文檔內容都有對應的文本節點,即便空格和換行符。
空格和換行不會對頁面內容產生影響,但它們確實以文本節點的形式存在於DOM樹中。
Document節點:類型常量爲Node.DOCUMENT_NODE
或9
。它不對應文檔的內容,而是做爲文檔的入口節點,每一個文檔都有且僅有一個入口,由於這種獨特性,賦予一個特殊的變量名稱document
。
註釋節點:類型常量爲Node.COMMENT_NODE
或8
。它對應文檔中的註釋標籤,文檔的註釋內容也是可讀取和修改的。
節點類:DOM內置許多節點類,類之間存在繼承關係,造成一套節點類框架。每一個節點對象都屬於節點類,擁有該類和其父類的方法與屬性,這使得操做節點十分簡單。節點類框架的一部分大概如圖:
這些節點上有豐富的屬性和方法,是繼承的結果,能夠看到,一個HTML標籤元素至少有四層的繼承關係。
以<a>
標籤爲例,它屬於HTMLAnchorElement
類,得到了a.target
,a.download
等屬性,接着繼承了HTMLElement
類上的title
, hidden
等屬性和click()
等方法,又從Element
類繼承了tagName
, className
等屬性和getAttribute()
, setAttribute()
等方法,再從Node
類繼承了nodeType
(前面說過的節點類型), appenChild()
, removeChild()
等方法,最後從EventTarget
類中繼承了事件相關的屬性和方法。
不要混淆節點類型和節點類這兩個概念。前者是一個生活中的類別,後者是編程意義上的類。節點對象的
nodeType
屬性表示了它的類型,而節點類是該節點的從屬的類。由於Dode
是一個抽象類,因此,若是知道了某個節點從屬的類,咱們就知道它的節點類型。
區分節點與元素節點。咱們常常關心元素節點(簡稱元素),由於這是一類最常使用的節點,可是並不是全部節點都是元素。
前面的說法太抽象了,讓咱們用實際的例子看看文檔、DOM樹與節點的關係。
Live DOM Viewer是一個能夠根據HTML文檔實時查看DOM樹的網站。你把下面的例子複製過去,或者本身去探索。
一個簡單的HTML文檔:
<!DOCTYPE HTML>
<html>
<body>
A simple text.
<ol title="this is a title">
<li>czpcalm</li>
<!-- comment -->
</ol>
</body>
</html>
複製代碼
它對應的DOM圖(顏色區分了節點類型):
留意這個圖,你要注意幾點:
總共有4種類型的節點,分別是標籤節點
(紅色),文本節點
(灰色),註釋節點
(黃色)和DOCTYPE節點
(紫色)。
文檔沒有<head>
標籤卻出現了HEAD
節點。這是由於HTML必然存在<html>
,<head>
,<body>
標籤,不存在時會自動補上。順便一提,當出現<table>
標籤時,也必定會有<tbody>
標籤。
文檔中的文本都會造成文本節點的內容,包括空格␣
和換行↵
。第一,單獨的空格和換行都會造成對應的文本節點
;第二,有內容的文本節點的值包含前導和後繼的空白。
不是說HTML中的空白字符都被忽略嗎?怎麼這裏又說全都是有效的字符?
在從文檔解析生成DOM樹的過程當中,HTML中的任何字符都是有效的;不過,在接下去的頁面渲染的過程當中,空白內容被忽略。因此從文檔到頁面的整過過程當中,空白確實被忽略了。
理解DOM樹中的父子關係對應文檔中的包含(嵌套)關係。一個極佳的類比是文件樹,把元素看作文件夾,文本看作文件,文件夾中能夠存放文件和新的文件夾,而後一層層深刻下去,DOM樹也是如此。
幾個原則能夠幫咱們快速理解這個DOM樹的構建:
自動補全
, 上面提到的自動添加必要元素,另外,還會自動補齊缺乏的關閉標籤。<head>
前面的空白會被忽略(歷史緣由)。<body>
後若是有內容,會被移到<body>
裏面。操做節點前,先要找到節點。導航是從一個節點到另外一個節點;搜索是從一個範圍中選出知足條件的節點。
Node
的類規定了節點具備的許多屬性,方便咱們從某個節點中找到跟它相關的另外節點。
頂級節點通常直接獲取:
document
-- 入口節點。document.documentElement
-- HTML節點。document.head
-- head節點。document.body
-- body節點。對node
節點,有如下屬性:
node.parentNode
-- 獲取節點的父節點。
node.previousSibling
-- 獲取節點的上一個兄弟節點。
node.nextSibling
-- 獲取節點的下一個兄弟節點。
node.childNodes
-- 獲取節點的孩子節點列表
。沒有子節點返回空列表。
node.firstChild
和node.lastChild
-- 獲取第一個和最後一個孩子節點,同node.childNodes[0]
和node.childNodes[node.childNodes.length-1]
。
助記:都是兩個單詞拼寫的。
這些是屬性,不是方法。不要錯誤使用bode.childNodes()之類的。
以上導航是基於節點的,包括元素節點、文本節點、註釋等。如1.3中的例子,document.body.firstChild
獲得的是A simple text
所在的文本節點。
由於咱們常常只關心元素節點,DOM也提供了一組純元素的導航屬性, 對元素elem
或者節點node
, 有:
node.parentElement
-- 父元素節點,該屬性來自Node
類。elem.previousElementSibling
-- 上一個兄弟元素elem.nextElementSibling
-- 下一個兄弟元素elem.children
-- 孩子元素列表elem.firstElementChild
和elem.lastElementChild
,第一個和最後一個孩子元素節點助記:children特殊,其它都有Element,沒有Node。
解惑:
node.parentNode
與node.parentElement
有區別嗎?父節點不該該都是元素節點嗎?通常狀況下,兩者等效,但html.parentNode === document
, document不是元素節點。
提醒:上面的導航屬性都是隻讀屬性。
elem.parentElement = anotherElem; 是錯誤的
。
常常地,咱們老是直接從文檔中找出知足某些條件的元素,從而獲取到目標元素。
document/element.getElementBy*()
系列getElementBy*
系列方法大家確定不陌生,但我想要提醒的是,注意個人寫法document/element.getElementBy*()
,表示這是兩個不一樣的類上的方法(參看上面節點類)。
首先,是兩個來自Document
類的方法:
document.getElementById(id)
: 根據id獲取文檔中的元素。document.getElementsByName(name)
: 根據name獲取文檔中的元素。不多使用。其次,是Document
類和Element
類都具備的方法:
document/element.getElementsByTagName(tagName)
: 根據標籤名稱獲取文檔或某個元素內的元素。document/element.getElementByClassName(className)
:根據類名獲取文檔或某個元素內的元素。注意:
沒有element.getElementById(id)
。
注意:不要忘記或多加了
s
。除了document.getElementById()
,其它方法的返回結果都是一個集合,沒有知足條件的元素則是空集合。
document/element.querySelectorAll/querySelector()
如今,推薦使用一組更強大的搜索新方法, 它們支持CSS選擇器:
document/element.querySelectorAll(CSSSelector)
:返回知足選擇器的一組節點列表。document/element.querySelector(CSSSelector)
: 返回第一個知足選擇器的元素。補充:
elem.matches(selector)
能夠檢查某個元素是否與選擇器匹配。
querySelector*
源自Selectors API 規範,與CSS選擇器結合,更加靈活,強大,已被全部現代瀏覽器支持。是如今比較推薦的作法。
getElementBy*()
源自DOM2標準,被認爲是傳統接口,老項目中普遍使用。或者須要兼容IE8以前使用。可是有較好的性能,如今仍然有人在用。
補充:做爲結果的集合。在以集合返回結果的時候,或者返回一個
NodeList
對象,或者返回一個HTMLCollection
對象,通常來講,兩者都是可迭代的類數組對象,能夠經過下標範圍或for...of
遍歷。注意:大部分結果集,包括
getElementBy*
和node.childNode
等,都是動態的,即便查詢並保存,以後若是修改文檔,該結果集也會隨之變化。但querySelectorAll()
返回的結果是靜態的。
有一些特殊的元素搜索方法,就我所知有:
elem.closest(selector)
-- 在elemd
的父元素上查找最近一個知足選擇器的元素。document.elementFromPoint(clientX,clientY)
-- 返回相對當前視口座標嵌套最深的(最上方)的元素。這一節涉及對某個節點的操做,通常不會引發文檔內容(DOM樹)的變化。
這些操做基於**Node**接口,對全部節點都是通用的。
判斷節點類型:node.nodeType
或node instanceof <NodeClass>
。二者均可以用於判斷節點類型,當須要明確的節點類的時候,只能經過後者。
node.nodeType === Node.ELEMENT_NODE; //或node.nodeType === 1;
node instanceof Element; //與上面等效
node instanceof HTMLInputElement; //判斷是不是輸入元素
複製代碼
獲取節點名稱:node.nodeName
,對於元素節點,返回對應的標籤名稱,如audio
。對其它類型節點,返回#與節點類型字符串,如#text
, #comment
,#document
。也能經過節點名稱判斷節點類型,但基本不用。
獲取或設置節點值:node.nodeValue
,文本節點或註釋節點返回文本內容,元素節點與document節點返回null。讀寫屬性,支持node.nodeValue = "A simple text"
。一樣的,空白文本也被包含在內容裏。
提醒:文本節點和註釋節點有一個
data
屬性,使用與nodeValue
相同,但它不是在Node接口上的。
判斷節點是否擁有子節點:node.hasChildNodes()
,返回true當節點有子節點時。
判斷節點是否擁有特定子節點:node.contains(childNode)
,返回true當childNode是node的子節點。
大部分狀況下咱們都是在元素節點上操做它的文本子節點,因此元素是咱們最關心的節點,**Element**接口提供了更多的屬性和方法。這裏只考慮HTML元素。
解惑:那何時須要操做文本節點?會看1.3節的例子,
A simple text
的父節點是body
,但body有其它元素節點。假如沒有文本這種類型的節點,很難在body節點上只修改A simple text
的內容。
判斷元素類型:elem.tagName
或elem.nodeName
,效果同樣,返回標籤的字符串,如audio
。另外,使用instanceof
能夠實現不一樣級別的類型判斷。
元素內容:有幾個屬性和元素內容相關:
elem.innerHTML
-- 獲取或設置元素內的HTML片斷。設置的內容會被當成HTML片斷解析,可能會引發文檔結構的變化。
注意:HTML片斷內的腳本不會執行。
elem.textContent
-- 獲取或設置元素的文本內容(標籤被忽略)。設置的文本以安全模式(不會被解析)寫入。
特性(attribute)是指html中寫在標籤內的特性,而屬性(property)是隻元素節點做爲編程對象具備的屬性。
特性 -- 屬性同步機制
:對標準規定的特性,元素對象具備響應的讀寫屬性, 如a.href
。這種機制極大的方便了在JS中獲取或修改元素的特性。
提醒:對不一樣HTML元素,規定的特性不一樣,屬性也就不一樣,如: 存在
a.href
但不存在div.href
。
通用的特性操做接口:
elem.hasAttribute(name)
-- 檢查是否存在某個特性。elem.getAttribute(name)
-- 獲取某一特性的值。elem.setAttribute(name, value)
-- 設置某一特性。elem.removeAttribute(name)
-- 刪除某一特性。elem.attributes()
-- 獲取全部的特性對,每一個特性對具備name
,value
屬性。特殊的data-*
:data-*特性是一種合法且安全的傳遞自定義數據的方式。可經過elem.dataset.name
讀取或修改data特性的值。屬性名稱採用駝峯寫法,如elem上的data-apple-price
對應elem.dataset.applePrice
。
修改樣式有兩種方式,一是把樣式寫到某個類裏,而後在代碼中修改元素的類,一是直接修改elem.style.*
。前者適用於隨狀態改變樣式的狀況,在代碼可維護性上更加,用得較多。後者適用於頻繁計算或切換的樣式。
elem.classList
: 一個包含elem全部類的可迭代的類數組對象。這個對象有幾個方法,方便咱們改變元素的類。
elem.classList.contains(class)
-- 檢查是否有某個類。elem.classList.add(class)
-- 添加某個類。elem.classList.remove(class)
-- 移除某個類。elem.classList.toggle(class)
-- 切換某個類,若是有就刪除,沒有就添加。elem.className
: 一個讀寫屬性,把元素的class特性當成一個總體看待。如HTML中某個元素elem存在特性class="first red"
,那麼elem.className
的值爲first red
。區分:經過
elem.classList
或elem.className
均可以對元素的類進行改動,前者更加靈活,且具備相應的方法,後者是一個總體的字符串屬性,適合刪除全部的類從新設置。
若是須要直接設定元素的樣式,能夠設置elem.style.*
。
elem.style.width = "20px"; //設置元素寬度
elem.style.backgroundColor = "red"; //設置顏色
複製代碼
提醒:多詞CSS屬性的轉化方式也是駝峯寫法,如
padding-left
對應elem.style.paddingLeft
,-webkit-border-radius
對應elem.style.WebkitBorderRadius
,-
意味着大寫。
補充:常用
node.hidden = true
實現節點隱藏,也能用於隱藏文本,在元素上,它的效果與elem.style.display:none
一致。
這種方式其實是經過元素上的style
特性實現的,它的優先級高於通常的CSS,因此都是有效的,除非你在其它地方用了!important
。elem.style.*
也是可讀取的,**可是它們只會讀取元素中style
特性存在的屬性,**對其它CSS是無效的。但咱們每每須要的是最終應用在元素的樣式數值。這種時候,須要使用
getComputedStyle(elem)
:獲取元素最終應用的樣式,它返回一個樣式對象,好比,可經過getComputedStyle(elem).backgroundColor
獲取顏色值。注意:
getComputedStyle
不支持簡寫屬性,如getComputedStyle(elem).padding是無效的。
<style> div { font-size: 20px; } </style>
<div id="div" style="padding:20px;">
czpcalm
</div>
<script> console.log(div.style.fontSize); //"" style特性上沒有該CSS屬性,因此爲空 console.log(div.style.padding); //20px console.log(getComputedStyle(div).fontSize); //20px </script>
複製代碼
若是須要清除代碼設定的樣式
, 能夠把elem.style.cssAttr
設爲""
(空字符串),反作用是原來html中的特性樣式也會被清除。
還有一種基本不會使用的重寫整個元素style特性的方式elem.cssText = csstext
,csstext不包含選擇器和花括號。
當設計元素的大小變化或位置移動時,咱們須要獲取元素的位置或尺寸。設置則用CSS方式設置。
位置是相對於參照物的,一個元素,有相對於定位父元素
,相對於視口
,相對於文檔
三種關係位置。
提醒:定位父元素是指CSS定位元素(position爲relative,absolute,fixed)或td,th,table元素,或者是body元素。
相對於定位父元素:elem.offsetLeft/offsetTop
,相對於參照父節點的左/上邊距。elem.offsetParent
獲取元素的定位父元素。
相對於視口:**elem.getBoundingClientRect()**獲取元素的定位矩形elemRect
。
elem.getBoundingClientRect().left/top/right/bottom
分別表示元素盒子(含邊框)四角到視口左或上邊的距離。elem.getBoundingClientRect().width/height
, 與elem.offsetWidth/offsetHeight
等效,盒子的寬高。elem.getBoundingClientRect().x/y
,通常狀況下,與elem.getBoundingClientRect().left/top
等效,且不兼容IE,不推薦使用。相對於文檔:沒有直接獲取的方式,但能夠經過相對於視口+滾動距離
簡單計算。
elem.scrollTop + elem.getBoundingClientRect().top
。elem.scrollLeft + elem.getBoundingClientRect().left
。元素盒子的尺寸也有多種狀況,須要考慮邊框、內邊距、是否爲標準盒子模型、甚至是否有滾動條。
含邊框的盒子尺寸:elem.offsetWidth/offsetHeight
或者elem.getBoundingClientRect().width/height
均可以獲取含邊框的寬高。
邊框寬度:elem.clientLeft/clientTop
-- 元素左上角邊框的寬度。能夠理解爲左邊框寬度和上邊框的寬度,若是存在滾動條,也包含滾動條的寬度。
補充:通常來講,上邊框和左邊框是經常使用的。若是四條邊框寬度不一,能夠經過
getComputedStyle(elem).borderRight
獲取,注意這是含單位的字符串。
elem.clientWidth/clientHeight
-- 內容寬度高度,包含padding,不包含滾動條。
不含邊框:elem.clientWidth/clientHeight
,獲取元素盒子內容寬高,不含邊框和滾動條,含padding。
內邊距問題與盒子類型:在涉及內邊距的時候,須要getComputedStyle(elem)
方法獲取,而且須要考慮是否爲標準盒子。
若是元素的內容存在滾動時,咱們可能須要知道與滾動相關的尺寸:
elem.scrollLeft/scrollTop
-- 水平和垂直方向上滾動的長度。可寫屬性,經過設置該值改變控制內容滾動。
elem.scrollWidth/scrollHeight
-- 元素內容的長度和寬度,包括滾動的內容。
區分:在沒有內容溢出發生滾動時,
clientWidth/Height
與scrollWidth/Height
等效;存在滾動時,前者是盒子的可視內容大小,後者是內容的大小,包括須要滾動查看的部分。
關於位置和尺寸,沒有圖片難以說清,不理解請參看:
DOM操做中,常常須要修改文檔結果或內容。這類操做涉及節點的插入、移除、替換等。
文檔的操做有一套普遍使用的傳統方法。也有一套新推出的API方法,它們更加靈活易用,但IE不兼容。
插入節點能夠分三步走:
建立一個節點:
let elem = document.createElement(tagName)
。let text = document.createTextNode(data)
。let dupNode = node.cloneNode(deep)
, deep爲true表示深拷貝,經常使用。默認爲false。編輯節點的屬性和內容
把節點插入文檔樹中
傳統方式:傳統方式須要在父節點上執行對節點的插入
parentNode.appendChild(node)
-- 把node做爲最後一個子節點插入。parentNode.insertBefore(node, nextSibling)
-- 在nextSibling以前插入node。現代方式:能夠實現多位置插入,你能夠在父節點上執行插入或在兄弟節點上執行插入。
parentNode.prepend(...nodes or strings)
。在第一個子節點以前插入。parentNode.append(...nodes or strings)
。在最後一個子節點以後插入。nextSibling.before(...nodes or strings)
。在本節點以前同級插入。previousSibling.after(...nodes or strings)
。在本節點以後同級插入。提醒:參數的形式說明它們支持一次插入多個,而且字符串會做爲文本節點插入。
有一種狀況,咱們但願直接描述節點的插入HTML代碼段,這種時候,可使用以前的elem.innerHTML
屬性, 或者使用**elem.insertAdjacentHTML(position, html)**進行插入。其中,position
的可選值有:"beforebegin"
, "afterbegin"
, "beforeend"
, afterend
.
區分:節點對象存在不一樣於節點在文檔樹中。前者只是在代碼中能夠經過node等變量名稱引用節點,但建立一個節點,並不會對文檔樹結構有影響,只有插入文檔樹後,節點才成爲文檔的一部分。反之,從文檔中移除節點,並不會致使節點變量實效,它仍是存在,能夠繼續被修改,並在某個時間點從新插入文檔中。
從文檔樹中移除更加簡單。
node.remove()
,移除節點。但IE不兼容,須要使用傳統方式,獲取其父節點,在父節點上移除子節點:node.parentNode.removeChild(node)
。與移除相似,使用node.repalceWith(...nodes or strings)
,一樣IE使用parentNode.replaceChild(newNode, node)
。
事件
是某事發生的信號, 全部的DOM對象都具備這些信號。事件處理程序
是當一個事件信號發生時運行的函數,用於對事件做出響應。事件處理
就是爲事件分配正確的處理函數,在事件發生時做出正確處理。事件類型
是事件的分類,常見的事件類型有
click
, contextmenu
, dbclick
, mousedown
, mouseup
,mouseover
, mouseout
等。keydown
, keyup
focus
, blur
, focusin
, focusout
,submit
, reset
cut
, copy
, paste
load
, unload
, error
, abort
事件對象
是事件在編程上的對象,具備不少和事件相關的屬性。經常在處理函數調用時自動做爲參數傳入。**假設一個div元素中放了一個button元素,若是點擊了button,算不算點擊了div?若是算,是先點擊了button仍是先點擊了div?**由於元素的嵌套關係,使得事件的產生對象每每是嵌套的,而不是獨立的,在上面的中,還有body元素,html元素,甚至整個文檔document。
第一個問題,對大多數的事件來講,應該是確定的,一個在子元素上發生的事件,也看作在該元素上發生了。
第二個問題,也被當作事件流
問題,即如何肯定事件在節點中的傳播順序。巧的是,當年IE和Netscape分別提出了事件冒泡
和事件捕獲
這兩個幾乎徹底相反的概念。
button->div->body->html->document
。document->html->body->div->button
。爲了統一這兩種觀點,DOM事件規範提出了3階段的事件流:事件捕獲階段->目標階段->事件冒泡階段
。
然而,事件捕獲階段不多被使用,下面的討論會忽略事件捕獲,默認事件從目標上開始冒泡。
注意:並不是全部事件都會冒泡。
有3種爲設置事件處理函數的方式:
on<event>
特性。on<event>
屬性。node.addEventListener(event, handler, capture)
和removeEventListener(event, handler)
。capture
是一個布爾值,表示是否在捕獲階段響應。對比:HTML特性的方式具備很大的侷限性,代碼量限制,維護性差,某些事件不支持等。DOM0級事件也對某些事件不支持,關鍵在於沒法爲一個事件類型分配多個處理函數。DOM2解決了這些問題,是最通用的方式,推薦使用。
<div id="div1" onclick="alert(event.type+': div1')">div1</div>
<div id="div2" onclick="handler('div2',event)">div2</div>
<div id="div3">div3</div>
<div id="div4">div4</div>
<script> function handler(message, event) { alert(event.type + ": " + message); } div3.onclick = e => handler("div3", e); //處理函數具備多參數,須要嵌套調用。 div4.addEventListener("click", e => handler("div4", e), false); </script>
複製代碼
上面的例子中, div1和div2使用了HTML特性添加事件處理,div3使用DOM0級事件處理,div4使用DOM2級事件處理。
HTML特性的內容會成爲事件處理函數的內容,DOM事件處理賦值的是函數對象。好比對一個處理函數func
,特性上的寫法是onclick="func()"
,而DOM0級事件處理的寫法是node.onclick=func
。區分它們的主要點在於理解特性的內容會被套上一個函數外殼,而後成爲處理函數。onclick="func()"
最後的效果是:
node.onclick = function(event){ //傳遞的事件參數名爲event
func(); //這一行是來自特性
}
複製代碼
這樣,你應該能明白爲何特性上是函數調用,而DOM上是函數對象賦值了。
由於這種設定,還產生兩個要注意的點。
第一是事件對象參數event
,若是你習慣了在DOM事件處理中使用變量名稱e
而不是event
,那麼在特性內容上可能寫出相似e.type
的表達,這是錯誤的,由於特性傳入的事件變量名是event
。
第二,在試圖阻止事件的默認響應的時候,可能會寫出這樣的代碼:
<a href="www.baidu.com" onclick="handler()">百度</a>
<script> function handler(){ /*....處理點擊事件*/ return false; //返回false阻止跳轉 } </script>
複製代碼
然而點擊以後仍是發生跳轉了,問題出在哪裏?讓咱們看一下最後生成的處理函數:
a.onclick = function(event){
handler();
}
複製代碼
事件處理函數只是調用了handler
,handler
返回了false,而事件處理函數並無理會,因此,外部看來,事件處理函數沒有返回值。正確的寫法應該是onclick="return handler()"
或者onclick="handler();return false;"
。
DOM2級事件處理以前,一個事件只能對應一個事件處理函數。若是你想解除事件處理函數,能夠簡單的採用node.on<event>=null
的方式。
DOM2node.addEventListener()
支持添加多個處理函數,這些處理函數的執行順序與添加順序一致,可使用node.removeEventListener(event, handler)
解除處理函數。
注意:
removeEventListener(handler)
必須使用添加時的同一個函數,而不是具備相同執行體的函數。node.addEventListener("click", ()=>alert("直接添加函數表達式將沒法別移除!"), false); let handler = ()=>alert("something") node.addEventListener("click", handler, false); node.removeEventListerner("click", ()=>alert("somthing")); //無效,傳入的是不一樣的函數對象 node.removeEventListener("click", handler); //成功移除 複製代碼
讓咱們看另外一個問題,事件處理函數的參數問題。
咱們只編寫事件處理函數,函數在事件發生時自動被調用,它的調用語句相似這樣
node.handler(event); //event在這裏是實參,這句語句是函數調用
複製代碼
咱們要根據調用規範本身的handler
函數。
調用時傳入了一個參數,根據JS函數參數的特色,若是在事件處理函數中沒有使用到event
,那處理函數能夠不聲明這個參數。其次,形參可使用任何合法變量名,咱們能夠不函數定義寫成handler(e)
,而後在函數內使用e
而不是event
。
它的調用方式決定了handler
最多隻能有一個參數。但在事件處理時確實有須要多個參數的時候,這種時候要使用嵌套的函數調用,如上面的例子。
最後,注意這種調用方式使得函數內部this === node
。這頗有用,方便咱們在事件處理的時候獲取到事件發生對象的信息。
事件對象event
在調用事件處理函數時自動傳入,它具備不少屬性和方法,在事件處理的時候大有用處。
event.preventDefault()
-- 取消事件的默認行爲,只有event.cancelable
爲true時纔有效。
補充:若是使用
on<event>
方式添加處理函數,在函數返回false也能夠取消默認事件。
event.stopPropagation()
-- 中止事件的繼續冒泡,上層的事件響應不會再發生。
event.stopImmediatePropagation()
-- 中止事件繼續響應。包括事件冒泡和當前目標的其它處理函數也不會發生。
event.type
-- 事件類型字符串,在使用一個處理函數處理多類事件時,能夠判斷當前發生的事件類型。
**event.target
與event.currentTarget
**-- 事件的最小目標和事件的當前目標。
區分:事件在最小目標上發生,而後往上冒泡的過程當中,
event.target
始終不變,指向最小目標,但event.currentTarget === this
,會隨着冒泡過程指向當前正在處理的節點。
event.phase
-- 事件流階段,整數,1表明捕獲,2表明目標階段,3表明冒泡階段。
event.bubbles
-- 布爾值,事件是否冒泡。
event.cancelable
-- 布爾值,事件是否可取消默認行爲。
event.trusted
-- 布爾值,若是事件是瀏覽器發生的,爲true,若是事件是js代碼發生的,爲false。
上面的屬性都是只讀的。
這些屬性和方法是通用的,對具體的事件類型,有更多的事件屬性和方法,將在每種事件中詳細說明。
鼠標事件是最多見的一類事件,有:
click
-- 鼠標左鍵點擊觸發,或觸摸屏的點擊。
提醒:點擊一次的含義是鼠標在目標上按下並鬆起,若是鼠標按下後滑動到元素外部鬆起,或者元素位置變化致使鼠標鬆起時再也不元素上方,不能造成有效點擊。
contextmenu
-- 鼠標右鍵點擊事件,該事件瀏覽器通常有默認的菜單,若是須要實現自定義菜單,須要阻止默認行爲。
dbclick
-- 雙擊鼠標左鍵。雙擊具備選擇文本的默認行爲。
mousedown/mouseup
-- 鼠標任意鍵按下/鬆起。
mouseenter/mouseleave
-- 鼠標進入/離開元素,不會冒泡。
mouseover/mouseout
-- 鼠標進入/離開元素,會冒泡,進入/離開子元素時也會觸發。
區分:
mouseenter/mouseleave
與mouseover/mouseout
事件相似,可是後者會冒泡,且在進入子元素會觸發mouseout
。
mousemove
-- 鼠標按下後鬆起前發送移動。
鼠標點擊時,會發生一系列事件,它們具備特定的順序,以某次雙擊爲例,依次觸發事件mousedown->mouseup->click->mousedown->mouseup->click->dbclick
。
鼠標事件有一些適合獲取事件相關信息的屬性:
鼠標按鍵:按鍵屬性只對mousedown/mouseup
有意義,event.button
,數字1~5
,表明鼠標上的按鍵,依次是:鼠標左鍵,中鍵,右鍵,前進,後退。
座標:它們是事件發生時刻(定點類)的座標,或者實時的(mousemove
)座標。
event.pageX/pageY
-- 相對於文檔的座標。event.clientX/clientY
-- 相對於窗口的座標。event.screenX/screenY
-- 相對於屏幕的座標,較少使用。組合鍵:在鼠標事件發生時,若是下列按鍵被按下,對應的屬性爲true。用於在一個事件類型上綁定多種任務。
event.shiftKey
-- shift鍵
是否被按下。event.ctrlKey
-- ctrl鍵
是否被按下。event.altKey
-- alt鍵
是否被按下。event.mateKey
-- cmd鍵
(Mac專用)是否被按下。提醒:若是想處理
ctrl鍵
,應該注意在Mac下使用cmd鍵
,因此應該判斷if(event.ctrlKey||event.cmdKey)
。
相關目標:event.relatedTarget
。mouseenter/mouseleave
和mouseover/mouseout
事件的屬性。若是鼠標從divA->divB
,在divA上發生mouseout(mouseleave),event.target === divA
而且event.relatedTarget === divB
。相反,在divB上發生mouseover(mouseenter),event.target ===divB
且event.relatedTarget === divA
。
這是一篇鼠標拖放事件的文章,應該能對鼠標事件的使用有所幫助。
鍵盤事件經常被用於建立各類熱鍵。如今普遍使用的有兩類鍵盤事件:
keydown
-- 按下任意鍵盤觸發。持續按住按鍵會持續觸發該事件。keyup
-- 鬆開任意按鍵。補充:不要試圖監聽
Fn鍵
,它是在比OS更低的級別上實現的,沒有鍵盤事件。補充:有一個被取消的
keypress
事件,這裏不討論。
鍵盤事件最重要的屬性是鍵碼,咱們經常須要獲取鍵盤的鍵碼,根據按下的鍵盤作出響應。
event.code
-- 鍵碼。鍵碼是惟一的。在按鍵判斷時,常用的屬性。鍵碼是字符串,常見規則:
Digit<num>
或Numpad<num>
(小鍵盤)。如Digit2
, Numpad2
Key<letter>
, 如KeyZ
, KeyA
F4
, Tab
,Enter
Left/Right
區分左右,左shift的鍵碼爲ShiftLeft
, 右shift的鍵碼爲ShiftRight
。補充:一個被廢棄的
event.keyCode
與event.code
有一樣的功能,但鍵碼是基於數字的。可能在兼容IE時須要用到。
event.key
-- 鍵。表徵按鍵的含義而不是位置。跟是否按下shift
、鍵盤語言有關。如按下z鍵
時表示字符z
,而shift+z
表示字符Z
。左右shift的key
也同樣。只在使用基於意義的時候才使用event.key
判斷。
鍵盤事件也一樣支持組合鍵,可使用event.ctrlKey
, event.shiftKey
, event.altKey
, event.metaKey
獲取其它按鍵是否被按下,而不用單獨監聽它們。
有一類與資源相關的事件,認識它們的最好方式是經過頁面生命週期:
DOMContentLoaded
-- 文檔被加載而且DOM樹構建完成後(圖片、樣式可能還未加載)。load
-- 頁面內容加載完畢,全部圖片、樣式資源都已應用。此時進行的操做都是安全的。beforeunload
-- 用戶正打算離開,能夠在此時訊問用戶是否離開,或者保存一些數據。unload
-- 用戶已經離開,但仍能夠進行少許操做,如釋放資源,發送數據。load
事件是這裏最重要的一個事件。它不只能夠用於確保頁面加載完成後執行任務,還能夠添加到img
, script
, style
這些節點中,在它們完成資源加載時候進行事件處理。
表單是一類特殊的元素,須要頻繁的獲取或修改,因此,通用的節點獲取方式上,增長了額外的屬性,方便表單操做。
document.forms
-- 獲取文檔中的表單元素集合。這是一個命名集合
。命名意味着能夠經過表單名稱方式document.forms.formName
獲取表單,而集合自己又支持下標方式document.form[0]
。
form.elements
-- 獲取表單form的輸入元素集合,一樣,這是一個動態的
命名集合。能夠經過form.elements.inputName
或者form.elements[index/inputName]
獲取。
form.inputName
或form[index/inputName]
-- 獲取表單form的輸入元素。form.elements.inputName
的簡寫。
注意:
(1)radio、checkbox等多個輸入共享一個name時,
form.elements.inputName
或form.inputName
返回一個集合。(2)沒法獲取到type爲image的輸入組件。
(3)若是表單中有
<fieldset>
(輸入組),它會成爲一個form.elements
的一個元素,可進一步經過fieldset.elements
獲取表單組中的輸入控件集合。
input.formName
-- 獲取輸入元素所在的表單。form.inputName
的反向引用。
<form name="myForm">
<input type="text" name="user"/>
<input type="radio" name="sex" value="male"/>
<input type="radio" name="sex" value="female"/>
</form>
<script> let form = document.forms.myForm; //或者document.forms[0] let userInput = form.user; //或者form.elements.user let sexInput = form.sex; sexInput[0].checked = true; //選擇第一個radio console.log(sexInput.value); //male </script>
複製代碼
根據屬性--特性同步,表單對象有如下屬性,它們與對應的特性意義相同。
form.action
form.method
form.name
form.target
另外,表單有兩個經常使用的方法:
form.submit()
-- 手動提交表單form.reset()
-- 初始化表單內容一個表單含有多個輸入字段,它們能夠是各種型的input標籤
,select標籤
,textarea標籤
,button標籤
。每這些表單字段有一些通用的屬性(固然還有其它的同步屬性):
input.value
-- 字段的值。對type="file"
的input,它是隻讀的,表示計算機上的文件地址。input.name
-- 字段名稱。input.type
-- 字段類型,對textarea
是"textarea"
。對select
是"select-one"
或"select-multiple"
。input.tabindex
-- 字段的tab索引。input.disabled
-- 字段是否被禁用。input.readOnly
-- 字段是否只可讀。這些屬性都是可寫的,大大便利了咱們對錶單元素的操做。
最關鍵,要數input.value
,對不一樣字段,它有不一樣的用法:
button
, reset
, submit
類型:表示用於顯示的字符串。radio
, select(單選)
類型:一個與被選中字段value相同的值。textbox
,select(多選)
類型:一個數組,對應被勾選的選線的值。有些事件是表單特有的:
submit
-- 表單提交前發生的事件。reset
-- 點擊reset按鈕時。input
-- 在字段輸入任意內容後。change
-- 字段失去焦點後,與以前輸入發生變化時。此外,也有一些事件在表單上發揮了主要做用:
pressdown
focus/blur
-- 得到或焦點時,不會冒泡。
focusin/focusout
-- 得到或失去焦點時,與上相似,可是會冒泡。
cut/copy/paste
-- 剪切板事件
補充:能夠經過
e.preventDefault()
(或返回false)取消剪切板事件默認行爲,來禁用頁面/輸入的剪切板功能。event.clipboardData
可用於讀寫剪切板內容。input.addEventListener("past", (event)=>{ let data = e.clipboardData; alert("Data in clipboard: " + data+". But you can't paste!"); return false; }) 複製代碼
表單客戶端驗證幾乎成爲表單最重要的一個功能,這但是JS誕生的原因呀!
html5提供了一套表單的驗證API,但我的認爲難用且界面依賴瀏覽器,使用很少,更可能是自定義表單的驗證方式。
表單驗證主要有兩種思路,一是在客戶完成每一個字段輸入時,獨立驗證,每一個字段,全部字段都經過後才容許提交操做。這種方式主要利用change
, input
等事件。
一是在用戶發起提交操做時,對整個表單進行驗證,若是不經過則提醒用戶更正,阻止表單提交,直到全部字段正確。這種方式主要使用submit
事件。
累了,貼個圖總結吧。
花了我好幾天,一邊學習,一邊整理。感謝閱讀!