# IFE前端(2015春)-task2

第一章 JavaScript数据类型及语言基础

期望达成

  • 掌握JavaScript的各种数据类型概念、判断方法
  • 掌握JavaScript函数、对象的概念
  • 掌握字符串、数字、数组、日期等对象的方法
  • 了解JavaScript的作用域
  • 初步掌握正则表达式的写法

1.1 实践判断各种数据类型的方法

任务描述

创建一个JavaScript文件,比如util.js;并在util.js中实现以下方法:

// 判断arr是否为一个数组,返回一个bool值
function isArray(arr) {
    // your implement
}

// 判断fn是否为一个函数,返回一个bool值
function isFunction(fn) {
    // your implement
}

解决方案

数组本来就有原生的方法Array.isArray(xxx),函数则可以使用typeof判断。

// 判断arr是否为一个数组,返回一个bool值
function isArray(arr) {
    // your implement
    return Array.isArray(arr);
}

// 判断fn是否为一个函数,返回一个bool值
function isFunction(fn) {
    // your implement
    return typeof fn=='function';
}

1.2 数据类型的特性

任务描述

了解值类型和引用类型的区别,了解各种对象的读取、遍历方式,在util.js中实现以下方法:

// 使用递归来实现一个深度克隆,可以复制一个目标对象,返回一个完整拷贝
// 被复制的对象类型会被限制为数字、字符串、布尔、日期、数组、Object对象。不会包含函数、正则对象等
function cloneObject(src) {
    // your implement
}

解决方案

基本数据类型包括undefinedNull(typeof操作返回Object对象),BooleanNumberStringObject

应当明确,引用数据类型(Object)是不可通过赋值的方法进行复制的。引用数据类型包括:对象,数组、日期和正则。实际上就是解决引用对象的复制的问题。既然不包括函数和正则,则可以使用typeof进行分类讨论。对与一般的Object对象,做一个遍历就可以了。

Date对象如何判定呢?实际上还有更为底层的方法:Object.prototype.toString.call(xxx)

关于Object.prototype.toString.call(xxx),可参考文章:http://www.cnblogs.com/ziyunfei/archive/2012/11/05/2754156.html

// 使用递归来实现一个深度克隆,可以复制一个目标对象,返回一个完整拷贝
// 被复制的对象类型会被限制为数字、字符串、布尔、日期、数组、Object对象。不会包含函数、正则对象等
function cloneObject(src) {
    // your implement
    var clone=null;
    if(typeof src!=='object'){
        clone=src;
    }else{
        if(Array.isArray(src)){
            clone=src.slice();
        }else if(Object.prototype.toString.call(src)=='[object Date]'){
            clone=new Date(src.valueOf());
        }else{
            clone={};
            for(var attr in src){
                clone[attr]=cloneObject(src[attr]);
            }
        }
    }
    return clone;
}

// 测试用例:
var srcObj = {
    a: 1,
    b: {
        b1: ["hello", "hi"],
        b2: "JavaScript"
    }
};
var abObj = srcObj;
var tarObj = cloneObject(srcObj);

srcObj.a = 2;
srcObj.b.b1[0] = "Hello";

console.log(abObj.a);//2
console.log(abObj.b.b1[0]);//"Hello"

console.log(tarObj.a);      // 1
console.log(tarObj.b.b1[0]);    // "hello"

测试通过。

1.3 数组、字符串、数字相关方法

任务描述

util.js中实现以下函数

// 对数组进行去重操作,只考虑数组中元素为数字或字符串,返回一个去重后的数组
function uniqArray(arr) {
    // your implement
}


// 实现一个简单的trim函数,用于去除一个字符串,头部和尾部的空白字符
// 假定空白字符只有半角空格、Tab
function simpleTrim(str) {
    // your implement
}

// 接下来,我们真正实现一个trim
// 对字符串头尾进行空格字符的去除、包括全角半角空格、Tab等,返回一个字符串
// 尝试使用一行简洁的正则表达式完成该题目
function trim(str) {
    // your implement
}



// 实现一个遍历数组的方法,针对数组中每一个元素执行fn函数,并将数组索引和元素作为参数传递
function each(arr, fn) {
    // your implement
}


// 获取一个对象里面第一层元素的数量,返回一个整数
function getObjectLength(obj) {}

解决方案

数组去重

数组去重无非是设置一个新数组,循环套循环判断,可以设置一个flag量。

