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

期望达成

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

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

任务描述

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

  1. // 判断arr是否为一个数组,返回一个bool值
  2. function isArray(arr) {
  3. // your implement
  4. }
  5. // 判断fn是否为一个函数,返回一个bool值
  6. function isFunction(fn) {
  7. // your implement
  8. }

解决方案

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

  1. // 判断arr是否为一个数组,返回一个bool值
  2. function isArray(arr) {
  3. // your implement
  4. return Array.isArray(arr);
  5. }
  6. // 判断fn是否为一个函数,返回一个bool值
  7. function isFunction(fn) {
  8. // your implement
  9. return typeof fn=='function';
  10. }

1.2 数据类型的特性

任务描述

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

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

解决方案

基本数据类型包括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

  1. // 使用递归来实现一个深度克隆,可以复制一个目标对象,返回一个完整拷贝
  2. // 被复制的对象类型会被限制为数字、字符串、布尔、日期、数组、Object对象。不会包含函数、正则对象等
  3. function cloneObject(src) {
  4. // your implement
  5. var clone=null;
  6. if(typeof src!=='object'){
  7. clone=src;
  8. }else{
  9. if(Array.isArray(src)){
  10. clone=src.slice();
  11. }else if(Object.prototype.toString.call(src)=='[object Date]'){
  12. clone=new Date(src.valueOf());
  13. }else{
  14. clone={};
  15. for(var attr in src){
  16. clone[attr]=cloneObject(src[attr]);
  17. }
  18. }
  19. }
  20. return clone;
  21. }
  22. // 测试用例:
  23. var srcObj = {
  24. a: 1,
  25. b: {
  26. b1: ["hello", "hi"],
  27. b2: "JavaScript"
  28. }
  29. };
  30. var abObj = srcObj;
  31. var tarObj = cloneObject(srcObj);
  32. srcObj.a = 2;
  33. srcObj.b.b1[0] = "Hello";
  34. console.log(abObj.a);//2
  35. console.log(abObj.b.b1[0]);//"Hello"
  36. console.log(tarObj.a); // 1
  37. console.log(tarObj.b.b1[0]); // "hello"

测试通过。

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

任务描述

util.js中实现以下函数

  1. // 对数组进行去重操作,只考虑数组中元素为数字或字符串,返回一个去重后的数组
  2. function uniqArray(arr) {
  3. // your implement
  4. }
  5. // 实现一个简单的trim函数,用于去除一个字符串,头部和尾部的空白字符
  6. // 假定空白字符只有半角空格、Tab
  7. function simpleTrim(str) {
  8. // your implement
  9. }
  10. // 接下来,我们真正实现一个trim
  11. // 对字符串头尾进行空格字符的去除、包括全角半角空格、Tab等,返回一个字符串
  12. // 尝试使用一行简洁的正则表达式完成该题目
  13. function trim(str) {
  14. // your implement
  15. }
  16. // 实现一个遍历数组的方法,针对数组中每一个元素执行fn函数,并将数组索引和元素作为参数传递
  17. function each(arr, fn) {
  18. // your implement
  19. }
  20. // 获取一个对象里面第一层元素的数量,返回一个整数
  21. function getObjectLength(obj) {}

解决方案

数组去重

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

  1. function uniqArray(arr) {
  2. var newArr=[];
  3. var check;
  4. for(var i=0;i<arr.length;i++){
  5. check=true;
  6. for(var j=0;j<newArr.length;j++){
  7. if(arr[i]==newArr[j]){
  8. check=false;
  9. break;
  10. }
  11. }
  12. if(check){
  13. newArr.push(arr[i]);
  14. }
  15. }
  16. return newArr;
  17. }
  18. // 使用示例
  19. var a = [1, 3, 5, 7, 5, 3];
  20. var b = uniqArray(a);
  21. console.log(b); // [1, 3, 5, 7]
trim函数

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

  1. function simpleTrim(str) {
  2. // your implement
  3. return str.trim();
  4. }

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

  1. function trim(str) {
  2. // 行首的所有空格和行尾的所有空格
  3. var re=/^\s+|\s+$/g;
  4. return str.replace(re,'');
  5. }
  6. // 使用示例
  7. var str = ' hi! ';
  8. str = trim(str);
  9. console.log(str); // 'hi!'
数组遍历

这跟forEach方法是一样的。

  1. // 实现一个遍历数组的方法,针对数组中每一个元素执行fn函数,并将数组索引和元素作为参数传递
  2. function each(arr, fn) {
  3. // your implement
  4. for(var i=0;i<arr.length;i++){
  5. fn(arr[i],i);
  6. }
  7. }
  8. // 使用示例
  9. var arr = ['java', 'c', 'php', 'html'];
  10. function output(item, index) {
  11. console.log(index + ': ' + item);
  12. }
  13. each(arr, output); // 0:java, 1:c, 2:php, 3:html
找到属性的数量

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

  1. // 获取一个对象里面第一层元素的数量,返回一个整数
  2. function getObjectLength(obj) {
  3. var count=0;
  4. for(var attr in obj){
  5. count++;
  6. }
  7. return count;
  8. }
  9. // 使用示例
  10. var obj = {
  11. a: 1,
  12. b: 2,
  13. c: {
  14. c1: 3,
  15. c2: 4
  16. }
  17. };
  18. console.log(getObjectLength(obj)); // 3

1.4 正则表达式

任务描述

util.js完成以下代码

  1. // 判断是否为邮箱地址
  2. function isEmail(emailStr) {
  3. // your implement
  4. }
  5. // 判断是否为手机号
  6. function isMobilePhone(phone) {
  7. // your implement
  8. }

