第一章 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); });

放一个效果吧:

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

最新文章

  1. Python3.5 day3作业二:修改haproxy配置文件。
  2. 配置SQL server远程连接(局域网)
  3. 向IPython Notebook中导入.py文件
  4. Js倒计时程序
  5. 代码演示 .NET 4.5 自带的 ReadonlyCollection 的使用
  6. python 代码片段10
  7. C语言的内存管理
  8. AppSetting ,connectionStrings配置节
  9. Java之绘制方法
  10. 新发现。css3控制浏览器滚动条的样式
  11. JSP和JSTL
  12. Android Studio JNI开发入门教程
  13. python 2.7中urllib 2 与python 3.5中 urllib的区别。
  14. XMLHttpRequest2 异步 ajax
  15. 我的运维之旅-查找文本的linux命令
  16. kubernetes入门(08)kubernetes单机版的安装和使用
  17. GL-inet路由器当主控制作WIFI视频小车
  18. tarjin求割点
  19. #Leetcode# 524. Longest Word in Dictionary through Deleting
  20. SQL SERVER EXPRESS 连接字符串

热门文章

  1. phpstrrchr()函数的问题
  2. ASP.NET Identity 2集成到MVC5项目--笔记02
  3. https://help.aliyun.com/knowledge_detail/49787.html?spm=a2c4g.11186631.2.3.6f856f39tiE98P
  4. runtime(二)
  5. Linux基础命令(三)
  6. SAP 后台job
  7. mysql第三天作业
  8. HDU - 3488 Tour (KM最优匹配)
  9. 认识shiro
  10. Spring 之混合配置