function uniqArray(arr) {
    var newArr=[];
    var check;
    for(var i=0;i
trim函数

实际上ES5早已提供了trim方法。

function simpleTrim(str) {
    // your implement
    return str.trim();
}

如果需要自己写,可以用正则匹配边界,或是做一个计数器,然后用循环查找字符串边界。

function trim(str) {
    // 行首的所有空格和行尾的所有空格
    var re=/^\s+|\s+$/g;
    return str.replace(re,'');
}

// 使用示例
var str = '   hi!  ';
str = trim(str);
console.log(str); // 'hi!'
数组遍历

这跟forEach方法是一样的。

// 实现一个遍历数组的方法,针对数组中每一个元素执行fn函数,并将数组索引和元素作为参数传递
function each(arr, fn) {
    // your implement
    for(var i=0;i
找到属性的数量

对象一般用for-in循环,循环次数就是这个属性的长度。

// 获取一个对象里面第一层元素的数量,返回一个整数
function getObjectLength(obj) {
    var count=0;
    for(var attr in obj){
        count++;
    }
    return count;
}

// 使用示例
var obj = {
    a: 1,
    b: 2,
    c: {
        c1: 3,
        c2: 4
    }
};
console.log(getObjectLength(obj)); // 3

1.4 正则表达式

任务描述

util.js完成以下代码

// 判断是否为邮箱地址
function isEmail(emailStr) {
    // your implement
}

// 判断是否为手机号
function isMobilePhone(phone) {
    // your implement
}

解决方案

邮箱和手机号是个很常用的判断。然而死记没有用,唯有多写。

// 判断是否为邮箱地址
function isEmail(emailStr) {
    // 开头必须以字母和数字跟着1一个“@”,后边是小写字母或数字,最后就是域名(2-4位)。
    var re=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/;
    return re.test(emailStr);
}
//测试
//console.log(isEmail('[email protected]'));

// 判断是否为手机号
function isMobilePhone(phone) {
    //手机号必须以1开头,后面跟着9位数字
    var re=/^1\d{10,10}$/;
    return re.test(phone);
}

//测试
//console.log(isMobilePhone('15515515515'));

第二章 DOM

  • 熟练掌握DOM的相关操作。

注:所有的dom测试需要在window.onload下完成。

2.1 DOM查询方法

任务描述

先来一些简单的,在你的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
}

解决方案

addClass方法的实现

classList属性返回元素的类名,作为 DOMTokenList 对象。该属性用于在元素中添加,移除及切换 CSS 类。classList 属性是只读的,但你可以使用 add() 和 remove() 方法修改它。

// 为element增加一个样式名为newClassName的新样式
function addClass(element, newClassName) {
    element.classList.add(newClassName);
}

// 移除element中的样式oldClassName
function removeClass(element, oldClassName) {
    element.classList.remove(oldClassName);
}
同辈方法
// 判断siblingNode和element是否为同一个父元素下的同一级的元素,返回bool值
function isSiblingNode(element, siblingNode) {
    // your implement
    return element.parentNode==siblingNode.parentNode;
}

2.2 基本选择器(mini $)

任务描述

接下来挑战一个mini $,它和之前的$是不兼容的,它应该是document.querySelector的功能子集,在不直接使用document.querySelector的情况下,在你的util.js中完成以下任务:

// 实现一个简单的Query
function $(selector) {

}

// 可以通过id获取DOM对象,通过#标示,例如
$("#adom"); // 返回id为adom的DOM对象

// 可以通过tagName获取DOM对象,例如
$("a"); // 返回第一个对象

// 可以通过样式名称获取DOM对象,例如
$(".classa"); // 返回第一个样式定义包含classa的对象

// 可以通过attribute匹配获取DOM对象,例如
$("[data-log]"); // 返回第一个包含属性data-log的对象

$("[data-time=2015]"); // 返回第一个包含属性data-time且值为2015的对象

// 可以通过简单的组合提高查询便利性,例如
$("#adom .classa"); // 返回id为adom的DOM所包含的所有子节点中,第一个样式定义包含classa的对象

解决方案

迷你jQuery的实现就是判断传进来的字符串特征。先思考,选择器需要什么功能?

  • 当传入字符串时,查找选择器。

    • 选择器首先需要判断是否存在selector1 selector2的写法。后代选择器应该用数组方法split进行解析,并进行递归。
    • 如果不是后代选择器,那就看首字母的特征
  • 设置一个父级参数,当没有时这个父级就是document

  • 设置class选择器时,需要考虑class兼容性

    请出getByClass函数吧!

    function getByClass(oParent, sClass){
        if(oParent.getElementsByClassName){
            return oParent.getElementsByClassName(sClass);
        }else{
            var res = [];
            var re = new RegExp(' ' + sClass + ' ', 'i');
            var aEle = oParent.getElementsByTagName('*');
            for(var i = 0; i < aEle.length; i++){
                if(re.test(' ' + aEle[i].className + ' ')){
                    res.push(aEle[i]);
                }
           }
        return res;
        }
    }
  • 应该用面向对象的方法进行封装。把选择器放到$.obj中。

方案如下

function $d(selector,oParent) {
    //不写第二个参数时,oParent就是document
    oParent=oParent?oParent:document;
    //用来存放选择器对象。
    this.obj=null;
  
    // 如果没有空格
    if(!selector.match(/\s/)){
        switch(selector[0]){
            case '#'://id选择器
                this.obj=oParent.getElementById(selector.substring(1));
            break;

            case '.'://类选择器
                this.obj=getByClass(oParent,selector.substring(1))[0];
            break;

            case '['://属性选择器
                // 提取方括号内的属性名
                var str=selector.replace(/\[|\]/g,'');
               // 找出父级的对象集合,方便遍历
                var all=oParent.getElementsByTagName('*');
               // 存放找到的html元素
                var arr=[];
                for(var i=0;i

既然用了面向对象的方法来写,那么这个函数就需要进化一下,把$变成$d构造函数的实例:

function $(selector,oParent){
    return new $d(selector,oParent);
}

调用时可以用$('#div1').obj进行查询。

另外,原生方法有querySelector方法,但是兼容性不强。


第三章 事件

  • 熟悉DOM事件相关知识

3.1 事件注册

任务描述

我们来继续用封装自己的小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
}

接下来我们实现一些方便的事件方法

// 实现对click事件的绑定
function addClickEvent(element, listener) {
    // your implement
}

// 实现对于按Enter键时的事件绑定
function addEnterEvent(element, listener) {
    // your implement
}

接下来我们把上面几个函数和$对象的一些方法

  • 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);

解决方案

注册事件有两种方法:addEventListenerattachEvent。两者用法类似,但是存在若干不同。

  • addEventListener

    适用于现代浏览器,此方法接受3个参数,事件名称,回调函数,是否事件捕获(默认为false,通常不写)。作为对应,还有一个removeEventListener。比如说,我要创建一个对某个按钮创建一个点击事件处理函数:

    function xxx(){
      //balabala
    }
    
    window.onload=function(){
      var oBtn=document.getElementById('btn');
      //注册
      oBtn.addEventListener('click',xxx);
      //移除
      oBtn.removeEventListener('click',xxx);
    
    };
  • attachEvent

    适用于IE远古浏览器家族,相比addEventListener,少了第三个参数,同时事件名需要加一个on在前面

    window.onload=function(){
      var oBtn=document.getElementById('btn');
      //注册
      oBtn.attachEvent('onclick',xxx);
      //移除
      oBtn.detachEvent('onclick',xxx);
    
    };
  • 为什么要注册事件?

    还是上面的例子,oBtn.onclick=function(){...}会把之前添加的的内容给覆盖掉。而使用注册后,允许你写多个不同的事件函数,按照注册事件发生。

首先看下如何获取浏览器信息,用的是navigator.userAgent

//判断是否为IE浏览器,返回-1或者版本号
function isIE() {
    var info=navigator.userAgent;
    var re=/msie (\d+\.\d+)/i;
    var reEdge=/rv:(\d+\.\d+)/i;

    if(info.match(re)){
        return info.match(re)[1];
    }else if(info.match(reEdge)&&!info.match(/firefox/i)){
        // 兼容Edge浏览器
        return info.match(reEdge)[1];
    }else{
        return -1;
    }
}

对于IE8.0以下的版本,使用attachEvent方法。

写事件总会遇到万恶的兼容性问题。难点在于兼容性和获取this

绑定this到元素身上的策略是call方法

detachEvent方法无法获取原本执行的效果函数,既然这样,就把这个效果函数设置为一个构造函数,存入实际要执行的对象,待需要解绑时,在调出来,最后删除这个属性

// 给一个element绑定一个针对_event事件的响应,响应函数为listener
function addEvent(element,_event,listener) {

    if(isIE()==-1||isIE()>=9){
        element.addEventListener(_event,listener);
    }else if(isIE()!==-1&&isIE()<9){
        // 如果函数的绑定信息没有,就创建一个
        if(!listener.prototype.bindEvents){
            listener.prototype.bindEvents=[];
        }

        var bindInfo={
            target:element,
            event:_event,
            fn:function(){
                return listener.call(element);
            }
        };

        listener.prototype.bindEvents.push(bindInfo);
        element.attachEvent('on'+_event,bindInfo.fn);
    }
}

// 移除element对象对于event事件发生时执行listener的响应
function removeEvent(element, _event, listener) {
    if(isIE()==-1||isIE()>=9){
        element.removeEventListener(_event,listener);
    }else if(isIE()!==-1&&isIE()<9){
        var events=listener.prototype.bindEvents;
        for(var i=0;i

有了这两个函数就可以做出各种事件了。

$d.prototype.on=function(_event,listener){
    addEvent(this.obj,_event,listener);
};

$d.prototype.un=function(_event,listener){
    removeEvent(this.obj,_event,listener);
};

$d.prototype.click=function(listener){
    addEvent(this.obj,'click',listener);
};

$d.prototype.enter=function(listener){
    addEvent(this.obj,'keydown',function(ev){
        var e=ev||window.event;
        if(e.keyCode==13){
            return listener();
        }
    });
};

经测试兼容IE8。

3.2 事件监听代理

任务描述

接下来考虑这样一个场景,我们需要对一个列表里所有的``增加点击事件的监听

最笨的方法

  • Simon
  • Kenner
  • Erik
function clickListener(event) {
    console.log(event);
}

$.click($("#item1"), clickListener);
$.click($("#item2"), clickListener);
$.click($("#item3"), clickListener);

上面这段代码要针对每一个item去绑定事件,这样显然是一件很麻烦的事情。

稍微好一些的

  • Simon
  • Kenner
  • Erik

我们试图改造一下

function clickListener(event) {
    console.log(event);
}

each($("#list").getElementsByTagName('li'), function(li) {
    addClickEvent(li, clickListener);
});

我们通过自己写的函数,取到id为list这个ul里面的所有li,然后通过遍历给他们绑定事件。这样我们就不需要一个一个去绑定了。但是看看以下代码:

  • Simon
  • Kenner
  • Erik
function clickListener(event) {
    console.log(event);
}

function renderList() {
    $("#list").innerHTML = '
  • new item
  • '; } 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);

    估计有同学已经开始吐槽了,函数里面一堆$看着晕啊,那么接下来把我们的事件函数做如下封装改变:

    $.delegate(selector, tag, event, listener) {
        // your implement
    }
    
    // 使用示例:
    $.click("[data-log]", logListener);
    $.delegate('#list', "li", "click", liClicker);

    解决方案

    事件监听是利用冒泡的机制,当你点击ul中的某个li,默认触发ul的点击。然后一层一层向上冒泡,冒泡到具体的li时,添加监听函数。

    $d.prototype.delegate=function(tags,_event,listener){
        addEvent(this.obj,_event,function(ev){
            var e=ev||window.event;
            //console.log(e.target.nodeName);
            if(e.target.nodeName.toUpperCase()==tags.toUpperCase()){
                return listener.call(e.target);
            }
        });
    };
    //测试
    window.onload=function(){
      $('#list').delegate('li','click',function(){
        console.log(this);
      })
    }

    遗憾的是该方法的nodeName不支持IE8


    第四章 BOM

    了解BOM的基础知识

    4.1 元素定位

    任务描述

    获取element相对于浏览器窗口的位置

    // 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
    function getPosition(element) {
        // your implement
    }
    // your implemen

    解决思路

    先看如何获取元素的绝对位置——不断累加元素和本身的offset值。直到不可再加。

        function getElementLeft(element){
        var actualLeft = element.offsetLeft;
        var current = element.offsetParent;
        while (current !== null){
          actualLeft += current.offsetLeft;
          current = current.offsetParent;
        }
        return actualLeft;
      }
    
      function getElementTop(element){
        var actualTop = element.offsetTop;
        var current = element.offsetParent;
        while (current !== null){
          actualTop += current.offsetTop;
          current = current.offsetParent;
        }
        return actualTop;
      }

    有了绝对方法,只要将绝对坐标减去页面的滚动条滚动的距离就可以了。

    // 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
    function getPosition(element) {
        // your implement
        function getElementLeft(ele){
        var actualLeft = ele.offsetLeft;
        var current = ele.offsetParent;
        while (current !== null){
          actualLeft += current.offsetLeft;
          current = current.offsetParent;
        }
        return actualLeft;
      }
    
      function getElementTop(ele){
        var actualTop = ele.offsetTop;
        var current = ele.offsetParent;
        while (current !== null){
          actualTop += current.offsetTop;
          current = current.offsetParent;
        }
        return actualTop;
      }
    
        var position={};
    
        var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
        var scrollLeft=document.documentElement.scrollLeft||document.body.scrollLeft;
        var left=getElementLeft(element)-scrollLeft;
        var top=getElementTop(element)-scrollTop;
        position.x=left;
        position.y=top;
    
        return position;
    }

    如果你想快速获得相对位置——

    // 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
    function getPosition(element) {
        // your implement
        var X= element.getBoundingClientRect().left;
        var Y =element.getBoundingClientRect().top;
        return {
          x:X,
          y:Y
        }
    }

    这两个方法都兼容IE8。

    任务描述

    实现以下函数

    // 设置cookie
    function setCookie(cookieName, cookieValue, expiredays) {
        // your implement
    }
    
    // 获取cookie值
    function getCookie(cookieName) {
        // your implement
    }

    解决方案

    cookie的测试需要在FF或服务器环境下进行。

    js中的cookie是document下的一个属性,cookie没有指定,其寿命就是浏览器进程。

    document.cookie="user=dangjingtao";
    document.cookie="pass=123";
    alert(document.cookie);

    # IFE前端(2015春)-task2_第1张图片

    cookie本质是一个字符串。通过document.cookie进行读取。但是是有意义的字符串,各个键值对通过分号隔开。包括基本属性(自定义)和过期时间(expires)。

    如果你要删除cookie,直接把过期时间设置为前一天就可以了。

    //以下是封装好的三个cookie函数
    function setCookie(name,value,iDay){
        var oDate=new Date();
        oDate.setDate(oDate.getDate()+iDay);
        document.cookie=name+'='+value+';expires='+oDate;
    }
    
    
    function getCookie(name){
        // 对cookie字符串转化为一个数组,
        // 每个数组元素对应是一个单独的cookie
        var arr=document.cookie.split(';');
    
        for(var i=0;i

    第五章 Ajax

    • 掌握Ajax的实现方式

    任务描述

    学习Ajax,并尝试自己封装一个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: 失败时的调用函数

    解决方案

    XMLHttpRequest对象是ajax技术的核心,在IE6中是ActiveXObject对象。因此创建XMLHttpRequest对象时需要兼容性处理。

        if(window.XMLHttpRequest){
            oAjax=new XMLHttpRequest();
        }else{
            oAjax=new ActiveXObject("Microsoft.XMLHTTP");
        }

    ajax的get请求基本过程如下

    创建对象=>oAjax.open()=>oAjax.send()=>根据返回的状态响应

    对于post请求,通常还要带上请求的数据。

    oAjax.setRequestHeader('Content-Type','application/json');
    oAjax.send(content);

    oAjax.readyState一共5个状态码:

    1. 0=>open方法尚未调用
    2. 1=>open已经调用
    3. 2=>接收到头信息
    4. 3=>接收到响应主体
    5. 4=>响应完成
    $d.prototype.ajax=function(url,json){
    
        var content=json.content?json.content:null;
        var type=json.type;
        var fnSucc=json.success;
        var fnFaild=json.faild;
    
        var oAjax=null;
        if(window.XMLHttpRequest){
            oAjax=new XMLHttpRequest();
        }else{
            oAjax=new ActiveXObject("Microsoft.XMLHTTP");
        }
    
        if(type.toUpperCase()=='GET'){
            oAjax.open('GET',url,true);
            oAjax.send();
        }else if(type.toUpperCase()=='POST'){
            oAjax.setRequestHeader('Content-Type','application/json');
            oAjax.open('POST',url,true);
            oAjax.send(content);
        }
    
    
        oAjax.onreadystatechange=function(){
            if(oAjax.readyState==4){
    
                if(oAjax.status==200){
                    fnSucc(oAjax.responseText);
                }else{
    
                    if(fnFaild){
                        fnFaild(oAjax.status);
                    }
    
                }
           }
       };
    };
    
    /* 使用示例
    ajax('json.json',{
      type:"POST",
      content:{
        name:"dangjingtao",
        password:"123"
      },
      success:function(res){
        alert(res);
      },
      faild:{
        alert('出错!');
      }
    });
    */

    第六章 js库的完善

    想要让目前这个$d库写起来像真正的jQuery一样顺手,需要完善的还有很多很多很多。

    具体查看解释,可以参照仿照jQuery封装个人的js库。本章该系列文章的浓缩版。

    $d参数放什么

    最开始是放字符串选择器。但是随着功能的增加,$d的方法越来越多。原来只传字符串进去显然不能满足了。

    一个流畅使用的$d选择器,应当满足:

    • 允许css形式的选择器字符串
    • 允许用$(function(){。。。})替代window.onload
    • 允许直接传html对象。或者更广。

    所以$d的代码结构应该是:

    function $d(selector oParent){
        switch(typeof selector){
          case:'function':
            //执行addEvent方法,主体对象是window,事件是load
          break;
          
          case:'object':
            //直接把该对象放到this.obj里面
          break;
          
          case:'string':
            //执行选择器操作
          break;
        }
    }

    群组选择器改进

    按照当初要求设计这个mini 版$库时有些坑爹啊。返回的是第一个元素,而不是一个数组。

    我们已经用$d.obj做了很多事情,再改就不现实了。群组选择器的全部结果放到一个$d.objs里面好了,那么this.obj就是this.objs[0]。

    // function $d(...){
    。。。。。
                case '.':
                    this.objs=getByClass(oParent,selector.substring(1));
                    this.obj=this.objs[0];
    //属性选择器,标签选择器也都这么做。

    方法支持群组选择器

    之前的任务中,写了一个each方法,以为addClass和removeClass为例:

    //添加删除css类
    $d.prototype.addClass=function(newClassName){
        each(this.objs,function(item,index){
            item.classList.add(newClassName);
        });
    };
    
    $d.prototype.removeClass=function(oldClassName){
        each(this.objs,function(item,index){
            item.classList.remove(oldClassName);
        });
    };

    其它支持群组选择器的统统使用群组遍历的形式添加方法。


    第七章 综合练习

    7.1 兴趣爱好列表

    任务描述

    task0002目录下创建一个task0002_1.html文件,以及一个js目录和css目录,在js目录中创建task0002_1.js,并将之前写的util.js也拷贝到js目录下。然后完成以下需求。

    第一阶段

    在页面中,有一个单行输入框,一个按钮,输入框中用来输入用户的兴趣爱好,允许用户用半角逗号来作为不同爱好的分隔。

    当点击按钮时,把用户输入的兴趣爱好,按照上面所说的分隔符分开后保存到一个数组,过滤掉空的、重复的爱好,在按钮下方创建一个段落显示处理后的爱好。

    第二阶段

    单行变成多行输入框,一个按钮,输入框中用来输入用户的兴趣爱好,允许用户用换行、空格(全角/半角)、逗号(全角/半角)、顿号、分号来作为不同爱好的分隔。

    当点击按钮时的行为同上

    第三阶段

    用户输入的爱好数量不能超过10个,也不能什么都不输入。当发生异常时,在按钮上方显示一段红色的错误提示文字,并且不继续执行后面的行为;当输入正确时,提示文字消失。

    同时,当点击按钮时,不再是输出到一个段落,而是每一个爱好输出成为一个checkbox,爱好内容作为checkbox的label。

    解决思路

    用面向对象的方法来写,构造一个Hobby对象,然后绑定点击方法。

        
        
        
    
        
    • 第一阶段:用split转化为一个数组。之前的js库中,已经有了数组去重方法uniqArray(arr)。(参见第一章第三节)正好拿出来用。

      function Hobby(textId,btnId,showerId){
        this.id={
            textId:textId,
            btnId:btnId,
            showerId:showerId
        };
      
      }
      
      Hobby.prototype.getHobby=function(){
        var _this=this;
        $(_this.id.btnId).click(function(){
            _this.text=$(_this.id.textId).obj.value;
            _this.hobbyList=_this.text.split(',');
            _this.newHobbyList=uniqArray(_this.hobbyList).filter(function(item){
                return item!=='';
            });
            var content='';
            _this.newHobbyList.forEach(function(item,index){
                content+='
    • '+item+'
    • '; }); $(_this.id.showerId).obj.innerHTML=content; }); };

      window.οnlοad=function(){
      var hobby=new Hobby('#text','#btn','#list');
      hobby.getHobby();

      };
      ```

    • 第二阶段:添加规则

      这一步无非是添加了多一个正则

        //换行、空格(全角/半角)、逗号(全角/半角)、顿号、分号
        this.re=/\n|\s|\,|,|、|;|;/g;

      text用replace转化为半角逗号,然后再处理

    • 第三阶段:表单验证

      要求表单验证是实时的,那么面向对象的优势就出来了。就用keyup事件来更新Hobby对象中的数据吧!然后把验证的结果存入hobby.check中,点击之后如果校验不通过,也不会进行下一步操作。点击时获取的数据就不用再写了

    最后的代码是

    function Hobby(textId,btnId,showerId){
        this.id={
            textId:textId,
            btnId:btnId,
            showerId:showerId
        };
        //换行、空格(全角/半角)、逗号(全角/半角)、顿号、分号
        this.re=/\n|\s|\,|,|、|;|;/g;
        this.check=false;
    }
    
    Hobby.prototype.getHobby=function(){
        var _this=this;
        $(_this.id.btnId).click(function(){
            var content='';
    
            //点击表单校验
            if(!this.check){
                return false;
            }else{
                _this.newHobbyList.forEach(function(item,index){
                    content+='
  • '+item+'
  • '; }); } $(_this.id.showerId).obj.innerHTML=content; }); }; Hobby.prototype.validate=function(validateId){ this.id.validateId=validateId; var _this=this; //通过keyUp获取实时数据 $(this.id.textId).on('keyup',function(){ console.log(_this); _this.text=$(_this.id.textId).obj.value; _this.hobbyList=_this.text.replace(_this.re,',').split(','); _this.newHobbyList=uniqArray(_this.hobbyList).filter(function(item){ return item!==''; }); var tips=''; //表单校验 if(_this.newHobbyList.length>10||_this.newHobbyList.length===0){ tips='不合法的数据!'; $(_this.id.validateId).obj.style.color='red'; _this.check=false; }else{ tips=''; _this.check=true; } $(_this.id.validateId).obj.innerText=tips; }); }; window.onload=function(){ var hobby=new Hobby('#text','#btn','#list'); hobby.getHobby(); hobby.validate('#validate'); };

    7.2 倒计时

    任务描述

    在和上一任务同一目录下面创建一个task0002_2.html文件,在js目录中创建task0002_2.js,并在其中编码,实现一个倒计时功能。

    • 界面首先有一个文本输入框,允许按照特定的格式YYYY-MM-DD输入年月日;
    • 输入框旁有一个按钮,点击按钮后,计算当前距离输入的日期的00:00:00有多少时间差
    • 在页面中显示,距离YYYY年MM月DD日还有XX天XX小时XX分XX秒
    • 每一秒钟更新倒计时上显示的数
    • 如果时差为0,则倒计时停止

    解决思路

    先把html写出来吧!

        
    距离 还有 小时

    解决这个问题主要在于计算倒计时方法。

    第一个注意的地方是,设置未来时间时,月份需要在原基础上减去1。

    var 未来=new Date(年份,月份-1,日期);

    接下来创建一个现在的时间,用未来减去现在,令结果为countDown,它一个毫秒差值。

        var day=Math.floor(countDown/1000/60/60/24);
        var hr=Math.floor(countDown/1000/60/60)%24;
        var min=Math.floor(countDown/1000/60)%60;
        var sec=Math.floor(countDown/1000)%60;

    这样就算出来了。

    然后就是写定时器,每秒刷新一次。注意每次点击后第一件事就是清除定时器。

    function Countdown(futrue){
        this.now=new Date();
        var timeList=futrue.split('-');
    
        this.futrue={
            year:timeList[0],
            month:timeList[1],
            date:timeList[2]
        };
    
    }
    
    Countdown.prototype.getFutrue=function(){
        $('#futrue').obj.innerText=this.futrue.year+'年'+this.futrue.month+'月'+this.futrue.date+'日';
    };
    
    Countdown.prototype.getCount=function(){
        var countDown=this.futrue.computedFutrue-this.now;
        if(countDown<0){
            $('#day').obj.innerHTML=0;
            $('#hours').obj.innerHTML=0;
            $('#min').obj.innerHTML=0;
            $('#sec').obj.innerHTML=0;
            return false;
        }
    
        this.countDown={
            day:Math.floor(countDown/1000/60/60/24),
            hr:Math.floor(countDown/1000/60/60)%24,
            min:Math.floor(countDown/1000/60)%60,
            sec:Math.floor(countDown/1000)%60
        };
    
        $('#day').obj.innerHTML = this.countDown.day;
        $('#hours').obj.innerHTML = this.countDown.hr;
        $('#min').obj.innerHTML = this.countDown.min;
        $('#sec').obj.innerHTML = this.countDown.sec;
    };
    
    window.onload=function(){
        $('#get').click(function(){
            clearInterval(this.timer);
            var futrue=$('#text').obj.value;
    
            this.timer=setInterval(function(){
                var countdown=new Countdown(futrue);
                countdown.getFutrue();
                countdown.getCount();
            },1000);
        });
    };
    

    再改改硬编码部分,那么任务就算完成了。

    7.3 轮播图

    任务描述

    在和上一任务同一目录下面创建一个task0002_3.html文件,在js目录中创建task0002_3.js,并在其中编码,实现一个轮播图的功能。

    • 图片数量及URL均在HTML中写好
    • 可以配置轮播的顺序(正序、逆序)、是否循环、间隔时长
    • 图片切换的动画要流畅
    • 在轮播图下方自动生成对应图片的小点,点击小点,轮播图自动动画切换到对应的图片

    效果示例:http://echarts.baidu.com/ 上面的轮播图(不需要做左右两个箭头)

    解决思路

    对此我只想感叹选项卡轮播图真乃DOM必做的范例。

    首先需要明确需求:

    • 动画切换看起来应该是说无缝滚动,那么需要写一个运动框架。
    • 轮播图需要一个index方法和eq方法。这是轮播图的核心
    • 点击时,可以使用事件代理
    • 配置自动播放的参数,因此最好是用面向对象的思路来写。
    运动框架

    先看运动框架,在之前的笔记里写了一个所谓完美运动框架,现在需要把它封装为$d的方法。

    function getStyle(obj,attr){
        if(obj.crrentStyle){
            return obj.currentStyle[attr];
            //兼容IE8以下
        }else{
            return getComputedStyle(obj,false)[attr];
            //参数false已废。照用就好
        }
    }
    
    $d.prototype.move=function(obj,json,fn){
        var obj=this.obj;
        //清理定时器
        if(obj.timer){
            clearInterval(obj.timer);
        }
    
        obj.timer=setInterval(function(){
            var bStop=false;//如果为false就停了定时器!
            var iCur=0;
            // 处理属性值
            for(var attr in json){
    
    
                if(attr=='opacity'){
                    iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
                }else{
                    iCur=parseInt(getStyle(obj,attr));
                }
    
                //定义速度值
                var iSpeed=(json[attr]-iCur)/8;
                iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);
    
                //检测停止:如果我发现某个值不等于目标点bStop就不能为true。
                if(iCur!==json[attr]){
                    bStop=false;
                }
    
                if(attr=='opacity'){
                    obj.style[attr]=(iCur+iSpeed)/100;
                    obj.style.filter='alpha(opacity:'+(iCur+iSpeed)+')';
                }else{
                    obj.style[attr]=iCur+iSpeed+'px';
                }
            }
    
            //检测是否停止,是的话关掉定时器
            if(bStop===true){
                if(iCur==json[attr]){
                    clearInterval(obj.timer);
                    if(fn){
                        fn();
                    }
                }
            }
            
        },30);
    }
    index方法

    接下来写一个$d的index方法。获取一组同辈元素内,某元素的索引值。

    $d.prototype.index=function(){
        var obj=this.obj;
        var aBrother=obj.parentNode.children;
        var i=0;
    
        for(i=0;i
    eq方法

    然后来写这个eq方法

    $d.prototype.eq=function(n){
        return $(this.objs[n]);
    };
    轮播图

    好了。准备工作搞定,就来写这个轮播图。

    *{
        margin:0;
        padding:0;
    }
    ul li{
        list-style: none;
    }
    
    #tab{
        width: 400px;
        height: 300px;
        margin:200px auto;
        position: relative;
    }
    
    #list{
        width: 400px;
        height: 1204px;
    }
    #list li{
        height: 300px;
    }
    #list img{
        width: 400px;
        height: 300px
    }
    
    #btns{
        position: absolute;
        left: 40px;
        bottom:10px;
        z-index: 999;
    }
    
    #btns li {
        width: 30px;
        height: 30px;
        float: left;
        margin-left: 20px;
        border-radius: 50%;
        background: rgba(0, 0, 0, 0.5);
        cursor: pointer;
    
    }
    
    #btns .active{
        background: red;
    }
    #tab{
        position: relative;
        width: 400px;
        height: 300px;
        overflow: hidden;
    }
    #list{
        position: absolute;
    }
    

    html结构

    点击按钮,要求移动一个图片的高度。

    只做选项卡的话,很快就出来效果了:

    $(function(){
        $('#btns').delegate('li','click',function(){
            $('#btns li').removeClass('active');
            $(this).addClass('active');
    
            var index=$(this).index();
            var height=parseInt(getStyle($('#list li').obj,"height"));
    
            $('#list').move({
                'top':-height*index,
            });
        });
    });

    但是我们要用面向对象的方法来做:

    
    
    $(function(){
        function Tab(option){
            //console.log(option.bLoop)
            //设置顺序
            if(option&&option.order=='-'){
                this.order=1;
                this.iNow=3;
                this.start=3;
                this.end=0;
    
            }else{
                this.order=-1;
                this.iNow=0;
                this.start=0;
                this.end=3;
            }
    
            //设置延迟时间
            if(option&&option.delay){
                this.delay=option.delay;
            }else{
                this.delay=2000;
            }
    
            //循环设置
            if(option&&option.bLoop=='false'){
                this.bLoop=false;
            }else{
                this.bLoop=true;
            }
    
            this.timer=null;
            this.count=0;
            this.height=parseInt(getStyle($('#list li').obj,"height"));
            //页面初始化设置
            $('#btns li').eq(this.iNow).addClass('active');
            $('#list').obj.style.top=-this.height*this.iNow+'px';
    
        }
    
    
        Tab.prototype.tab=function(){
            var _this=this;
            $('#btns li').removeClass('active');
            $('#btns li').eq(_this.iNow).addClass('active');
            $('#list').move({
                'top':-_this.height*_this.iNow,
            });
        };
    
    
    
        Tab.prototype.timerInner=function(){
            this.iNow-=this.order;
            if(this.iNow==this.end-this.order){
                this.iNow=this.start;
                this.tab();
                if(!this.bLoop){
                    //不循环则停止定时器!
                    clearInterval(this.timer);
                }
            }else{
                this.tab();
            }
        };
    
        Tab.prototype.move=function(){
            var _this=this;
    
            $('#btns').delegate('li','click',function(){
                _this.iNow=$(this).index();
                _this.tab();
            });
    
            _this.timer=setInterval(function(){
                return _this.timerInner();
            },_this.delay);
    
            $('#tab').on('mouseover',function(){
                clearInterval(_this.timer);
            });
    
            $('#tab').on('mouseout',function(){
                _this.timer=setInterval(function(){
                    return _this.timerInner();
                },_this.delay);
            });
        };
    
        var _tab=new Tab({
            delay:1000,
            order:'-',
            bLoop:'false'
        });
    
        _tab.move();
    });
    

    放个效果吧:

    # IFE前端(2015春)-task2_第2张图片

    7.4 输入提示框

    任务需求

    在和上一任务同一目录下面创建一个task0002_4.html文件,在js目录中创建task0002_4.js,并在其中编码,实现一个类似百度搜索框的输入提示的功能。

    要求如下:

    • 允许使用鼠标点击选中提示栏中的某个选项
    • 允许使用键盘上下键来选中提示栏中的某个选项,回车确认选中
    • 选中后,提示内容变更到输入框中

    • 自己搭建一个后端Server,使用Ajax来获取提示数据

    示例:

    # IFE前端(2015春)-task2_第3张图片

    解决思路

    html结构:

        
        

      通过ajax方法通过GET请求获取基本数据,然后根据输入内容在数据中查找。

      基本框架应该是:

      $(function(){
          $().ajax('server.json',{
              type:"GET",
              faild:function(status){
                  console.log(status);
              },
              success:function(data){
                  //console.log(data);
                  //主要内容
              }
          });
      });

      然后在根文件夹下建立一个"server.json"文件夹,存放自己做出来的数据

      [
          {
              "id":1,
              "content":"阿姆斯特朗回旋加速喷气式阿姆斯特朗炮"
          },
      
          {
              "id":2,
              "content":"阿森纳"
          },
      
          {
              "id":3,
              "content":"阿斯顿维拉"
          },
          {
              "id":4,
              "content":"阿姆斯特丹"
          }
      ]

      在服务器环境下测试,可以拿到数据。

      但是拿到的是一个字符串,而不是数组。那就用eval方法转一下吧!

      success:function(data){
                  //console.log(data);
                  data=eval(data);
      
                  //主要内容
                  $('#text').on('keyup',function(){
                      var value=this.value;
                      var arr=[];
                      var str='';
                      data.forEach(function(item,index){
                          if(value!==''&&item.content.indexOf(value)!==-1){
                              str+='
    • '+item.content+'
    • '; } }); $('#ul1').obj.innerHTML=str; }); }

      那么这样基本功能就实现了。

      提示框点选发生keyup时,监控event的内容,比如按上,下时,可以点选提示框内容,注意,此处不是真的要让提示框的内容为focus状态。而是高亮显示就可以了。

      写一个success函数内的全局变量index。默认为0,当执行了点击上下方向键时。#ul内的li高亮显示。再点击回车时,高亮显示的li的内容被打印到文本框中。

      但是又有一个问题。当DOM结构改变时,index值应该初始化为0。DOMCharacterDataModified事件可以监听文本节点发生变化。实现想要的功能:

      $('#ul1').on('DOMCharacterDataModified',function(){
          index=0;
      });

      但是那么好用的事件,居然被废弃了。文档提供了一个官方的对象MutationObserver()。本着简单问题简单处理的思路,只要判断#ul1的innerHTML是否变动就可以了。

      $(function(){
          $().ajax('server.json',{
              type:"GET",
              faild:function(status){
                  console.log(status);
              },
              success:function(data){
                  //console.log(data);
                  data=eval(data);
      
                  var index=0;
                  var str='';
      
                  //主要内容
                  $('#text').on('keyup',function(ev){
                      var e=ev||window.event;
                      //console.log(e);
                      var value=this.value;
                      var arr=[];
                      var newStr='';
      
                      data.forEach(function(item,index){
                          if(value!==''&&item.content.indexOf(value)!==-1){
                              arr.push(item.content);
                              newStr+='
    • '+item.content+'
    • '; } }); $('#ul1').obj.innerHTML=newStr; // 如果不同,就把index设置为0. if(str!==newStr){ index=0; str=newStr; } // 先判断按下的键是什么,上下回车 if(e.code=='ArrowUp'&&$('#ul1 li').eq(index)){ index--; if(index<0){ index=arr.length-1; } } if(e.code=='ArrowDown'&&$('#ul1 li').eq(index)){ index++; if(index>arr.length-1){ index=0; } } if(e.code=='Enter'&&$('#ul1 li').eq(index)){ var selector=$('#ul li').eq(index).obj.innerText; this.value=selector; $('#ul1').obj.innerHTML=''; return; } $('#ul1 li').eq(index).addClass('active'); }); } }); });

      放一个效果吧:

      # IFE前端(2015春)-task2_第4张图片

      7.5 界面拖拽交互

      任务需求

      • 实现一个可拖拽交互的界面
      • 如示例图,左右两侧各有一个容器,里面的选项可以通过拖拽来左右移动
      • 被选择拖拽的容器在拖拽过程后,在原容器中消失,跟随鼠标移动
      • 注意拖拽释放后,要添加到准确的位置
      • 拖拽到什么位置认为是可以添加到新容器的规则自己定
      • 注意交互中良好的用户体验和使用引导

      # IFE前端(2015春)-task2_第5张图片

      解决思路

      还是尝试用js的语言来描述需求

      首先是要拖拽。像ps那样。

      其次是拖拽是个模糊位置

      拖的逻辑

      做的是一个绝对定位的元素。通过mousedown事件和mousemove事件实现。

      这是一种很流行的用户界面模式。对于这个效果,也可以考虑把它封装为$djs库的方法。

      $d.prototype.drag=function(){
          each(this.objs,function(item,index){
              drag(item);
          });
      
          function drag(oDiv){//拖拽函数
              oDiv.onmousedown=function (ev){
                  var oEvent=ev||event;
                  //鼠标位置减去偏移量是鼠标相对于html块级元素的位置
                  var disX=oEvent.clientX-oDiv.offsetLeft;
                  var disY=oEvent.clientY-oDiv.offsetTop;
      
                  document.onmousemove=function (ev){
                      var oEvent=ev||event;
                      // 拖拽时,html实际位置就是鼠标拖拽的位置减去相对位置
                      oDiv.style.left=oEvent.clientX-disX+'px';
                      oDiv.style.top=oEvent.clientY-disY+'px';
                  };
      
                  document.onmouseup=function (){
                      document.onmousemove=null;
                      document.onmouseup=null;
                  };
              };
          }
      };

      但是我们发现,需求中的拖拽不是完全自由的。而且完全自由的拖拽在网页中也是不现实的。

      在拖拽之前,它是应该不是绝对定位实现的,一个思路是当鼠标按下,它变为绝对定位,当鼠标松开时,又变为默认的static 定位。同时把之前给这个对象赋予的left和top值恢复到原来的样子(其实就是空字符串)。

      $d.prototype.drag=function(){
          each(this.objs,function(item,index){
              drag(item);
          });
      
          function drag(oDiv){//拖拽函数
              oDiv.onmousedown=function (ev){
                  oDiv.style.position='absolute';
                  var oEvent=ev||event;
      
                  //鼠标位置减去偏移量是鼠标相对于html块级元素的位置
                  var disX=oEvent.clientX-oDiv.offsetLeft;
                  var disY=oEvent.clientY-oDiv.offsetTop;
      
                  document.onmousemove=function (ev){
                      var oEvent=ev||event;
                      // 拖拽时,html实际位置就是鼠标拖拽的位置减去相对位置
                      oDiv.style.left=oEvent.clientX-disX+'px';
                      oDiv.style.top=oEvent.clientY-disY+'px';
                  };
      
                  document.onmouseup=function (){
                      oDiv.style.position='static';
                      oDiv.style.left='';
                      oDiv.style.top='';
      
                      document.onmousemove=null;
                      document.onmouseup=null;
                  };
              };
          }
      };
      基本框架

      先把结构写出来。

      • 阿姆斯特朗炮
      • 阿姆斯特丹
      • 阿姆
      • 阿姆斯壮

      css样式

      *{
          margin:0;
          padding: 0;
      }
      ul li{
          list-style: none;
          font-size: 30px;
          text-align: center;
          color: #fff;
      }
      
      #ul1{
          position: relative;
          float: left;
          width: auto;
          height: 400px;
          border: 1px solid #ccc;
      }
      #ul1 li{
          margin-bottom: 2px;
          width: 200px;
          height: 50px;
          background: red;
      }
      
      #ul2{
          position: relative;
          float: left;
          margin-left: 200px;
          height: 400px;
          border: 1px solid #ccc;
      }
      #ul2 li{
          margin-bottom: 2px;
          width: 200px;
          height: 50px;
          background: blue;
      }

      接下来js部分两行代码就搞定了:

      $(function(){
          $('#ul1 li').drag();
          $('#ul2 li').drag();
      });
      放的逻辑

      当鼠标指针进入到指定区域(比如从#ul1移动到#ul2)后,松开鼠标,立刻从原来的区域复制一个节点,添加到新的区域中,并从原来的区域删除该节点。

      既然有了拖放的目标,所以drag方法必须接受一个id字符串参数。比如$(#ul1 li).drag('#ul2')——这样当鼠标松开,clientX和clientY的坐标在#ul2的范围内时就触发DOM改变。

      document.onmouseup=function (ev){
                      var oEvent=ev||window.event;
                      var x=oEvent.clientX;
                      var y=oEvent.clientY;
                      var l=$(id).obj.offsetLeft;
                      var r=parseInt(getStyle($(id).obj,'width'))+l;
                      var t=$(id).obj.offsetTop;
                      var b=parseInt(getStyle($(id).obj,'height'))+t;
      
                      if(x>l&&xt&&y
      DOM操作

      DOM操作及其简单:

                      if(x>l&&xt&&y

      但是问题又来了。当拖过去的li再想拖回来,就不行了。

      证明用$(#ul1 li).drag('#ul2')写成的函数还是有问题。

      有两个思路,一个是把drag作为一个事件,添加事件代理。一个就是监听DOM变动,重新赋值,这里不用担心重复添加事件。在这里为了简单起见采用第二种方法。

      $(function(){
          $('#ul1 li').drag('#ul2');
          $('#ul2 li').drag('#ul1');
      
          // Firefox和Chrome早期版本中带有前缀
          var         MutationObserver=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;
          // 创建观察者对象
          var observer=new MutationObserver(function(mutations) {
              $('#ul1 li').drag('#ul2');
              $('#ul2 li').drag('#ul1');
          });
      
          // 配置观察选项:
          var config = { attributes: true, childList: true, characterData: true };
          // 传入目标节点和观察选项
          observer.observe($('#ul1').obj, config);
      
      });

      放一个效果吧:

      # IFE前端(2015春)-task2_第6张图片

      至此百度前端初级班任务完成。

      转载于:https://www.cnblogs.com/djtao/p/6347372.html

      你可能感兴趣的:(# IFE前端(2015春)-task2)