解决方案

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

  1. // 判断是否为邮箱地址
  2. function isEmail(emailStr) {
  3. // 开头必须以字母和数字跟着1一个“@”,后边是小写字母或数字,最后就是域名(2-4位)。
  4. var re=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/;
  5. return re.test(emailStr);
  6. }
  7. //测试
  8. //console.log(isEmail('dangjingtao@ds.cc'));
  9. // 判断是否为手机号
  10. function isMobilePhone(phone) {
  11. //手机号必须以1开头,后面跟着9位数字
  12. var re=/^1\d{10,10}$/;
  13. return re.test(phone);
  14. }
  15. //测试
  16. //console.log(isMobilePhone('15515515515'));

第二章 DOM

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

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

2.1 DOM查询方法

任务描述

先来一些简单的,在你的util.js中完成以下任务:

  1. // 为element增加一个样式名为newClassName的新样式
  2. function addClass(element, newClassName) {
  3. // your implement
  4. }
  5. // 移除element中的样式oldClassName
  6. function removeClass(element, oldClassName) {
  7. // your implement
  8. }
  9. // 判断siblingNode和element是否为同一个父元素下的同一级的元素,返回bool值
  10. function isSiblingNode(element, siblingNode) {
  11. // your implement
  12. }

解决方案

addClass方法的实现

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

  1. // 为element增加一个样式名为newClassName的新样式
  2. function addClass(element, newClassName) {
  3. element.classList.add(newClassName);
  4. }
  5. // 移除element中的样式oldClassName
  6. function removeClass(element, oldClassName) {
  7. element.classList.remove(oldClassName);
  8. }
同辈方法
  1. // 判断siblingNode和element是否为同一个父元素下的同一级的元素,返回bool值
  2. function isSiblingNode(element, siblingNode) {
  3. // your implement
  4. return element.parentNode==siblingNode.parentNode;
  5. }

2.2 基本选择器(mini $)

任务描述

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

  1. // 实现一个简单的Query
  2. function $(selector) {
  3. }
  4. // 可以通过id获取DOM对象,通过#标示,例如
  5. $("#adom"); // 返回id为adom的DOM对象
  6. // 可以通过tagName获取DOM对象,例如
  7. $("a"); // 返回第一个<a>对象
  8. // 可以通过样式名称获取DOM对象,例如
  9. $(".classa"); // 返回第一个样式定义包含classa的对象
  10. // 可以通过attribute匹配获取DOM对象,例如
  11. $("[data-log]"); // 返回第一个包含属性data-log的对象
  12. $("[data-time=2015]"); // 返回第一个包含属性data-time且值为2015的对象
  13. // 可以通过简单的组合提高查询便利性,例如
  14. $("#adom .classa"); // 返回id为adom的DOM所包含的所有子节点中,第一个样式定义包含classa的对象

解决方案

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

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

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

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

    请出getByClass函数吧!

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

