轉載自個人我的博客javascript
歡迎你們批評指正html
先來一些簡單的,在你的util.js
中完成如下任務:前端
// 爲element增長一個樣式名爲newClassName的新樣式 function addClass(element, newClassName) { // your implement } // 移除element中的樣式oldClassName function removeClass(element, oldClassName) { // your implement } // 判斷siblingNode和element是否爲同一個父元素下的同一級的元素,返回bool值 function isSiblingNode(element, siblingNode) { // your implement } // 獲取element相對於瀏覽器窗口的位置,返回一個對象{x, y} function getPosition(element) { // your implement } // your implement
其實這裏能夠先定義一個hasClass
函數。用來判斷該節點是否含有某個className。java
addClass
添加樣式。調用hasClass
函數,判斷element
是否含有待添加的新className,若沒有則添加,不然什麼都不作。git
removeClass
刪除樣式。調用hasClass
函數,判斷element
是否含有該指定樣式,若含有的話刪除該className。沒有的話什麼都不作。github
判斷siblingNode和element是否爲同一個父元素下的同一級的元素。這裏直接判斷parentNode
就能夠了吧數組
獲取element相對於瀏覽器窗口的位置,返回一個對象{x, y}。瀏覽器
這個題應該是這幾個中比較複雜的一個了。由於不能直接使用offsetLeft/Top
。offsetLeft/Top
所獲取的是其相對父元素的相對位置。當多層定位嵌套時想要獲取到當前元素相對網頁的位置就會不對。緩存
而且因爲在表格
和iframe
中,offsetParent對象未必等於父容器,因此也不能直接利用該元素的parent
來獲取位置,由於其對於表格
和iframe
中的元素不適用。ruby
經過查詢知道有一個Element.getBoundingClientRect()
方法。它返回一個對象,其中包含了left、right、top、bottom四個屬性,分別對應了該元素的左上角和右下角相對於瀏覽器窗口(viewport)左上角的距離。
可是用該方法獲取到的是元素的相對位置,在出現滾動時,距離會發生改變,要得到絕對位置時,還須要加上滾動的距離。由於Firefox或Chrome的不兼容問題須要進行兼容性處理,參考document.body.scrollTop or document.documentElement.scrollTop
最終根據兩個值,獲得絕對位置。
//其實也簡單,只須要獲取到兩個值,取其中的最大值便可。 var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
//判斷element中是否含有className爲sClass。 function hasClass(element, sClass) { return element.className.match(new RegExp("(\\s|^)" + sClass + "(\\s|$)")); } // 爲element增長一個樣式名爲newClassName的新樣式 function addClass(element, newClassName) { if (!hasClass(element, newClassName)) { element.className += " " + newClassName; } } // 移除element中的樣式oldClassName function removeClass(element, oldClassName) { if (hasClass(element, oldClassName)) { var reg = new RegExp("(\\s|^)" + oldClassName + "(\\s|$)"); element.className = element.className.replace(reg, ""); } } // 判斷siblingNode和element是否爲同一個父元素下的同一級的元素,返回bool值 function isSiblingNode(element, siblingNode) { return element.parentNode === siblingNode.parentNode } // 獲取element相對於瀏覽器窗口的位置,返回一個對象{x, y} function getPosition(element) { var position = {}; position.x = element.getBoundingClientRect().left + Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);//獲取相對位置+滾動距離=絕對位置. position.y = element.getBoundingClientRect().top + Math.max(document.documentElement.scrollTop, document.body.scrollTop); return position; }
參考資料:(還沒看完)
mini $
接下來挑戰一個mini $
,它和以前的$
是不兼容的,它應該是document.querySelector
的功能子集,在不直接使用document.querySelector
的狀況下,在你的util.js
中完成如下任務:
// 實現一個簡單的Query function $(selector) { } // 能夠經過id獲取DOM對象,經過#標示,例如 $("#adom"); // 返回id爲adom的DOM對象 // 能夠經過tagName獲取DOM對象,例如 $("a"); // 返回第一個<a>對象 // 能夠經過樣式名稱獲取DOM對象,例如 $(".classa"); // 返回第一個樣式定義包含classa的對象 // 能夠經過attribute匹配獲取DOM對象,例如 $("[data-log]"); // 返回第一個包含屬性data-log的對象 $("[data-time=2015]"); // 返回第一個包含屬性data-time且值爲2015的對象 // 能夠經過簡單的組合提升查詢便利性,例如 $("#adom .classa"); // 返回id爲adom的DOM所包含的全部子節點中,第一個樣式定義包含classa的對象
實現思路:
嗯,這個題思考了好久,網上找了不少資料但仍是不怎麼會,還達不到想要的效果,有點鑽牛角尖了。儘可能來寫一下吧。(我果真是個弱雞)。感謝秒味課堂的免費課程。
題目要求獲取到全部的節點中的第一個,因此不須要用數組來儲存獲取到的節點。
額。。想了半天,仍是使用函數包裝來實現後代選擇器比較好,因此VQuery函數返回是獲取到的完整節點對象數組,$
函數用來達到題目要求。
因此在VQuery函數中就不須要考慮空格了,直接使用switch分支,來斷定不一樣的狀況。#
、.
、[
、 [=]
。
在$
函數中,判斷字符串中是否含有空格,有空格的話須要分割成數組,數組的前一項是爲父選擇符,後一項爲子選擇符。分不一樣的狀況來調用VQuery函數,並返回對象。
/** * $函數的依賴函數,選擇器函數 * @param {string} selector CSS方式的選擇器 * @param {object} root 可選參數,selector的父對象。不存在時,爲document * @returns {Array} 返回獲取到的節點數組,須要注意的是使用ID選擇器返的也是數組 */ function VQuery(selector, root) { //用來保存選擇的元素 var elements = []; //保存結果節點數組 var allChildren = null; //用來保存獲取到的臨時節點數組 root = root || document; //若沒有給root,賦值document switch (selector.charAt(0)) { case "#": //id選擇器 elements.push(root.getElementById(selector.substring(1))); break; case ".": //class選擇器 if (root.getElementsByClassName) { //標準 elements = root.getElementsByClassName(selector.substring(1)); } else { //兼容低版本瀏覽器 var reg = new RegExp("\\b" + selector.substring(1) + "\\b"); allChildren = root.getElementsByTagName("*"); for (var i = 0, len = allChildren.length; i < len; i++) { if (reg.test(allChildren[i].className)) { elements.push(allChildren[i]); } } } break; case "[": //屬性選擇器 if (selector.indexOf("=") === -1) { //只有屬性沒有值的狀況 allChildren = root.getElementsByTagName("*"); for (var i = 0, len = allChildren.length; i < len; i++) { if (allChildren[i].getAttribute(selector.slice(1, -1)) !== null) { elements.push(allChildren[i]); } } } else { //既有屬性又有值的狀況 var index = selector.indexOf("="); //緩存=出現的索引位置。 allChildren = root.getElementsByTagName("*"); for (var i = 0, len = allChildren.length; i < len; i++) { if (allChildren[i].getAttribute(selector.slice(1, index)) === selector.slice(index + 1, -1)) { elements.push(allChildren[i]); } } } break; default: //tagName elements = root.getElementsByTagName(selector); } return elements } /** * 模仿jQuery的迷你$選擇符。 * @param {string} selector CSS方式的選擇器,支持簡單的後代選擇器(只支持一級) * @returns {object} 返回獲取到的第一個節點對象,後代選擇器時,返回第一個對象中的第一個符合條件的對象 */ function $(selector) { //這裏trim處理輸入時兩端出現空格的狀況,支持ie9+。可是這個函數實現起來也特別簡單,能夠參考我task0002(-)前面有trim函數的實現。稍微修改一下,這樣就沒兼容性問題了。 if (selector == document) { return document; } selector = selector.trim(); //存在空格時,使用後代選擇器 if (selector.indexOf(" ") !== -1) { var selectorArr = selector.split(/\s+/); //分割成數組,第一項爲parent,第二項爲chlid。 //這裏沒去考慮特別多的狀況了,只是簡單的把參數傳入。 return VQuery(selectorArr[1], VQuery(selectorArr[0])[0])[0]; } else { //普通狀況,只返回獲取到的第一個對象 return VQuery(selector,document)[0]; } }
咱們來繼續用封裝本身的小jQuery庫來實現咱們對於JavaScript事件的學習,仍是在你的util.js
,實現如下函數
// 給一個element綁定一個針對event事件的響應,響應函數爲listener function addEvent(element, event, listener) { // your implement } // 例如: function clicklistener(event) { ... } addEvent($("#doma"), "click", a); // 移除element對象對於event事件發生時執行listener的響應 function removeEvent(element, event, listener) { // your implement }
這裏慕課網的視頻講的特別清楚,就不贅述了。
慕課網 DOM事件探祕。這一部分,主要看這個。
/** * 事件添加函數 * @param {object} element 須要綁定事件的對象 * @param {string} event 事件類型 * @param {function} listener 事件觸發執行的函數 */ function addEvent(element, event, listener) { if (element.addEventListener) { //標準 element.addEventListener(event, listener, false); } else if (element.attachEvent) { //低版本ie element.attachEvent("on" + event, listener); } else { //都不行的狀況 element["on" + event] = listener; } } /** * 事件移除函數 * @param {object} element 須要移除事件的對象 * @param {string} event 事件類型 * @param {function} listener 須要被移除事件函數 */ function removeEvent(element, event, listener) { // your implement if (element.removeEventListener) { //標準 element.removeEventListener(event, listener, false); } else if (element.detachEvent) { //低版本ie element.detachEvent("on" + event, listener); } else { //都不行的狀況 element["on" + event] = null; } }
click
事件、Enter
事件利用上面寫好的事件綁定函數就很簡單了。
click
事件,這個簡單,直接函數封裝一層就行。
Enter
事件,這裏主要考察的鍵盤的事件的觸發。
keydown
事件:在鍵盤按下時觸發.
keyup
事件:在按鍵釋放時觸發,也就是你按下鍵盤起來後的事件
keypress
事件:在敲擊按鍵時觸發,咱們能夠理解爲按下並擡起同一個按鍵
keyCode
屬性:在鍵盤事件觸發時,按下的鍵的值。值=13時,爲Enter
鍵。(需進行兼容處理)
// 實現對click事件的綁定 function addClickEvent(element, listener) { addEvent(element, "click", listener); } // 實現對於按Enter鍵時的事件綁定 function addEnterEvent(element, listener) { // your implement addEvent(element, "keydown", function (ev) { //兼容性處理。 var oEvent = ev || window.event; if (oEvent.keyCode === 13) { listener(); } }); }
接下來咱們把上面幾個函數和$作一下結合,把他們變成$對象的一些方法
addEvent(element, event, listener) -> $.on(element, event, listener)
;
removeEvent(element, event, listener) -> $.un(element, event, listener)
;
addClickEvent(element, listener) -> $.click(element, listener)
;
addEnterEvent(element, listener) -> $.enter(element, listener)
;
//在js中萬物皆對象(原諒我這麼淺顯的說),因此實現就特別簡單了 $.on = function (element, type, listener) { return addEvent(element, type, listener); }; $.un = function (element, type, listener) { return removeEvent(element, type, listener); }; $.click = function (element, listener) { return addClickEvent(element, listener); } $.enter = function (element, listener) { $.enter addEnterEvent(element, listener); };
接下來考慮這樣一個場景,咱們須要對一個列表裏全部的<li>
增長點擊事件的監聽
咱們經過本身寫的函數,取到id爲list這個ul裏面的全部li,而後經過遍歷給他們綁定事件。這樣咱們就不須要一個一個去綁定了。可是看看如下代碼:
<ul id="list"> <li id="item1">Simon</li> <li id="item2">Kenner</li> <li id="item3">Erik</li> </ul> <button id="btn">Change</button> function clickListener(event) { console.log(event); }
function renderList() { $("#list").innerHTML = '<li>new item</li>'; } function init() { each($("#list").getElementsByTagName('li'), function(item) { $.click(item, clickListener); }); $.click($("#btn"), renderList); } init();
咱們增長了一個按鈕,當點擊按鈕時,改變list裏面的項目,這個時候你再點擊一下li,綁定事件再也不生效了。那是否是咱們每次改變了DOM結構或者內容後,都須要從新綁定事件呢?固然不會這麼笨,接下來學習一下事件代理,而後實現下面新的方法:
// 先簡單一些 function delegateEvent(element, tag, eventName, listener) { // your implement } $.delegate = delegateEvent; // 使用示例 // 仍是上面那段HTML,實現對list這個ul裏面全部li的click事件進行響應 $.delegate($("#list"), "li", "click", clickHandle);
寫到這裏,恰好前幾天CSS魔法寫的《前端進階之路:點擊事件綁定》有提到「事件代理/委託」,不過是直接使用jQuery來實現的。因此地址有興趣的本身搜索吧-_-。
「事件代理」 的本質是利用了事件冒泡的特性。當一個元素上的事件被觸發的時候,好比說鼠標點擊了一個按鈕,一樣的事件將會在那個元素的全部祖先元素中被觸發。這一過程被稱爲事件冒泡;
這個事件從原始元素開始一直冒泡到DOM樹的最上層。任何一個事件的目標元素都是最開始的那個元素,在咱們的這個例子中也就是按鈕,而且它在咱們的元素對象中以屬性的形式出現。使用事件代理,咱們能夠把事件處理器添加到一個元素上,等待一個事件從它的子級元素裏冒泡上來,而且能夠得知這個事件是從哪一個元素開始的。
這裏就不細說事件冒泡與事件捕獲了(阻止默認行爲也會用到,有興趣去網上找找看),可是要理解事件代理就必須先知道它們。下面這張圖能夠先看看。(圖片來自網絡,侵刪)
理解了這個以後就沒那麼難了,只須要進行一點兼容性處理。
參考資料
/** * 事件代理 * @param {HTMLElement} element 須要進行事件代理的父元素。 * @param {string} tag 須要觸發事件的標籤名 * @param {string} eventName 觸發的事件類型 * @param {function} listener 事件執行的函數 * @returns {[[Type]]} [[Description]] */ function delegateEvent(element, tag, eventName, listener) { // your implement return addEvent(element, eventName, function (ev) { var oEvent = ev || event; //兼容處理 var target = oEvent.target || oEvent.srcElement; //兼容處理 if (target.tagName.toLocaleLowerCase() === tag) { listener.call(target, oEvent); //使用call方法修改執行函數中的this指向,如今this指向觸發了事件的HTML節點(可直接使用this.innerHTML返回該節點內容) } }) } $.delegate = function (element, tag, eventName, listener) { return delegateEvent(element, tag, eventName, listener); };
估計有同窗已經開始吐槽了,函數裏面一堆$看着暈啊,那麼接下來把咱們的事件函數作以下:(這裏應該是把前面的$.on
、$.click
、$.un
、$.delegate
都改寫一下。比較簡單,就拿一個出來做例子吧。)
//和上面的函數同樣,原來第一個參數是傳入獲取到的父HTMLElement對象,如今直接傳入選擇器名稱就行 $.delegate = function (selector, tag, event, listener) { //這裏的`$(selector)`,是用的本身封裝的選擇器函數,願意的話能夠換成標準支持的`document.querySelector()` return delegateEvent($(selector), tag, event, listener); }; // 使用示例: $.delegate('#list', "li", "click", liClicker);