百度前端技术学院(IFE)2015春task练习记录02

所有代码可以在我的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的对象

思路

这一题花费的时间比较久。

  1. 第一种思路是直接在$()中判断是否有空格,然后再分情况查询。单独查询的话比较好实现,但在组合查询中遇到了麻烦。直接写的话比较麻烦几乎是把几种情况都再写了一遍;使用递归的话因为返回的值不是数组,效果并不理想。于是放弃。

  2. 第二种思路是不再判断是否包含空格,而是直接使用split(" ")分成数组,使用for循环挨个查询。但在组合查询这一情况时想不到好的解决办法,还是放弃了。

最后搜索了一下别人的答案,综合了一下各种思路,个人感觉这种最好理解实现起来也比较方便:
首先实现domQuery函数,这个函数直接返回查询到的结果,而不是数组中的第一个对象,然后再$()中再查询到第一个对象。
domQuery(selector,root)中有两个参数,其中root就是考虑到组合查询的情况,在传入的selector有两个的情况下,第一个selector查询到的元素作为第二个子selector的root来进行查询。

实现

  1. 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;
    }
  1. parents = domQuery(selectors[0])获得最开始的root根节点,然后开始for循环,每一次循环domQuery得到的值都作为下一次循环的根节点,从而达到组合查询的目的;
  2. 这里有个问题是getElementByIdgetElementsByClassNamegetElementsByTagName等方法不同在于getElementById返回单个元素,其他方法返回的则是数组,这就造成了处理上的困难;
  3. if (parents.length)就用于判断当前得到的结果是否为数组,如果是,则只取数组的第一个元素;
  4. 但这样的查询其实是不全面的,只能查询到返回数组的第一个中是否包含所查询元素,如果是第二个或者第三个包含,那么是查询不到的。
  5. 现在所能做到的,要么查询传进来的参数只有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);

思路

  1. 一开始其实没太懂这个要求,尝试写了一下也不尽人意,于是搜索了一下其他人的方法,才发现其实是很简单的一个问题自己想复杂了,直接使用js中提供的addEventListener方法就可以实现,接下来的几个方法同理
  2. 弄懂了事件代理的原理后就很简单,将事件绑定到元素上一级的element中,在点击时判断是否节点名nodeName等于tag,是的话就执行listener函数。
  3. 封装没什么好说的,不过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
}

思路

  1. 本来想用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)
    分两种情况,一个正则式就可以搞定。

  2. 设置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: 未找到页面
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 前言 个人觉得IFE的练习非常不错,学习前端不久,看完几本Head First,JS编程艺术等等后,我就拿来当练习...
    franose阅读 1,573评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 文/丹顶鹤的日记本 学习笔记注:本文所说的表单(Form),是网页或者移动应用表单。 一) 表单的作用 表单的主要...
    丹顶鹤的日记本阅读 1,637评论 2 20