方案如下

  1. function $d(selector,oParent) {
  2. //不写第二个参数时,oParent就是document
  3. oParent=oParent?oParent:document;
  4. //用来存放选择器对象。
  5. this.obj=null;
  6. // 如果没有空格
  7. if(!selector.match(/\s/)){
  8. switch(selector[0]){
  9. case '#'://id选择器
  10. this.obj=oParent.getElementById(selector.substring(1));
  11. break;
  12. case '.'://类选择器
  13. this.obj=getByClass(oParent,selector.substring(1))[0];
  14. break;
  15. case '['://属性选择器
  16. // 提取方括号内的属性名
  17. var str=selector.replace(/\[|\]/g,'');
  18. // 找出父级的对象集合,方便遍历
  19. var all=oParent.getElementsByTagName('*');
  20. // 存放找到的html元素
  21. var arr=[];
  22. for(var i=0;i<all.length;i++){
  23. if(all[i].getAttribute(str)){
  24. arr.push(all[i]);
  25. }else if(str.indexOf('=')>0){
  26. // 匹配等号又边
  27. var value=str.replace(/[^...]+?(?=\=)|\=|['"]/g,'');
  28. // 匹配等号左边
  29. var attr=str.match(/[^...]+?(?=\=)|\=|['"]/g)[0];
  30. if(all[i].getAttribute(attr)==value){
  31. arr.push(all[i]);
  32. }
  33. }
  34. }
  35. // 如果查找不到,则返回空对象
  36. this.obj=arr[0]?arr[0]:null;
  37. break;
  38. default:
  39. this.obj=oParent.getElementsByTagName(selector)[0];
  40. }
  41. }else{// 后代选择器
  42. var nodeList=selector.split(' ');
  43. var parent=nodeList[0];
  44. var children=nodeList[1];
  45. this.obj=$(children,$(parent).obj).obj;
  46. }
  47. }

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

  1. function $(selector,oParent){
  2. return new $d(selector,oParent);
  3. }

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

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


第三章 事件

  • 熟悉DOM事件相关知识

3.1 事件注册

任务描述

我们来继续用封装自己的小jQuery库来实现我们对于JavaScript事件的学习,还是在你的util.js,实现以下函数

  1. // 给一个element绑定一个针对event事件的响应,响应函数为listener
  2. function addEvent(element, event, listener) {
  3. // your implement
  4. }
  5. // 例如:
  6. function clicklistener(event) {
  7. ...
  8. }
  9. addEvent($("#doma"), "click", a);
  10. // 移除element对象对于event事件发生时执行listener的响应
  11. function removeEvent(element, event, listener) {
  12. // your implement
  13. }

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

  1. // 实现对click事件的绑定
  2. function addClickEvent(element, listener) {
  3. // your implement
  4. }
  5. // 实现对于按Enter键时的事件绑定
  6. function addEnterEvent(element, listener) {
  7. // your implement
  8. }

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

  • 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。比如说,我要创建一个对某个按钮创建一个点击事件处理函数:

    1. function xxx(){
    2. //balabala
    3. }
    4. window.onload=function(){
    5. var oBtn=document.getElementById('btn');
    6. //注册
    7. oBtn.addEventListener('click',xxx);
    8. //移除
    9. oBtn.removeEventListener('click',xxx);
    10. };
  • attachEvent

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

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

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

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

  1. //判断是否为IE浏览器,返回-1或者版本号
  2. function isIE() {
  3. var info=navigator.userAgent;
  4. var re=/msie (\d+\.\d+)/i;
  5. var reEdge=/rv:(\d+\.\d+)/i;
  6. if(info.match(re)){
  7. return info.match(re)[1];
  8. }else if(info.match(reEdge)&&!info.match(/firefox/i)){
  9. // 兼容Edge浏览器
  10. return info.match(reEdge)[1];
  11. }else{
  12. return -1;
  13. }
  14. }

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

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

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

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

  1. // 给一个element绑定一个针对_event事件的响应,响应函数为listener
  2. function addEvent(element,_event,listener) {
  3. if(isIE()==-1||isIE()>=9){
  4. element.addEventListener(_event,listener);
  5. }else if(isIE()!==-1&&isIE()<9){
  6. // 如果函数的绑定信息没有,就创建一个
  7. if(!listener.prototype.bindEvents){
  8. listener.prototype.bindEvents=[];
  9. }
  10. var bindInfo={
  11. target:element,
  12. event:_event,
  13. fn:function(){
  14. return listener.call(element);
  15. }
  16. };
  17. listener.prototype.bindEvents.push(bindInfo);
  18. element.attachEvent('on'+_event,bindInfo.fn);
  19. }
  20. }
  21. // 移除element对象对于event事件发生时执行listener的响应
  22. function removeEvent(element, _event, listener) {
  23. if(isIE()==-1||isIE()>=9){
  24. element.removeEventListener(_event,listener);
  25. }else if(isIE()!==-1&&isIE()<9){
  26. var events=listener.prototype.bindEvents;
  27. for(var i=0;i<events.length;i++){
  28. if(events[i].target==element&&events[i].event==_event){
  29. //调用这个属性,然后删除
  30. element.detachEvent('on'+_event,events[i].fn);
  31. events.splice(i,1);
  32. }
  33. }
  34. }
  35. }

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

  1. $d.prototype.on=function(_event,listener){
  2. addEvent(this.obj,_event,listener);
  3. };
  4. $d.prototype.un=function(_event,listener){
  5. removeEvent(this.obj,_event,listener);
  6. };
  7. $d.prototype.click=function(listener){
  8. addEvent(this.obj,'click',listener);
  9. };
  10. $d.prototype.enter=function(listener){
  11. addEvent(this.obj,'keydown',function(ev){
  12. var e=ev||window.event;
  13. if(e.keyCode==13){
  14. return listener();
  15. }
  16. });
  17. };

经测试兼容IE8。

3.2 事件监听代理

任务描述

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

最笨的方法

  1. <ul id="list">
  2. <li id="item1">Simon</li>
  3. <li id="item2">Kenner</li>
  4. <li id="item3">Erik</li>
  5. </ul>
  1. function clickListener(event) {
  2. console.log(event);
  3. }
  4. $.click($("#item1"), clickListener);
  5. $.click($("#item2"), clickListener);
  6. $.click($("#item3"), clickListener);

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

稍微好一些的

  1. <ul id="list">
  2. <li>Simon</li>
  3. <li>Kenner</li>
  4. <li>Erik</li>
  5. </ul>

我们试图改造一下

  1. function clickListener(event) {
  2. console.log(event);
  3. }
  4. each($("#list").getElementsByTagName('li'), function(li) {
  5. addClickEvent(li, clickListener);
  6. });

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

  1. <ul id="list">
  2. <li id="item1">Simon</li>
  3. <li id="item2">Kenner</li>
  4. <li id="item3">Erik</li>
  5. </ul>
  6. <button id="btn">Change</button>
  1. function clickListener(event) {
  2. console.log(event);
  3. }
  4. function renderList() {
  5. $("#list").innerHTML = '<li>new item</li>';
  6. }
  7. function init() {
  8. each($("#list").getElementsByTagName('li'), function(item) {
  9. $.click(item, clickListener);
  10. });
  11. $.click($("#btn"), renderList);
  12. }
  13. init();

我们增加了一个按钮,当点击按钮时,改变list里面的项目,这个时候你再点击一下li,绑定事件不再生效了。那是不是我们每次改变了DOM结构或者内容后,都需要重新绑定事件呢?当然不会这么笨,接下来学习一下事件代理,然后实现下面新的方法:

  1. // 先简单一些
  2. function delegateEvent(element, tag, eventName, listener) {
  3. // your implement
  4. }
  5. $.delegate = delegateEvent;
  6. // 使用示例
  7. // 还是上面那段HTML,实现对list这个ul里面所有li的click事件进行响应
  8. $.delegate($("#list"), "li", "click", clickHandle);

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

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

解决方案

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

  1. $d.prototype.delegate=function(tags,_event,listener){
  2. addEvent(this.obj,_event,function(ev){
  3. var e=ev||window.event;
  4. //console.log(e.target.nodeName);
  5. if(e.target.nodeName.toUpperCase()==tags.toUpperCase()){
  6. return listener.call(e.target);
  7. }
  8. });
  9. };
  10. //测试
  11. window.onload=function(){
  12. $('#list').delegate('li','click',function(){
  13. console.log(this);
  14. })
  15. }

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


第四章 BOM

了解BOM的基础知识

4.1 元素定位

任务描述

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

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

解决思路

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

  1. function getElementLeft(element){
  2.     var actualLeft = element.offsetLeft;
  3.     var current = element.offsetParent;
  4.     while (current !== null){
  5.       actualLeft += current.offsetLeft;
  6.       current = current.offsetParent;
  7.     }
  8.     return actualLeft;
  9.   }
  10.   function getElementTop(element){
  11.     var actualTop = element.offsetTop;
  12.     var current = element.offsetParent;
  13.     while (current !== null){
  14.       actualTop += current.offsetTop;
  15.       current = current.offsetParent;
  16.     }
  17.     return actualTop;
  18.   }

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

  1. // 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
  2. function getPosition(element) {
  3. // your implement
  4. function getElementLeft(ele){
  5.     var actualLeft = ele.offsetLeft;
  6.     var current = ele.offsetParent;
  7.     while (current !== null){
  8.       actualLeft += current.offsetLeft;
  9.       current = current.offsetParent;
  10.     }
  11.     return actualLeft;
  12.   }
  13.   function getElementTop(ele){
  14.     var actualTop = ele.offsetTop;
  15.     var current = ele.offsetParent;
  16.     while (current !== null){
  17.       actualTop += current.offsetTop;
  18.       current = current.offsetParent;
  19.     }
  20.     return actualTop;
  21.   }
  22. var position={};
  23. var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
  24. var scrollLeft=document.documentElement.scrollLeft||document.body.scrollLeft;
  25. var left=getElementLeft(element)-scrollLeft;
  26. var top=getElementTop(element)-scrollTop;
  27. position.x=left;
  28. position.y=top;
  29. return position;
  30. }

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

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

这两个方法都兼容IE8。

4.2 cookie

任务描述

实现以下函数

  1. // 设置cookie
  2. function setCookie(cookieName, cookieValue, expiredays) {
  3. // your implement
  4. }
  5. // 获取cookie值
  6. function getCookie(cookieName) {
  7. // your implement
  8. }

解决方案

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

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

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

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

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

  1. //以下是封装好的三个cookie函数
  2. function setCookie(name,value,iDay){
  3. var oDate=new Date();
  4. oDate.setDate(oDate.getDate()+iDay);
  5. document.cookie=name+'='+value+';expires='+oDate;
  6. }
  7. function getCookie(name){
  8. // 对cookie字符串转化为一个数组,
  9. // 每个数组元素对应是一个单独的cookie
  10. var arr=document.cookie.split(';');
  11. for(var i=0;i<arr.length;i++){
  12. // 再对每个单独的cookie进一步细分,
  13. // arr2[0]代表名字,arr2[1]代表值
  14. var arr2=arr[i].split('=');
  15. if(arr2[0]==name){
  16. // 把这个cookie值传出去!
  17. return arr2[1];
  18. }
  19. }
  20. // 如果查找不到,返回空字符串
  21. return '';
  22. }
  23. function removeCookie(name){
  24. setCookie(name,'null',-1);
  25. }

第五章 Ajax

  • 掌握Ajax的实现方式

任务描述

学习Ajax,并尝试自己封装一个Ajax方法。实现如下方法:

  1. function ajax(url, options) {
  2. // your implement
  3. }
  4. // 使用示例:
  5. ajax(
  6. 'http://localhost:8080/server/ajaxtest',
  7. {
  8. data: {
  9. name: 'simon',
  10. password: '123456'
  11. },
  12. onsuccess: function (responseText, xhr) {
  13. console.log(responseText);
  14. }
  15. }
  16. );

options是一个对象,里面可以包括的参数为:

  • type: post或者get,可以有一个默认值
  • data: 发送的数据,为一个键值对象或者为一个用&连接的赋值字符串
  • onsuccess: 成功时的调用函数
  • onfail: 失败时的调用函数

解决方案

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

  1. if(window.XMLHttpRequest){
  2. oAjax=new XMLHttpRequest();
  3. }else{
  4. oAjax=new ActiveXObject("Microsoft.XMLHTTP");
  5. }

ajax的get请求基本过程如下

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

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

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

oAjax.readyState一共5个状态码:

  1. 0=>open方法尚未调用
  2. 1=>open已经调用
  3. 2=>接收到头信息
  4. 3=>接收到响应主体
  5. 4=>响应完成
  1. $d.prototype.ajax=function(url,json){
  2. var content=json.content?json.content:null;
  3. var type=json.type;
  4. var fnSucc=json.success;
  5. var fnFaild=json.faild;
  6. var oAjax=null;
  7. if(window.XMLHttpRequest){
  8. oAjax=new XMLHttpRequest();
  9. }else{
  10. oAjax=new ActiveXObject("Microsoft.XMLHTTP");
  11. }
  12. if(type.toUpperCase()=='GET'){
  13. oAjax.open('GET',url,true);
  14. oAjax.send();
  15. }else if(type.toUpperCase()=='POST'){
  16. oAjax.setRequestHeader('Content-Type','application/json');
  17. oAjax.open('POST',url,true);
  18. oAjax.send(content);
  19. }
  20. oAjax.onreadystatechange=function(){
  21. if(oAjax.readyState==4){
  22. if(oAjax.status==200){
  23. fnSucc(oAjax.responseText);
  24. }else{
  25. if(fnFaild){
  26. fnFaild(oAjax.status);
  27. }
  28. }
  29. }
  30. };
  31. };
  32. /* 使用示例
  33. ajax('json.json',{
  34. type:"POST",
  35. content:{
  36. name:"dangjingtao",
  37. password:"123"
  38. },
  39. success:function(res){
  40. alert(res);
  41. },
  42. faild:{
  43. alert('出错!');
  44. }
  45. });
  46. */

第六章 js库的完善

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

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

$d参数放什么

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

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

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

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

  1. function $d(selector oParent){
  2. switch(typeof selector){
  3. case:'function':
  4. //执行addEvent方法,主体对象是window,事件是load
  5. break;
  6. case:'object':
  7. //直接把该对象放到this.obj里面
  8. break;
  9. case:'string':
  10. //执行选择器操作
  11. break;
  12. }
  13. }

群组选择器改进

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

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

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

方法支持群组选择器

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

  1. //添加删除css类
  2. $d.prototype.addClass=function(newClassName){
  3. each(this.objs,function(item,index){
  4. item.classList.add(newClassName);
  5. });
  6. };
  7. $d.prototype.removeClass=function(oldClassName){
  8. each(this.objs,function(item,index){
  9. item.classList.remove(oldClassName);
  10. });
  11. };

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


第七章 综合练习

7.1 兴趣爱好列表

任务描述

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

第一阶段

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

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

第二阶段

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

当点击按钮时的行为同上

第三阶段

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

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

解决思路

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

  1. <textarea id="text"></textarea>
  2. <button type="button" id="btn" name="button">获取</button>
  3. <span id="validate></span>
  4. <ol id="list">
  5. </ol>
  • 第一阶段:用split转化为一个数组。之前的js库中,已经有了数组去重方法uniqArray(arr)。(参见第一章第三节)正好拿出来用。

    1. function Hobby(textId,btnId,showerId){
    2. this.id={
    3. textId:textId,
    4. btnId:btnId,
    5. showerId:showerId
    6. };
    7. }
    8. Hobby.prototype.getHobby=function(){
    9. var _this=this;
    10. $(_this.id.btnId).click(function(){
    11. _this.text=$(_this.id.textId).obj.value;
    12. _this.hobbyList=_this.text.split(',');
    13. _this.newHobbyList=uniqArray(_this.hobbyList).filter(function(item){
    14. return item!=='';
    15. });
    16. var content='';
    17. _this.newHobbyList.forEach(function(item,index){
    18. content+='<li>'+item+'</li>';
    19. });
    20. $(_this.id.showerId).obj.innerHTML=content;
    21. });
    22. };

    window.onload=function(){

    var hobby=new Hobby('#text','#btn','#list');

    hobby.getHobby();

    };


  • 第二阶段:添加规则

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

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

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

  • 第三阶段:表单验证

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

最后的代码是

  1. function Hobby(textId,btnId,showerId){
  2. this.id={
  3. textId:textId,
  4. btnId:btnId,
  5. showerId:showerId
  6. };
  7. //换行、空格(全角/半角)、逗号(全角/半角)、顿号、分号
  8. this.re=/\n|\s|\,|,|、|;|;/g;
  9. this.check=false;
  10. }
  11. Hobby.prototype.getHobby=function(){
  12. var _this=this;
  13. $(_this.id.btnId).click(function(){
  14. var content='';
  15. //点击表单校验
  16. if(!this.check){
  17. return false;
  18. }else{
  19. _this.newHobbyList.forEach(function(item,index){
  20. content+='<li>'+item+'</li>';
  21. });
  22. }
  23. $(_this.id.showerId).obj.innerHTML=content;
  24. });
  25. };
  26. Hobby.prototype.validate=function(validateId){
  27. this.id.validateId=validateId;
  28. var _this=this;
  29. //通过keyUp获取实时数据
  30. $(this.id.textId).on('keyup',function(){
  31. console.log(_this);
  32. _this.text=$(_this.id.textId).obj.value;
  33. _this.hobbyList=_this.text.replace(_this.re,',').split(',');
  34. _this.newHobbyList=uniqArray(_this.hobbyList).filter(function(item){
  35. return item!=='';
  36. });
  37. var tips='';
  38. //表单校验
  39. if(_this.newHobbyList.length>10||_this.newHobbyList.length===0){
  40. tips='不合法的数据!';
  41. $(_this.id.validateId).obj.style.color='red';
  42. _this.check=false;
  43. }else{
  44. tips='';
  45. _this.check=true;
  46. }
  47. $(_this.id.validateId).obj.innerText=tips;
  48. });
  49. };
  50. window.onload=function(){
  51. var hobby=new Hobby('#text','#btn','#list');
  52. hobby.getHobby();
  53. hobby.validate('#validate');
  54. };

7.2 倒计时

任务描述

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

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

解决思路

先把html写出来吧!

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

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

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

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

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

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

这样就算出来了。

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

  1. function Countdown(futrue){
  2. this.now=new Date();
  3. var timeList=futrue.split('-');
  4. this.futrue={
  5. year:timeList[0],
  6. month:timeList[1],
  7. date:timeList[2]
  8. };
  9. }
  10. Countdown.prototype.getFutrue=function(){
  11. $('#futrue').obj.innerText=this.futrue.year+'年'+this.futrue.month+'月'+this.futrue.date+'日';
  12. };
  13. Countdown.prototype.getCount=function(){
  14. var countDown=this.futrue.computedFutrue-this.now;
  15. if(countDown<0){
  16. $('#day').obj.innerHTML=0;
  17. $('#hours').obj.innerHTML=0;
  18. $('#min').obj.innerHTML=0;
  19. $('#sec').obj.innerHTML=0;
  20. return false;
  21. }
  22. this.countDown={
  23. day:Math.floor(countDown/1000/60/60/24),
  24. hr:Math.floor(countDown/1000/60/60)%24,
  25. min:Math.floor(countDown/1000/60)%60,
  26. sec:Math.floor(countDown/1000)%60
  27. };
  28. $('#day').obj.innerHTML = this.countDown.day;
  29. $('#hours').obj.innerHTML = this.countDown.hr;
  30. $('#min').obj.innerHTML = this.countDown.min;
  31. $('#sec').obj.innerHTML = this.countDown.sec;
  32. };
  33. window.onload=function(){
  34. $('#get').click(function(){
  35. clearInterval(this.timer);
  36. var futrue=$('#text').obj.value;
  37. this.timer=setInterval(function(){
  38. var countdown=new Countdown(futrue);
  39. countdown.getFutrue();
  40. countdown.getCount();
  41. },1000);
  42. });
  43. };

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

7.3 轮播图

任务描述

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

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

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

解决思路

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

首先需要明确需求:

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

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

  1. function getStyle(obj,attr){
  2. if(obj.crrentStyle){
  3. return obj.currentStyle[attr];
  4. //兼容IE8以下
  5. }else{
  6. return getComputedStyle(obj,false)[attr];
  7. //参数false已废。照用就好
  8. }
  9. }
  10. $d.prototype.move=function(obj,json,fn){
  11. var obj=this.obj;
  12. //清理定时器
  13. if(obj.timer){
  14. clearInterval(obj.timer);
  15. }
  16. obj.timer=setInterval(function(){
  17. var bStop=false;//如果为false就停了定时器!
  18. var iCur=0;
  19. // 处理属性值
  20. for(var attr in json){
  21. if(attr=='opacity'){
  22. iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
  23. }else{
  24. iCur=parseInt(getStyle(obj,attr));
  25. }
  26. //定义速度值
  27. var iSpeed=(json[attr]-iCur)/8;
  28. iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);
  29. //检测停止:如果我发现某个值不等于目标点bStop就不能为true。
  30. if(iCur!==json[attr]){
  31. bStop=false;
  32. }
  33. if(attr=='opacity'){
  34. obj.style[attr]=(iCur+iSpeed)/100;
  35. obj.style.filter='alpha(opacity:'+(iCur+iSpeed)+')';
  36. }else{
  37. obj.style[attr]=iCur+iSpeed+'px';
  38. }
  39. }
  40. //检测是否停止,是的话关掉定时器
  41. if(bStop===true){
  42. if(iCur==json[attr]){
  43. clearInterval(obj.timer);
  44. if(fn){
  45. fn();
  46. }
  47. }
  48. }
  49. },30);
  50. }
index方法

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

  1. $d.prototype.index=function(){
  2. var obj=this.obj;
  3. var aBrother=obj.parentNode.children;
  4. var i=0;
  5. for(i=0;i<aBrother.length;i++){
  6. if(aBrother[i]==obj){
  7. return i;
  8. }
  9. }
  10. };
eq方法

然后来写这个eq方法

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

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

  1. *{
  2. margin:0;
  3. padding:0;
  4. }
  5. ul li{
  6. list-style: none;
  7. }
  8. #tab{
  9. width: 400px;
  10. height: 300px;
  11. margin:200px auto;
  12. position: relative;
  13. }
  14. #list{
  15. width: 400px;
  16. height: 1204px;
  17. }
  18. #list li{
  19. height: 300px;
  20. }
  21. #list img{
  22. width: 400px;
  23. height: 300px
  24. }
  25. #btns{
  26. position: absolute;
  27. left: 40px;
  28. bottom:10px;
  29. z-index: 999;
  30. }
  31. #btns li {
  32. width: 30px;
  33. height: 30px;
  34. float: left;
  35. margin-left: 20px;
  36. border-radius: 50%;
  37. background: rgba(0, 0, 0, 0.5);
  38. cursor: pointer;
  39. }
  40. #btns .active{
  41. background: red;
  42. }
  43. #tab{
  44. position: relative;
  45. width: 400px;
  46. height: 300px;
  47. overflow: hidden;
  48. }
  49. #list{
  50. position: absolute;
  51. }

