所有代码可以在我的Github中找到,记录从task002开始。
DOM操作
要求
// 实现一个简单的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的对象
思路
这一题花费的时间比较久。
第一种思路是直接在
$()
中判断是否有空格,然后再分情况查询。单独查询的话比较好实现,但在组合查询中遇到了麻烦。直接写的话比较麻烦几乎是把几种情况都再写了一遍;使用递归的话因为返回的值不是数组,效果并不理想。于是放弃。第二种思路是不再判断是否包含空格,而是直接使用
split(" ")
分成数组,使用for
循环挨个查询。但在组合查询这一情况时想不到好的解决办法,还是放弃了。
最后搜索了一下别人的答案,综合了一下各种思路,个人感觉这种最好理解实现起来也比较方便:
首先实现domQuery函数
,这个函数直接返回查询到的结果,而不是数组中的第一个对象,然后再$()
中再查询到第一个对象。
在domQuery(selector,root)
中有两个参数,其中root就是考虑到组合查询的情况,在传入的selector有两个的情况下,第一个selector查询到的元素作为第二个子selector的root来进行查询。
实现
-
domQuery
函数
function domQuery(selector, root) {
var text;
var elements = [];
//if root is not defined, root = document
if (!root) {
root = document;
}
if (selector.charAt(0) === "#") {
text = selector.replace(/^\#/, "");
elements = document.getElementById(text);
} else if (selector.charAt(0) === ".") {
text = selector.replace(/^\./, "");
elements = root.getElementsByClassName(text);
} else if ((selector.charAt(0) === "[") && (selector.charAt(selector.length - 1) === "]")) {
//get all the elements
var eles = root.getElementsByTagName("*");
//delete "[" and "]"
selector = selector.replace(/^\[/, "");
selector = selector.replace(/\]$/, "");
var texts = selector.split("=");
var attr = texts[0];
var value = texts[1];
//有属性值的情况
if (texts[1]) {
for (var i = 0, length1 = eles.length; i < length1; i++) {
if (eles[i].hasAttribute(attr)) {
if (eles[i].getAttribute(attr) === value) {
elements = eles[i];
}
}
}
}
//没有属性值
else {
for (var i = 0, length1 = eles.length; i < length1; i++) {
if (eles[i].hasAttribute(attr)) {
elements = eles[i];
}
}
}
} else {
elements = root.getElementsByTagName(selector);
}
return elements;
}
2.$()
函数
function $(selector) {
//multiple queries
var result = [];
if (selector.indexOf(" ") !== -1) {
//split selector by space
var selectors = selector.split(" ");
parents = domQuery(selectors[0]);
for (var i = 1, length1 = selectors.length; i < length1; i++) {
if (parents.length) {
parents = domQuery(selectors[i], parents[0]);
} else {
parents = domQuery(selectors[i], parents);
}
}
result = parents;
}
//single query
else {
var result = domQuery(selector, document);
}
if (result.length) {
return result[0];
} else {
return result;
}
}
补充
代码逻辑
重点说明一下$()
中的组合查询的逻辑
if (selector.indexOf(" ") !== -1) {
//split selector by space
var selectors = selector.split(" ");
parents = domQuery(selectors[0]);
for (var i = 1, length1 = selectors.length; i < length1; i++) {
if (parents.length) {
parents = domQuery(selectors[i], parents[0]);
} else {
parents = domQuery(selectors[i], parents);
}
}
result = parents;
}
-
parents = domQuery(selectors[0])
获得最开始的root根节点,然后开始for循环,每一次循环domQuery得到的值都作为下一次循环的根节点,从而达到组合查询的目的; - 这里有个问题是
getElementById
和getElementsByClassName
、getElementsByTagName
等方法不同在于getElementById
返回单个元素,其他方法返回的则是数组,这就造成了处理上的困难; -
if (parents.length)
就用于判断当前得到的结果是否为数组,如果是,则只取数组的第一个元素; - 但这样的查询其实是不全面的,只能查询到返回数组的第一个中是否包含所查询元素,如果是第二个或者第三个包含,那么是查询不到的。
- 现在所能做到的,要么查询传进来的参数只有2个;若要查询多个,则像上面所说的那样无法查询除第一个以外的元素是否包含。
如何改进还在摸索中
补充发现
如果组合中第二个selector为id,即类似$(".a #b")
这种情况是不行的。会报root.getElementById is not a function
错误。
在Chrome控制台也实验了一下,getElementById
方法只能用于document
。
猜测了一下,大概是因为id是唯一的,并不需要多一个子集去筛选?
DOM事件
要求
-
// 给一个element绑定一个针对event事件的响应
//响应函数为listener
function addEvent(element, event, listener) {
// your implement
}
// 移除element对象对于event事件发生时执行listener的响应
function removeEvent(element, event, listener) {
// your implement
}
// 实现对click事件的绑定
function addClickEvent(element, listener) {
// your implement
}
// 实现对于按Enter键时的事件绑定
function addEnterEvent(element, listener) {
// your implement
}
-
// 事件代理
function delegateEvent(element, tag, eventName, listener) {
// your implement
}
$.delegate = delegateEvent;
// 使用示例
// 还是上面那段HTML,实现对list这个ul里面所有li的click事件进行响应
$.delegate($("#list"), "li", "click", clickHandle);
-
//函数封装
$.on(selector, event, listener) {
// your implement
}
$.click(selector, listener) {
// your implement
}
$.un(selector, event, listener) {
// your implement
}
$.delegate(selector, tag, event, listener) {
// your implement
}
// 使用示例:
$.click("[data-log]", logListener);
$.delegate('#list', "li", "click", liClicker);
思路
- 一开始其实没太懂这个要求,尝试写了一下也不尽人意,于是搜索了一下其他人的方法,才发现其实是很简单的一个问题自己想复杂了,直接使用js中提供的
addEventListener
方法就可以实现,接下来的几个方法同理 - 弄懂了事件代理的原理后就很简单,将事件绑定到元素上一级的element中,在点击时判断是否节点名
nodeName
等于tag,是的话就执行listener函数。 - 封装没什么好说的,不过element换成了selector,那么利用之前实现的
$()
把element变成$(selector)
就没问题了。
实现
-
// 给一个element绑定一个针对event事件的响应,响应函数为listener
function addEvent(element, event, listener) {
if (element.addEventListener) {
element.addEventListener(event, listener, false)
}
}
// 移除element对象对于event事件发生时执行listener的响应
function removeEvent(element, event, listener) {
if (element.removeEventListener) { //标准
element.removeEventListener(event, listener, false);
}
}
// 实现对click事件的绑定
function addClickEvent(element, listener) {
addEvent(element, 'click', listener);
}
// 实现对于按Enter键时的事件绑定
function addEnterEvent(element, listener) {
addEvent(element, "keydown", function(e) {
if (e.keyCode === 13) {
listener();
}
});
}
-
function delegateEvent(element, tag, eventName, listener) {
$.eventName(element, function(e) {
var e = e || window.event;
var target = e.target || e.srcElement;
if (target.nodeName.toLowerCase() === tag) {
//?????
listener.call(target, e);
}
});
}
-
//函数封装
$.on = function(selector, event, listener) {
return addEvent($(selector), event, listener);
};
$.un = function(selector, event, listener) {
return removeEvent($(selector), event, listener);
};
$.click = function(selector, listener) {
return addClickEvent($(selector), listener);
};
$.enter = function(selector, listener) {
return addEnterEvent($(selector), listener);
};
$.delegate = function(selector, tag, event, listener) {
return delegateEvent($(selector), tag, eventName, listener);
};
补充
暂无
BOM
要求
// 判断是否为IE浏览器,返回-1或者版本号
function isIE() {
// your implement
}
// 设置cookie
function setCookie(cookieName, cookieValue, expiredays) {
// your implement
}
// 获取cookie值
function getCookie(cookieName) {
// your implement
}
思路
本来想用navigator判断浏览器是否为IE的,但是看到说navigator的信息有误导性,然后搜了一下发现还有用
ActiveXObject
检测的。MSDN里面这么描述的:
此对象为 Microsoft 扩展,仅在 Internet Explorer 中受支持,在 Windows 8.x 应用商店应用中不受支持。
那么只要检测是否存在ActiveXObject
对象就可以知道是否为IE浏览器。
接着是检查版本,在自己电脑上试了一下,IE版本是11,userAgent
信息:
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko"
而IE10(不包括)之前的IE的userAgent
信息格式:
Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)
分两种情况,一个正则式就可以搞定。设置cookie和获取cookie在W3C里直接就给了方法了,也没啥难理解的点,就了解一下cookie了。
实现
-
function isIE() {
if (!!window.ActiveXObject || "ActiveXObject" in window) {
var version = getIEVersion();
return version;
} else {
return -1;
}
}
function getIEVersion () {
var reg = /(Trident.*rv\:|MSIE\s)((\d+)\.0)/;
var uaString = navigator.userAgent;
var versionMatch = uaString.match(reg);
if (versionMatch) {
return versionMatch[3];
}
}
-
function setCookie(c_name,value,expiredays)
{
var exdate=new Date()
exdate.setDate(exdate.getDate()+expiredays)
document.cookie=c_name+ "=" +escape(value)+
((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
}
function getCookie(c_name)
{
if (document.cookie.length>0)
{
c_start=document.cookie.indexOf(c_name + "=")
if (c_start!=-1)
{
c_start=c_start + c_name.length+1
c_end=document.cookie.indexOf(";",c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return "";
}
补充
暂无
AJAX
要求
function ajax(url, options) {
// your implement
}
// 使用示例:
ajax(
'http://localhost:8080/server/ajaxtest',
{
data: {
name: 'simon',
password: '123456'
},
onsuccess: function (responseText, xhr) {
console.log(responseText);
}
}
);
options是一个对象,里面可以包括的参数为:
- type: post或者get,可以有一个默认值
- data: 发送的数据,为一个键值对象或者为一个用&连接的赋值字符串
- onsuccess: 成功时的调用函数
- onfail: 失败时的调用函数
思路
这个也没啥好说的,了解一下xhr对象就能写了。
实现
function ajax(url, options) {
//新建一个XHR对象
var xmlhttp;
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
} else {
// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//若没有设置type,则默认为get
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
options.onsuccess();
} else {
options.onfail();
}
type = options.type || get;
xmlhttp.open(type, url, true);
if (type === "get") {
xmlhttp.send();
} else {
xmlhttp.send(options.data);
}
}
补充
这里补充一下readyState和status的几种状态
属性 | 描述 |
---|---|
readyState | 0: 请求未初始化 1: 服务器连接已建立 2: 请求已接收 3: 请求处理中 4: 请求已完成,且响应已就绪 |
status | 200: "OK" 404: 未找到页面 |