转载自个人我的博客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);