html结构

  1. <div id="tab">
  2. <ul id="btns">
  3. <li></li>
  4. <li></li>
  5. <li></li>
  6. <li></li>
  7. </ul>
  8. <div id="wrap">
  9. <ul id="list">
  10. <li class="imgs"><img src="images/1.jpg"></li>
  11. <li class="imgs"><img src="images/2.jpg"></li>
  12. <li class="imgs"><img src="images/3.jpg"></li>
  13. <li class="imgs"><img src="images/4.jpg"></li>
  14. </ul>
  15. </div>
  16. </div>

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

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

  1. $(function(){
  2. $('#btns').delegate('li','click',function(){
  3. $('#btns li').removeClass('active');
  4. $(this).addClass('active');
  5. var index=$(this).index();
  6. var height=parseInt(getStyle($('#list li').obj,"height"));
  7. $('#list').move({
  8. 'top':-height*index,
  9. });
  10. });
  11. });

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

  1. $(function(){
  2. function Tab(option){
  3. //console.log(option.bLoop)
  4. //设置顺序
  5. if(option&&option.order=='-'){
  6. this.order=1;
  7. this.iNow=3;
  8. this.start=3;
  9. this.end=0;
  10. }else{
  11. this.order=-1;
  12. this.iNow=0;
  13. this.start=0;
  14. this.end=3;
  15. }
  16. //设置延迟时间
  17. if(option&&option.delay){
  18. this.delay=option.delay;
  19. }else{
  20. this.delay=2000;
  21. }
  22. //循环设置
  23. if(option&&option.bLoop=='false'){
  24. this.bLoop=false;
  25. }else{
  26. this.bLoop=true;
  27. }
  28. this.timer=null;
  29. this.count=0;
  30. this.height=parseInt(getStyle($('#list li').obj,"height"));
  31. //页面初始化设置
  32. $('#btns li').eq(this.iNow).addClass('active');
  33. $('#list').obj.style.top=-this.height*this.iNow+'px';
  34. }
  35. Tab.prototype.tab=function(){
  36. var _this=this;
  37. $('#btns li').removeClass('active');
  38. $('#btns li').eq(_this.iNow).addClass('active');
  39. $('#list').move({
  40. 'top':-_this.height*_this.iNow,
  41. });
  42. };
  43. Tab.prototype.timerInner=function(){
  44. this.iNow-=this.order;
  45. if(this.iNow==this.end-this.order){
  46. this.iNow=this.start;
  47. this.tab();
  48. if(!this.bLoop){
  49. //不循环则停止定时器!
  50. clearInterval(this.timer);
  51. }
  52. }else{
  53. this.tab();
  54. }
  55. };
  56. Tab.prototype.move=function(){
  57. var _this=this;
  58. $('#btns').delegate('li','click',function(){
  59. _this.iNow=$(this).index();
  60. _this.tab();
  61. });
  62. _this.timer=setInterval(function(){
  63. return _this.timerInner();
  64. },_this.delay);
  65. $('#tab').on('mouseover',function(){
  66. clearInterval(_this.timer);
  67. });
  68. $('#tab').on('mouseout',function(){
  69. _this.timer=setInterval(function(){
  70. return _this.timerInner();
  71. },_this.delay);
  72. });
  73. };
  74. var _tab=new Tab({
  75. delay:1000,
  76. order:'-',
  77. bLoop:'false'
  78. });
  79. _tab.move();
  80. });

