DOM
添加class、移除class、是否同级元素、获取元素位置
先来一些简单的,在你的util.js
中完成以下任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function addClass(element, newClassName) { }
function removeClass(element, oldClassName) { }
function isSiblingNode(element, siblingNode) { }
function getPosition(element) { }
|
思路:
- 其实这里可以先定义一个
hasClass
函数。用来判断该节点是否含有某个className。
addClass
添加样式。调用hasClass
函数,判断element
是否含有待添加的新className,若没有则添加,否则什么都不做。
removeClass
删除样式。调用hasClass
函数,判断element
是否含有该指定样式,若含有的话删除该className。没有的话什么都不做。
判断siblingNode和element是否为同一个父元素下的同一级的元素。这里直接判断parentNode
就可以了吧
获取element相对于浏览器窗口的位置,返回一个对象{x, y}。
这个题应该是这几个中比较复杂的一个了。因为不能直接使用offsetLeft/Top
。**offsetLeft/Top
所获取的是其相对父元素的相对位置。**当多层定位嵌套时想要获取到当前元素相对网页的位置就会不对。
并且由于在表格
和iframe
中,offsetParent对象未必等于父容器,所以也不能直接利用该元素的parent
来获取位置,因为其对于表格
和iframe
中的元素不适用。
通过查询知道有一个Element.getBoundingClientRect()
方法。它返回一个对象,其中包含了left、right、top、bottom四个属性,分别对应了该元素的左上角和右下角相对于浏览器窗口(viewport)左上角的距离。
但是用该方法获取到的是元素的相对位置,在出现滚动时,距离会发生改变,要获得绝对位置时,还需要加上滚动的距离。因为Firefox或Chrome的不兼容问题需要进行兼容性处理,参考document.body.scrollTop or document.documentElement.scrollTop
最终根据两个值,得到绝对位置。
1 2 3
| var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
|
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function hasClass(element, sClass) { return element.className.match(new RegExp("(\\s|^)" + sClass + "(\\s|$)")); }
function addClass(element, newClassName) { if (!hasClass(element, newClassName)) { element.className += " " + newClassName; } }
function removeClass(element, oldClassName) { if (hasClass(element, oldClassName)) { var reg = new RegExp("(\\s|^)" + oldClassName + "(\\s|$)"); element.className = element.className.replace(reg, ""); } }
function isSiblingNode(element, siblingNode) { return element.parentNode === siblingNode.parentNode }
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
中完成以下任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function $(selector) {
}
$("#adom");
$("a");
$(".classa");
$("[data-log]");
$("[data-time=2015]");
$("#adom .classa");
|
实现思路:
嗯,这个题思考了很久,网上找了很多资料但还是不怎么会,还达不到想要的效果,有点钻牛角尖了。尽量来写一下吧。(我果然是个弱鸡)。感谢秒味课堂的免费课程。
- 题目要求获取到所有的节点中的第一个,所以不需要用数组来储存获取到的节点。
- 额。。想了半天,还是使用函数包装来实现后代选择器比较好,所以VQuery函数返回是获取到的完整节点对象数组,
$
函数用来达到题目要求。
- 所以在VQuery函数中就不需要考虑空格了,直接使用switch分支,来判定不同的情况。
#
、.
、[
、 [=]
。
- 在
$
函数中,判断字符串中是否含有空格,有空格的话需要分割成数组,数组的前一项是为父选择符,后一项为子选择符。分不同的情况来调用VQuery函数,并返回对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
function VQuery(selector, root) { var elements = []; var allChildren = null; root = root || document; switch (selector.charAt(0)) { case "#": elements.push(root.getElementById(selector.substring(1))); break; case ".": 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: elements = root.getElementsByTagName(selector); } return elements }
function $(selector) {
if (selector == document) { return document; } selector = selector.trim(); if (selector.indexOf(" ") !== -1) { var selectorArr = selector.split(/\s+/); return VQuery(selectorArr[1], VQuery(selectorArr[0])[0])[0]; } else { return VQuery(selector,document)[0]; } }
|
事件
事件绑定、事件移除
我们来继续用封装自己的小jQuery库来实现我们对于JavaScript事件的学习,还是在你的util.js
,实现以下函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function addEvent(element, event, listener) { }
function clicklistener(event) { ... } addEvent($("#doma"), "click", a);
function removeEvent(element, event, listener) { }
|
这里慕课网的视频讲的特别清楚,就不赘述了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
function addEvent(element, event, listener) { if (element.addEventListener) { element.addEventListener(event, listener, false); } else if (element.attachEvent) { element.attachEvent("on" + event, listener); } else { element["on" + event] = listener; } }
function removeEvent(element, event, listener) { if (element.removeEventListener) { element.removeEventListener(event, listener, false); } else if (element.detachEvent) { element.detachEvent("on" + event, listener); } else { element["on" + event] = null; } }
|
click
事件、Enter
事件
利用上面写好的事件绑定函数就很简单了。
click
事件,这个简单,直接函数封装一层就行。
Enter
事件,这里主要考察的键盘的事件的触发。
keydown
事件:在键盘按下时触发.
keyup
事件:在按键释放时触发,也就是你按下键盘起来后的事件
keypress
事件:在敲击按键时触发,我们可以理解为按下并抬起同一个按键
keyCode
属性:在键盘事件触发时,按下的键的值。值=13时,为Enter
键。(需进行兼容处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function addClickEvent(element, listener) { addEvent(element, "click", listener); }
function addEnterEvent(element, listener) { 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)
;
1 2 3 4 5 6 7 8 9 10 11 12 13
| $.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,然后通过遍历给他们绑定事件。这样我们就不需要一个一个去绑定了。但是看看以下代码:
1 2 3 4 5 6 7 8 9
| <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); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| 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结构或者内容后,都需要重新绑定事件呢?当然不会这么笨,接下来学习一下事件代理,然后实现下面新的方法:
1 2 3 4 5 6 7 8 9
| function delegateEvent(element, tag, eventName, listener) { }
$.delegate = delegateEvent;
$.delegate($("#list"), "li", "click", clickHandle);
|
实现思路:
写到这里,刚好前几天CSS魔法写的《前端进阶之路:点击事件绑定》有提到“事件代理/委托”,不过是直接使用jQuery来实现的。所以地址有兴趣的自己搜索吧-_-。
“事件代理” 的本质是利用了事件冒泡的特性。当一个元素上的事件被触发的时候,比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;
这个事件从原始元素开始一直冒泡到DOM树的最上层。任何一个事件的目标元素都是最开始的那个元素,在我们的这个例子中也就是按钮,并且它在我们的元素对象中以属性的形式出现。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。
这里就不细说事件冒泡与事件捕获了(阻止默认行为也会用到,有兴趣去网上找找看),但是要理解事件代理就必须先知道它们。下面这张图可以先看看。(图片来自网络,侵删)
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
function delegateEvent(element, tag, eventName, listener) { 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); } }) }
|
封装改变
估计有同学已经开始吐槽了,函数里面一堆$看着晕啊,那么接下来把我们的事件函数做如下:(这里应该是把前面的$.on
、$.click
、$.un
、$.delegate
都改写一下。比较简单,就拿一个出来作例子吧。)
1 2 3 4 5 6 7
| $.delegate = function (selector, tag, event, listener) {
return delegateEvent($(selector), tag, event, listener); };
$.delegate('#list', "li", "click", liClicker);
|