第一章 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。

4.2 cookie

任务描述

实现以下函数

// 设置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); });

放一个效果吧:

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

# IFE前端(2015春)-task2的更多相关文章

  1. 个人待办事项工具的设计和搭建(IFE前端2015春季 任务3)

    这是我几个月之前的项目作品,花了相当的时间去完善.博客人气不高,但拿代码的人不少,所以一直处于保密状态.没有公开代码.但如果对你有帮助,并能提出指导意见的,我将十分感谢. IFE前端2015春季 任务 ...

  2. 输入框提示--------百度IFE前端task2

    第一版本: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <tit ...

  3. 深度克隆(对象、数组)--------百度IFE前端task2

    var srcObj = { a: 1, b: { b1: ["hello", "hi"], b2: "JavaScript" }}; co ...

  4. 百度前端技术学院-task2.18-2.19源码以及个人总结

    源码:http://yun.baidu.com/share/link?shareid=2310452098&uk=1997604551 1.感觉在写js的时候,最好先理清思路,先干什么,在干什 ...

  5. 喜迎2015年新年:坦克大战(Robocode)游戏编程比赛图文总结

    2015春节前,葡萄城的软件工程师以特有的方式来迎接新年——2015新年编程邀请赛. 邀请赛的初衷,是和大家一起,寻找编程最初的单纯的快乐.       在代码的世界里,添加动力,继续远航.      ...

  6. JS删除数组条目中重复的条目

    [腾讯2015春招web前端开发练习卷] 请给Array本地对象增加一个原型方法,它用于删除数组条目中重复的条目(可能有多个),返回值是一个包含被删除的重复条目的新数组. Array.prototyp ...

  7. jsonp跨越请求百度搜索api 实现下拉列表提示

    题目来源: 最近在做百度IFE前端技术学院的题,然后有一题就是模拟百度搜索智能提示.题目是开源的,稍后给出地址. 因为博主没学过后端啊,欲哭无泪,所以不能实现后端模糊搜索,那如果前端ajax纯粹请求一 ...

  8. 小爬虫。爬取网站多页的通知标题并存取在txt文档里。

    爬取网页中通知标题的内容展示: this is  1  page!<精算学综合>科目考试参考大纲2016年上半年研究生开题报告评议审核结果公示[答辩]2016下半年研究生论文答辩及学位评定 ...

  9. python 爬取腾讯微博并生成词云

    本文以延参法师的腾讯微博为例进行爬取并分析 ,话不多说 直接附上源代码.其中有比较详细的注释. 需要用到的包有 BeautifulSoup WordCloud jieba # coding:utf-8 ...

随机推荐

  1. delphi弹出信息框大全(转载)

    1. 警告信息框 MessageBox(Handle,'警告信息框','警告信息框',MB_ICONWARNING); 2.疑问信息框 MessageBox(Handle,'疑问信息框','疑问信息框 ...

  2. 学习js的正确姿势

    轻松入门: http://dwz.cn/6yYLoo 全面理解: http://www.shouce.ren/api/view/a/1116 大师进阶: http://es6.ruanyifeng.c ...

  3. 第18章—后端分页(Mybatis)

    spring boot 系列学习记录:http://www.cnblogs.com/jinxiaohang/p/8111057.html 码云源码地址:https://gitee.com/jinxia ...

  4. 我的Android进阶之旅------>Android Studio使用statistics插件统计项目代码总行数

    今天公司说要统计一下项目总共了多少行代码,于是上网看了一下使用statistic插件可以统计代码总行数,下面给大家分享一下 ! 1.下载[statistic]插件 首先当然要把名为[statistic ...

  5. action接收请求参数

    一.采用基本类型接收请求参数(get/post)在Action类中定义与请求参数同名的属性,struts2便能接收自动接收请求参数并赋给同名属性. action的代码: public class Pa ...

  6. android studio本地gradle

    1.从网站上下载http://services.gradle.org/distributions/ 2.打开工程里的gradle-wrapper.properties, distributionUrl ...

  7. Nothing is impossible

    题记: <你凭什么上北大>--贺舒婷.依稀记得这篇文章是我高二的时候在<青年文摘>读到的,从此她就成了我为之奋斗的动力.北大,也是我梦中的学府,虽然自己也曾刻苦过,但是还是没有 ...

  8. HDU1421:搬寝室(线性dp)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1421 又是一道,没有思想的题,看了题解,我发现我的dp题几乎都看了题解,我总是想不好状态转移方程,汗颜,以 ...

  9. Hadoop十年解读与发展预测

    编者按:Hadoop于2006年1月28日诞生,至今已有10年,它改变了企业对数据的存储.处理和分析的过程,加速了大数据的发展,形成了自己的极其火爆的技术生态圈,并受到非常广泛的应用.在2016年Ha ...

  10. Python Static Method

    How to define a static method in Python?Demo: #!/usr/bin/python2.7 #coding:utf-8 # FileName: test.py ...