放个效果吧:

7.4 输入提示框

任务需求

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

要求如下:

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

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

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

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

示例:

解决思路

html结构:

  1. <input type="text" id="text">
  2. <div id="tips">
  3. <ul id=ul1></ul>
  4. </div>

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

基本框架应该是:

  1. $(function(){
  2. $().ajax('server.json',{
  3. type:"GET",
  4. faild:function(status){
  5. console.log(status);
  6. },
  7. success:function(data){
  8. //console.log(data);
  9. //主要内容
  10. }
  11. });
  12. });

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

  1. [
  2. {
  3. "id":1,
  4. "content":"阿姆斯特朗回旋加速喷气式阿姆斯特朗炮"
  5. },
  6. {
  7. "id":2,
  8. "content":"阿森纳"
  9. },
  10. {
  11. "id":3,
  12. "content":"阿斯顿维拉"
  13. },
  14. {
  15. "id":4,
  16. "content":"阿姆斯特丹"
  17. }
  18. ]

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

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

  1. success:function(data){
  2. //console.log(data);
  3. data=eval(data);
  4. //主要内容
  5. $('#text').on('keyup',function(){
  6. var value=this.value;
  7. var arr=[];
  8. var str='';
  9. data.forEach(function(item,index){
  10. if(value!==''&&item.content.indexOf(value)!==-1){
  11. str+='<li>'+item.content+'</li>';
  12. }
  13. });
  14. $('#ul1').obj.innerHTML=str;
  15. });
  16. }

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

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

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

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

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

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

  1. $(function(){
  2. $().ajax('server.json',{
  3. type:"GET",
  4. faild:function(status){
  5. console.log(status);
  6. },
  7. success:function(data){
  8. //console.log(data);
  9. data=eval(data);
  10. var index=0;
  11. var str='';
  12. //主要内容
  13. $('#text').on('keyup',function(ev){
  14. var e=ev||window.event;
  15. //console.log(e);
  16. var value=this.value;
  17. var arr=[];
  18. var newStr='';
  19. data.forEach(function(item,index){
  20. if(value!==''&&item.content.indexOf(value)!==-1){
  21. arr.push(item.content);
  22. newStr+='<li>'+item.content+'</li>';
  23. }
  24. });
  25. $('#ul1').obj.innerHTML=newStr;
  26. // 如果不同,就把index设置为0.
  27. if(str!==newStr){
  28. index=0;
  29. str=newStr;
  30. }
  31. // 先判断按下的键是什么,上下回车
  32. if(e.code=='ArrowUp'&&$('#ul1 li').eq(index)){
  33. index--;
  34. if(index<0){
  35. index=arr.length-1;
  36. }
  37. }
  38. if(e.code=='ArrowDown'&&$('#ul1 li').eq(index)){
  39. index++;
  40. if(index>arr.length-1){
  41. index=0;
  42. }
  43. }
  44. if(e.code=='Enter'&&$('#ul1 li').eq(index)){
  45. var selector=$('#ul li').eq(index).obj.innerText;
  46. this.value=selector;
  47. $('#ul1').obj.innerHTML='';
  48. return;
  49. }
  50. $('#ul1 li').eq(index).addClass('active');
  51. });
  52. }
  53. });
  54. });

