第一章 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
}
解决方案
基本数据类型包括undefined
,Null
(typeof操作返回Object对象),Boolean
,Number
,String
,Object
。
应当明确,引用数据类型(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=/^1d{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);
解决方案
注册事件有两种方法:addEventListener
和attachEvent
。两者用法类似,但是存在若干不同。
-
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个状态码:
- 0=>open方法尚未调用
- 1=>open已经调用
- 2=>接收到头信息
- 3=>接收到响应主体
- 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=/ |s|\,|,|、|;|;/g;
text用replace转化为半角逗号,然后再处理
-
第三阶段:表单验证
要求表单验证是实时的,那么面向对象的优势就出来了。就用keyup事件来更新Hobby对象中的数据吧!然后把验证的结果存入hobby.check中,点击之后如果校验不通过,也不会进行下一步操作。点击时获取的数据就不用再写了
最后的代码是
function Hobby(textId,btnId,showerId){
this.id={
textId:textId,
btnId:btnId,
showerId:showerId
};
//换行、空格(全角/半角)、逗号(全角/半角)、顿号、分号
this.re=/
|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{
400px;
height: 300px;
margin:200px auto;
position: relative;
}
#list{
400px;
height: 1204px;
}
#list li{
height: 300px;
}
#list img{
400px;
height: 300px
}
#btns{
position: absolute;
left: 40px;
bottom:10px;
z-index: 999;
}
#btns li {
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;
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事件实现。
这是一种很流行的用户界面模式。对于这个效果,也可以考虑把它封装为$d
js库的方法。
$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;
auto;
height: 400px;
border: 1px solid #ccc;
}
#ul1 li{
margin-bottom: 2px;
200px;
height: 50px;
background: red;
}
#ul2{
position: relative;
float: left;
margin-left: 200px;
height: 400px;
border: 1px solid #ccc;
}
#ul2 li{
margin-bottom: 2px;
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);
});
放一个效果吧:
至此百度前端初级班任务完成。