第一章 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<arr.length;i++){
        check=true;
        for(var j=0;j<newArr.length;j++){
            if(arr[i]==newArr[j]){
                check=false;
                break;
            }
        }
        if(check){
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

// 使用示例
var a = [1, 3, 5, 7, 5, 3];
var b = uniqArray(a);
console.log(b); // [1, 3, 5, 7]
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<arr.length;i++){
        fn(arr[i],i);
    }
}

// 使用示例
var arr = ['java', 'c', 'php', 'html'];
function output(item, index) {
    console.log(index + ': ' + item);
}
each(arr, output);  // 0:java, 1:c, 2:php, 3:html
找到属性的数量

对象一般用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('dangjingtao@ds.cc'));

// 判断是否为手机号
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"); // 返回第一个<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<all.length;i++){
                    if(all[i].getAttribute(str)){
                        arr.push(all[i]);
                    }else if(str.indexOf('=')>0){
                        // 匹配等号又边
                        var value=str.replace(/[^...]+?(?=\=)|\=|['"]/g,'');
                        // 匹配等号左边
                        var attr=str.match(/[^...]+?(?=\=)|\=|['"]/g)[0];
                        if(all[i].getAttribute(attr)==value){
                            arr.push(all[i]);
                        }
                    }
                }
               // 如果查找不到,则返回空对象
                this.obj=arr[0]?arr[0]:null;
            break;

            default:
                this.obj=oParent.getElementsByTagName(selector)[0];
        }
    }else{// 后代选择器
        var nodeList=selector.split(' ');
        var parent=nodeList[0];
        var children=nodeList[1];

        this.obj=$(children,$(parent).obj).obj;
    }

}

既然用了面向对象的方法来写,那么这个函数就需要进化一下,把$变成$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<events.length;i++){
            if(events[i].target==element&&events[i].event==_event){
                //调用这个属性,然后删除
                element.detachEvent('on'+_event,events[i].fn);
                events.splice(i,1);
            }
        }
    }
}

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

$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 事件监听代理

任务描述

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

最笨的方法

<ul id="list">
    <li id="item1">Simon</li>
    <li id="item2">Kenner</li>
    <li id="item3">Erik</li>
</ul>
function clickListener(event) {
    console.log(event);
}

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

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

稍微好一些的

<ul id="list">
    <li>Simon</li>
    <li>Kenner</li>
    <li>Erik</li>
</ul>

我们试图改造一下

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

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

我们通过自己写的函数,取到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);

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

$.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);

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<arr.length;i++){
        // 再对每个单独的cookie进一步细分,
        // arr2[0]代表名字,arr2[1]代表值
        var arr2=arr[i].split('=');

        if(arr2[0]==name){
            // 把这个cookie值传出去!
            return arr2[1];
        }
    }
    // 如果查找不到,返回空字符串
    return '';
}

function removeCookie(name){
    setCookie(name,'null',-1);
}

第五章 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对象,然后绑定点击方法。

	<textarea id="text"></textarea>
    <button type="button" id="btn" name="button">获取</button>
	<span id="validate></span>
    <ol id="list">

    </ol>
  • 第一阶段:用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+='<li>'+item+'</li>';
    		});
    		$(_this.id.showerId).obj.innerHTML=content;
    	});
    };
    

    window.onload=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+='<li>'+item+'</li>';
			});
		}

		$(_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写出来吧!

	<input type="text" id="text"><button id="get">get</button><br>
    <span>距离 <span id="futrue"></span> 还有 <span id="day"></span> 天 <span id="hours"></span> 小时 <span id="min"></span> 分 <span id="sec"></span>秒</span>

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

第一个注意的地方是,设置未来时间时,月份需要在原基础上减去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<aBrother.length;i++){
        if(aBrother[i]==obj){
            return 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结构

<div id="tab">
        <ul id="btns">
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
        <div id="wrap">
            <ul id="list">
                <li class="imgs"><img src="images/1.jpg"></li>
                <li class="imgs"><img src="images/2.jpg"></li>
                <li class="imgs"><img src="images/3.jpg"></li>
                <li class="imgs"><img src="images/4.jpg"></li>
            </ul>
        </div>
    </div>

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

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

$(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();
});

放个效果吧:

7.4 输入提示框

任务需求

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

要求如下:

  • 允许使用鼠标点击选中提示栏中的某个选项

  • 允许使用键盘上下键来选中提示栏中的某个选项,回车确认选中

  • 选中后,提示内容变更到输入框中

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

示例:

示例

解决思路

html结构:

	<input type="text" id="text">
    <div id="tips">
		<ul id=ul1></ul>
	</div>

通过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+='<li>'+item.content+'</li>';
					}
				});

				$('#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+='<li>'+item.content+'</li>';
					}
				});


				$('#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');
			});
		}
	});
});

放一个效果吧:

7.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;
            };
        };
    }
};
基本框架

先把结构写出来。

<ul id="ul1">
        <li>阿姆斯特朗炮</li>
        <li>阿姆斯特丹</li>
    </ul>

    <ul id="ul2">
        <li>阿姆</li>
        <li>阿姆斯壮</li>
    </ul>

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&&x<r&&y>t&&y<b){
                    console.log(1);
                }
                oDiv.style.position='static';
                oDiv.style.left='';
                oDiv.style.top='';

                document.onmousemove=null;
                document.onmouseup=null;
            };
DOM操作

DOM操作及其简单:

				if(x>l&&x<r&&y>t&&y<b){
                    //console.log(1);
                    $(id).obj.appendChild(oDiv);
                }

但是问题又来了。当拖过去的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);

});

放一个效果吧:

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

 posted on 2017-01-24 16:11  葡萄美酒夜光杯  阅读(765)  评论(0编辑  收藏  举报