放一个效果吧:

7.5 界面拖拽交互

任务需求

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

解决思路

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

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

其次是拖拽是个模糊位置

拖的逻辑

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

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

  1. $d.prototype.drag=function(){
  2. each(this.objs,function(item,index){
  3. drag(item);
  4. });
  5. function drag(oDiv){//拖拽函数
  6. oDiv.onmousedown=function (ev){
  7. var oEvent=ev||event;
  8. //鼠标位置减去偏移量是鼠标相对于html块级元素的位置
  9. var disX=oEvent.clientX-oDiv.offsetLeft;
  10. var disY=oEvent.clientY-oDiv.offsetTop;
  11. document.onmousemove=function (ev){
  12. var oEvent=ev||event;
  13. // 拖拽时,html实际位置就是鼠标拖拽的位置减去相对位置
  14. oDiv.style.left=oEvent.clientX-disX+'px';
  15. oDiv.style.top=oEvent.clientY-disY+'px';
  16. };
  17. document.onmouseup=function (){
  18. document.onmousemove=null;
  19. document.onmouseup=null;
  20. };
  21. };
  22. }
  23. };

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

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

  1. $d.prototype.drag=function(){
  2. each(this.objs,function(item,index){
  3. drag(item);
  4. });
  5. function drag(oDiv){//拖拽函数
  6. oDiv.onmousedown=function (ev){
  7. oDiv.style.position='absolute';
  8. var oEvent=ev||event;
  9. //鼠标位置减去偏移量是鼠标相对于html块级元素的位置
  10. var disX=oEvent.clientX-oDiv.offsetLeft;
  11. var disY=oEvent.clientY-oDiv.offsetTop;
  12. document.onmousemove=function (ev){
  13. var oEvent=ev||event;
  14. // 拖拽时,html实际位置就是鼠标拖拽的位置减去相对位置
  15. oDiv.style.left=oEvent.clientX-disX+'px';
  16. oDiv.style.top=oEvent.clientY-disY+'px';
  17. };
  18. document.onmouseup=function (){
  19. oDiv.style.position='static';
  20. oDiv.style.left='';
  21. oDiv.style.top='';
  22. document.onmousemove=null;
  23. document.onmouseup=null;
  24. };
  25. };
  26. }
  27. };
基本框架

先把结构写出来。

  1. <ul id="ul1">
  2. <li>阿姆斯特朗炮</li>
  3. <li>阿姆斯特丹</li>
  4. </ul>
  5. <ul id="ul2">
  6. <li>阿姆</li>
  7. <li>阿姆斯壮</li>
  8. </ul>

css样式

  1. *{
  2. margin:0;
  3. padding: 0;
  4. }
  5. ul li{
  6. list-style: none;
  7. font-size: 30px;
  8. text-align: center;
  9. color: #fff;
  10. }
  11. #ul1{
  12. position: relative;
  13. float: left;
  14. width: auto;
  15. height: 400px;
  16. border: 1px solid #ccc;
  17. }
  18. #ul1 li{
  19. margin-bottom: 2px;
  20. width: 200px;
  21. height: 50px;
  22. background: red;
  23. }
  24. #ul2{
  25. position: relative;
  26. float: left;
  27. margin-left: 200px;
  28. height: 400px;
  29. border: 1px solid #ccc;
  30. }
  31. #ul2 li{
  32. margin-bottom: 2px;
  33. width: 200px;
  34. height: 50px;
  35. background: blue;
  36. }

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

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

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

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

  1. document.onmouseup=function (ev){
  2. var oEvent=ev||window.event;
  3. var x=oEvent.clientX;
  4. var y=oEvent.clientY;
  5. var l=$(id).obj.offsetLeft;
  6. var r=parseInt(getStyle($(id).obj,'width'))+l;
  7. var t=$(id).obj.offsetTop;
  8. var b=parseInt(getStyle($(id).obj,'height'))+t;
  9. if(x>l&&x<r&&y>t&&y<b){
  10. console.log(1);
  11. }
  12. oDiv.style.position='static';
  13. oDiv.style.left='';
  14. oDiv.style.top='';
  15. document.onmousemove=null;
  16. document.onmouseup=null;
  17. };
DOM操作

DOM操作及其简单:

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

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

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

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

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

放一个效果吧:

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

# 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. TADDConnetion组件,TADOQuery

    一.TADDConnetion 二.TADOQuery 1.RecNo:从1开始 当前记录行数;ADOQuery1.RecNo 选择后一行数据集内容:ADOQuery1.RecNo:=ADOQuery ...

  2. IO流入门-第二章-FileOutputStream

    FileOutputStreamj基本用法和方法示例 /* java.io.OutputStream java.io.FileOutputStream 文件字节输出流 将计算机内存中的数据写入到硬盘文 ...

  3. for...in循环取Json数据

    var result = { "Tables":{ "B2B_DS_ORDERMX0":{ "ordernum":"tables- ...

  4. 百度 验证码识别API 使用

    先到百度云申请文字识别API ,会给你一个API KEY和一个SECRET KEY,免费,一天最多500次请求. try: temp_url = 'https://aip.baidubce.com/o ...

  5. Django HttpResponse对象详解

    HttpResponse对象 Django服务器接收到客户端发送过来的请求后,会将提交上来的这些数据封装成一个HttpRequest对象传给视图函数.那么视图函数在处理完相关的逻辑后,也需要返回一个响 ...

  6. 通信—HTTP 协议入门(转载)

    HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等). HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于199 ...

  7. Linux学习笔记—vim程序编辑器

    vi和vim vim是vi的升级版,支持vi的所有指令 vi的使用 vi分为三种模式:一般模式.编辑模式.命令行模式 一般模式 以vi打开一个文件就直接进入一般模式了,这个模式下可以使用上下左右按键来 ...

  8. python 多进程使用Queue通信的例子

    import time from multiprocessing import Process,Queue MSG_QUEUE = Queue(5) def startA(msgQueue): whi ...

  9. day3-set集合

    set是一个无序且不重复的元素集合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...

  10. 初学hadoop的个人历程

       在学习hadoop之前,我就明确了要致力于大数据行业,成为优秀的大数据研发工程师的目标,有了大目标之后要分几步走,然后每一步不断细分,采用大事化小的方法去学习hadoop.下面开始叙述我是如何初 ...