task0002(三)- BOM + AJAX

BOM

实现以下函数

判断是否为IE浏览器

  • 这里应该说一下,在从网上了解到的资料来看,在对于某个功能事件的时候不要去做浏览器检测,而应该做特性检测。这样更符合要求,且浏览器的UserAgent可人为修改
  • 需要实现的话,ie11的版本号中并没有msie:。只能通过rv:获得。所以需进行一些处理。但是该属性同样可以返回火狐浏览器的版本号- -。所以还需要多加一层判断。查找是否存在Trident\/7.0;
  • 使用正则表达式的match方法,详细见我写的正则表达式的博客。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 判断是否为IE浏览器,返回-1或者版本号
function isIE() {
var uUserAgent = navigator.userAgent; //保存浏览器的userAgent
var ieAgent = uUserAgent.match(/msie (\d+.\d+)/i);
if (ieAgent) {
return ieAgent[1];
} else {
if (uUserAgent.match(/Trident\/7.0;/i)) { //处理到ie11.
ieAgent = uUserAgent.match(/rv:(\d+.\d+)/i);
return ieAgent[1];
}
return -1; //不是ie浏览器。
}
}

Cookie相关

秒味js的免费课程,把cookie讲的很好。

什么是cokie

  • 页面用来保存信息

  • 比如:自动登入/保存用户名

  • cookie的特性

  • 同一个网站中所有页面共享一套cookie

  • 数量/大小有限

  • 过期时间

  • JS中使用cookie

    • document.cookie

设置cookie

1
2
3
4
5
6
7
8
9
10
11
/**
* 设置cookie
* @param {String} cookieName 设置cookie名
* @param {String} cookieValue 对对应的cookie名
* @param {Number} expiredays 过期的时间(多少天后)
*/
function setCookie(cookieName, cookieValue, expiredays) {
var oDate = new Date();
oDate.setDate(oDate.getDate() + expiredays);
document.cookie = cookieName + "=" + cookieValue + ";expires=" + oDate;
}

获取cookie值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 /**
* 获取cookie
* @param {String} cookieName 待寻找的cookie名
* @returns {String} 返回寻找到的cookie值,无时为空
*/
function getCookie(cookieName) {
var arr = document.cookie.split("; ");
for (var i = 0; i < arr.length; i++) {
var arr2 = arr[i].split("=");
if (arr2[0] == cookieName) {
return arr2[1];
}
}
return "";
}

删除cookies

1
2
3
4
5
6
7
/**
* 删除cookie
* @param {String} cookieName 待删除的cookie名
*/
function removeCookie(cookieName) {
setCookie(cookieName, "1", -1)
}

扩展阅读:

  • 共同点
    都是保存在浏览器端,且同源的。都是键值对存储。
  • 区别
特性 Cookie localStorage sessionStorage
数据的声明周期 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或关闭浏览器后被清除
存放数据大小 4K左右 一般为5MB 同左(话说markdown不支持跨列表格,只能用html写,懒得麻烦了-_-)
与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信 同左
易用性 需要程序员自己封装,原生的Cookie接口不友好 原生接口可以接受,亦可再次封装来对Object和Array有更好的支持 同左
  • 安全性
    cookie 中最好不要放置任何明文的东西。而且特别需要注意敏感数据的存放。不是什么数据都适合放在 Cookie、localStorage 和 sessionStorage中的。使用它们的时候,需要时刻注意是否有代码存在 XSS 注入的风险。
  • 参考:详说 Cookie, LocalStorage 与 SessionStorage

Ajax

任务描述

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
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: 失败时的调用函数

实现如下:

专门用两篇博客来记录了一下AJAX的学习过程,以及该函数的完成过程
Ajax入门(一)从0开始到一次成功的GET请求
Ajax入门(二)Ajax函数封装

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* AJAX函数封装
* @param {string} url 请求地址(必须)
* @param {object} options 发送请求的选项参数
* @config {string} [options.type] 请求发送的类型。默认为GET。
* @config {Object} [options.data] 需要发送的数据。
* @config {Function} [options.onsuccess] 请求成功时触发,function(oAjax.responseText, oAjax)。(必须)
* @config {Function} [options.onfail] 请求失败时触发,function(oAjax)。(oAJax为XMLHttpRequest对象)
*
*@returns {XMLHttpRequest} 发送请求的XMLHttpRequest对象
*/
function ajax(url, options) {
//1.创建ajax对象
var oAjax = null;
/**
* 此处必须需要使用window.的方式,表示为window对象的一个属性.不存在时值为undefined,进入else
* 若直接使用XMLHttpRequest,在不支持的情况下会报错
**/
if (window.XMLHttpRequest) {
//IE6以上
oAjax = new XMLHttpRequest();
} else {
oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}

//2.连接服务器
//open(方法,url,是否异步)
var param = ""; //请求参数。
//只有data存在,且为对象使才执行
var data = options.data ? options.data : -1; //缓存data
if (typeof (data) === "object") {
for (var key in data) { //请求参数拼接
if (data.hasOwnProperty(key)) {
param += key + "=" + data[key] + "&";
}
}
param.replace(/&$/, "");
} else {
param = "timestamp=" + new Date().getTime();
}

//3.发送请求
var type = options.type ? options.type.toUpperCase() : "GET";
if (type === "GET") {
oAjax.open("GET", url + "?" + param, true);
oAjax.send();
} else {
oAjax.open("POST", url, true);
oAjax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
oAjax.send(param);
}

//4.接收返回
//OnRedayStateChange事件
oAjax.onreadystatechange = function () {
if (oAjax.readyState === 4) {
if (oAjax.status === 200) {
//请求成功。形参为获取到的字符串形式的响应数据
options.onsuccess(oAjax.responseText, oAjax);
} else {
//先判断是否存在请求失败函数
//存在时,形参为XMLHttpRequest对象,便于进行错误进行处理
if (options.onfail) {
options.onfail(oAjax);
}
}
}
};
return oAjax;//发送请求的XMLHttpRequest对象
}

《老子是癞蛤蟆》

为什么我会喜欢《老子是癞蛤蟆》这本书?

说实话这本书有很多三观不正的地方,比如男主的后宫群,里面描述的各种关系等等。。。毕竟一本YY小说嘛,但是作为一个有思想的人,读书不都是应该取其精华的吗?

我想说的是:敢于吃天鹅肉的才是好癞蛤蟆

  • 一个隐富二代的奋斗史。(比你有钱的人,比你还努力,你在干嘛?)
  • 一个男人的成长史。(人嘛,从来都不是一开始就天下无敌的。随着各种各样的经历,看的书越来越多,才能慢慢成熟)
  • 对于感情的极致描写。(宠溺孙子到了极点的奶奶,胡璃之死,张乌梅的日记)很多事,可能家里确实做的不对,但是相信家人总是想着为你好,试着多一点理解。
  • 对于努力的极致描写。(袁树,沈大元帅,项如意)请多努力一点,吃苦一点,总会有苦尽甘来的一天。
  • 运动(早晚各10圈,一个好的身体才能支持你想要完成的事情。)
  • 偏执(假如说别人对于赵甲第的期望值是100分的话,那么他这个偏执狂就自己在往120分上努力。疯魔状态)
  • 忠诚(韩道德:“韩道德,你就算一条狗,人若以国士待我,我必以国士报之”)
  • 读书(笔记达百万字?四年512本书?国考废纸卖141块?)
  • 专注(工行模型,闭关)
  • 浪子回头。(不跳的马小跳)
  • 做人。
  • …… 太多太多,每一次看,都能从里面学到自己应该要去改变的地方。

下面摘抄自《老子是癞蛤蟆》的段落(侵删)

开学典礼

结果看到一个像门卫的老头踏着一双布鞋自顾自拿了一个话筒,就走到演讲台,没有发言稿,没有主持人预报,咳嗽了两声,不温不火道:“各位同仁,各位同学,我今天不想代表谁发言,只是以一个已经在本校扎根足足50年、并即将离开这所学校的老人这么个身份,跟2000多名新生说几句话。”

  老人嗓音并不大,但清晰传到体育馆内每一个人耳朵里去。

  台下一下子议论纷纷,都在窃窃私语。

  老人一只手拿话筒,另一只手依然背负身后,厚重的老学究眼镜,踩着一双廉价橡胶底布鞋,一身土老帽的装扮,不理睬台下的喧闹,继续道:“我们身处的学区有个不太准确的叫法,杨浦大学城,这块土地上有复旦,有同济,有二军大,还有财大,还有呢?我不太记得住了,相信你们也一定不太清楚,这就是说,如果有人问起我们这所学校,他好不容易听清楚你的解释后,会恍然大悟,哦,就是在同济和二军大边上那所大学啊。或者等有一天你去上海市区逛街,等不到公交车,坐出租车来杨浦大学城,司机一定也一样不知道这么个地儿,所以你还得说,师傅,你干脆先把我送到同济大学吧。”

  全场哄然大笑。

  老校长也笑了,只是原先谈笑风生的主席台却鸦雀无声,一个个噤若寒蝉。

老人轻轻摘下眼镜,用衬衫擦了擦,戴上后继续说道:“所以坦白说,这不是一所能让你说出去就可以赢得喝彩和羡慕的大学,我不知道2000多名学子中有多少是得意洋洋而来,觉得已经九九八十一难过后,可以逍遥快活了,也不知道又有多少是垂头丧气而来,只是混个文凭,然后就走上社会,给复旦学子北大清华学子们打杂做下手,跑跑腿端茶送水之类的。

对此,我这个20岁那年就进入本校,然后就没有再离开的老头子还是想说点心里话,人的一生只有一个终点,却有很多个起点,从娘胎出生起是第一个大起点,这个谁都无法更改,接下来小学升初中,初中升高中,又是两个新起点,然后很多孩子就把高中升大学提前看作人生的终点了,这都是一种不负责任,18岁以后,你可以不必对你父母负责,但起码你得学会开始对自己负责。

我记得有这么一句话,是你们某位学长一次醉酒后跟我说的:就是被人踩得像一滩烂泥,也要捏出狗尾巴花来。

如果我没有记错,他来自一个贫困县,每年学费都是欠着,然后都是靠他在学校拿奖学金和两个假期各做四份杂工和家教一块钱一块钱攒出来的,到毕业那天他跟我说这句话,那顿酒还是我付的钱。现在,这个大学四年期间从图书馆破本校纪录借了512本书的家伙,可能再过十来年等他走出国家发改委,再来上海,就是我的领导了。”

  这一次笑声已经稀疏很多。

  老校长笑了笑,环视台下2000多张稚嫩面孔,道:“我不要求你们跟那位学长一样每年借一百多本书,我觉得一年大概30多本就差不多了,当然必须是教材之外的书籍,说实话,大学拼命要你们读的书,反而是不太有用的东西,你们自己愿意去阅读去咀嚼去反思的作品,才是影响你们一生的精神财富。

我一个普普通通的农民子弟,20岁艰辛考上大学,26岁开始做老师,教书育人到今日,就只证明了一件事情,哪怕是一只癞蛤蟆,能够几十年如一日地充实自己,迟早都有跳出池塘吃上天鹅肉的一天。
这个天鹅肉可以是桃李满天下,可以是抱得美人归,也可以是功成名就光耀门楣,还可以是做一名伟大的金融家,我问心无愧了,没有遗憾了,也一直在等你们自认没有对自己愧疚的那一天。

也许听到这里,很多同学会问,凭什么你这么所不起眼的学校就要求我们奋发图强,是啊,这所学校既没有中国大学泛滥成灾的大楼,也没有几位中国学府集体漠视很多年的大师,凭什么?”

  老校长停顿了一下,指了指自己,又指了指台下全场,“凭自己。

  全场沉默。

  老人恢复轻松笑脸道:“好了,耽误大家那么多听歌看报纸的时间,很抱歉。散会。”

毕业典礼。

  老校长没有做隆重的毕业致辞,仅是做了个简短并且破格的发言,几乎可以算作一场个人表彰。

  “我们学校还是那个不怎么出名的学校,师资一般,就业率一般,藏书量一般,什么都很平常,还是比不上临近那些个财大气粗的复旦啊同济啊之类的名牌学。

所以我如果要说让你们这帮在这里呆了四年可能只有失望的孩子认可‘我以学校为荣,学校以你为荣’,没几个人当回事,所以我在这里,不奢望什么,只希望以后听到有人骂这所学校的时候,出来说一句我们学校是不咋样但你们外人一边凉快去。

可能有些学生已经知道,你们当中有人在去年国考拿下了170分,别说你们不信,我都不信,我还专门打电话去上海教委那边确认,说没错,是170分。那边一个我的学生都好几年没跟我拜年了,正月里特意跑来跟我道贺,说自己母校很不错,听到这话,我是高兴又不高兴。其中滋味,就不细说了。

我要说的是这个考了170分的学生,名字不去提,其实教学楼那边很多老师都认识,尤其是跟我办公室一层那些个官帽没我大肚子倒是有我两个大的领导们,肯定记得,因为去年8月9月的时候,这个学生就窝在同一层的小房间,做一个连经济学者都觉得太复杂看不懂的模型,一做就是两个月,后来,我们这些闲着没事的校领导,当然也包括我,就得出一个挺有意思的结论:那穿着很熏人衣服一脸胡茬的年轻孩子如果是空手冲向洗手间,那肯定是上小号,如果说带上了纸笔,不用猜,是上大号了。

你们谁有兴趣,可以把工行10年的社会责任模型翻出来研究一下,就是这个学生做的。但哪怕是这样,我还是不信他能考出170,我要去考,这个分数,除以2,还差不多嘛,所以我很纳闷,凭什么你一个二本大学生考得出来?

我就把他喊到办公室,问他怎么考出来的,这小子估摸着是在我办公室蹭吃蹭喝习惯了,随便说了一句看书做题来应付我,我当然不满意,说你小子不说出个子丑寅卯,毕业生扣下。

结果他想了想,很认真对我说开始冲刺国考的7个月,考完以后把真题集、做过的行测试卷、申论材料这些乱七八糟全当废纸卖了,讨价还价以后一斤卖7毛钱,他卖了140块9毛钱,收废纸的给了他141块整,我问他这些钱能不能请我吃顿饭?

这小子笑着说说报名费花去96块,还剩下47块。后来我和他找了个附近的大排挡,这段饭,是我这十几年来吃得最舒坦的一顿饭,因为我之前总觉得做了三十来年的狗屁校长,还不如最早的二十多年普通教师来得有用,吃完饭的时候,我对这个学生说。

“你不用以学校为荣,但学校以你为荣。”

  全场寂静。

老校长顿了一下,依然是不急不缓的特色语调:“记得四年前开学典礼上跟耽误你们看报纸听歌的时候,有说到凭什么要你们奋发图强,答案是凭你们自己

今天,我再唠叨一句,希望哪一天你们参加这所学校几几年校庆的时候,可以对新校长理直气壮说一句‘学校要以我为荣’。

好了,毕业!”

赵甲第回校演讲

当一身休闲装的他站在讲台上,看着大教室黑压压一片的脑袋脸孔,有点骑虎难下,尤其是还有老校长亲自坐镇,大半校领导都坐在最后一排,这待遇,罕见。赵甲第深呼吸了几下,挤出一个笑脸,开门见山自嘲道:

“这次老校长突然跟我说要开个讲座,一开始打死不答应,可架不住老校长威胁,只能硬着头皮来,到学校门口的时候都没想好该讲什么,光顾着紧张了。老校长说让我随便说,我就真的随便说了。”

  响起一些笑声。

  “不管你们信不信,我也仇富,也当过愤青。当然更打过架,不过胜多输少,高中挨过不少的警告处分,写过情书,但不怎么能收到情书,因为高中打篮球总是被盖帽给白马王子们找信心,踢足球总是跑最多触球最少的那个家伙,也会嫉妒那些长得比我帅的,羡慕口才比我好的,崇拜那些敢在教室跟女朋友亲嘴的。

说这些,就是想说我的高中跟你们的没两样。考进这所学校,说实话没什么感觉,唯一记忆深刻的两件事,一件就是悲愤我们学校漂亮学姐太少。”

  台下笑声明显多了点。

  赵甲第笑了笑,一口洁白的牙齿,是很阳光向上,“第二件事,就是老校长在开学典礼上的那些话,但那会儿只是觉得这学校破破烂烂,跟复旦同济一比就跟丫鬟见着小姐一样,寒碜。但好歹有个不太一样的校长,总算没太大失望,大学四年,那么多日子总得过不是?
  
  除了中途休学一年,三年里跟在座的学弟学妹一样为了生存去学校食堂,价格死贵,东西死难吃,吃来吃去吃了一肚子的腹诽,不知道现在食堂伙食咋样了。”

  台下开始互动:“一个德性!”
  赵甲第望向后排玩笑道:“老校长,给改善改善?”
  老校长呵呵笑道:“我亲手抓一抓好了,总不能让你白来。”

  一阵雀跃欢呼。

  赵甲第继续:“因为要打着听讲座的旗号去各个学校踩点,我买了两辆坐骑,第一辆自行车挂了两把锁都给偷了,真佩服那哥们的本事。三年下来把杨浦校区碾压了无数圈,觉得有些个复旦妹子确实挺耐看,我们学校,呃,只能说还好还好。”
  一个被室友拉着来听讲座的漂亮mm举手道:“抗议。”
  赵甲第笑道:“我应该晚生两年。”
  哄堂大笑。

  赵甲第犹豫了一下,笑容灿烂道:“现在想起来,真的挺怀念大学,跟室友一起翘课睡觉,一起去网吧通宵游戏,不过如果有美女上课,我总是不太舍得逃课。跟班上牲口些好不容易蜗牛速度下载来的爱情动作片,还会故意把音箱开到最大,对,就是29栋,被很多女生向校方举报的那个地方。”

  一哥们嚷道:“学长,我就是29栋的!”
  赵甲第打趣道:“那这个优良传统就靠你们发扬光大了。”
  女生们会心一笑,没有丝毫反感。
  这个学长,真是不太一样啊。

  赵甲第似乎渐入佳境,不再有任何拘谨,轻松道:“刚才到门口看到这么多人,尤其是连走廊都有,我这手都在抖,现在好点了。
说了这么多随便的,那就稍微说点不那么随便的。都是自己的心得,可能片面,你们拣爱听的就行。

一个曾经让我当作超越目标的男人说过,他之所以成功,是因为他不读书,那些本来可以成功的人,都被钉死在书本上了。

一开始我觉得这话说得偏激而霸气,很带劲,然后我就琢磨,我自己是属于读书还是不读书的?答案挺二百五的,还算是认真读书的老实孩子,小学天天被数学老师打板子,跟我唠叨越是好料越不能长歪,打着打着,就养成了越级做题的习惯,而这位老师教我最重要的东西,不是拿满分如何重要,而是如何去拿满分,读武侠小说的同学都知道‘天下武功唯快不破’‘万佛朝宗’这些调调,其实放在读书上,也是可以套用的。

兜了一圈,读书到底有没有用?这个命题太大,我还没盖棺的资本,不敢瞎扯,但就我个人而言,读书有用没用,得看我们是读死书,还是读书读出世事洞明和人情练达,读死书,大概就是那个所谓的钉死在书本上了,读活了,哪怕成绩一般,我想到了社会,未必会比那些个高考状元差。

读书无用论不可取,但存在即合理,当年暴发户嘲笑书生无用,其实有一半是对的,因为他们是社会这个大染缸里的高材生,错的一半不需要我多说,现在的社会趋势已经说明一切,要不中南海会有清华帮聚会?会有工科当政的说法?以后如果工科不再当政,那也肯定是风水轮流到了文科,怎么都轮不到天天上网玩游戏的,是吧?

越是成熟的社会,就越讲究积淀,放在各个行业也是如此,草莽龙蛇的年代一过,注定越来越靠扎实底蕴。

读书好坏不在成绩,但话说回来,你连那些套教科书都玩不过,反而被玩死,就眼巴巴等着踏入社会大杀四方?不读书,或者说拼都不肯拼不敢拼一下,都成骄傲的资本了?天天嚷着富二代可恨该杀没用,人家照样吃香喝辣,还不如多想想富二代这么惬意,富一代是如何挣扎上位的?有人就说了官二代官三代太子党们又怎么讲,呵,问这个问题的,肯定是历史没学好,一部《解放战争》足以说明一切。”

  “扯远了,难怪我作文一直没能拿过高分,议论文还好,散文完全是形散神更散。我想证明读书有用,现在没资格,希望以后有机会能证明,输了,正好当反面典型。接下来是不是该问答环节了?刚才在报刊亭那边买报纸,看到满摊子都是我的照片,你们多半是被这些骗进来的,我尽量做到有问必答。”

  场面立即火爆起来。

  问题一个个抛出来,不少都极为尖锐,赵甲第还真做到了知无不言言无不尽。

  “学长,听说你精通围棋,那篇《他是谁》提到你在弈城有注册,我是业余里的业余,但在弈城也泡了好几年,就想问你是不是那个强九段‘国士无双’。”
  “是。”
  那位兄弟立马崇拜得五体投地,恨不得当场拜师学艺了。
  “你已经进入发改委了,还会继承家族企业吗?”
  “不出意外,会。”
  满堂震惊。

  “学长,我矮穷龊,一见到美女就脸红,能不能传授点经验?”

  “舍得一身剁能把皇帝拉下马这是最逆天的屁民,这个我们不敢想,但舍得一身剐能把女神扛上床,这才是最凶残的癞蛤蟆。我一直坚信这点,脸皮厚点肯定没错,但前提是能保证自己给她幸福。”

Ajax入门(二)Ajax函数封装

如果看了的我上一篇博客《Ajax入门(一)从0开始到一次成功的GET请求》的话,肯定知道我们已经完成了一个简单的get请求函数了。如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 一个简单的get请求
* @param {String} url 请求地址,文件名
* @param {Function} fnSucc 请求成功时执行的函数
* @param {Function} fnFaild 请求失败执行的函数
*/
function AJAX(url, fnSucc, fnFaild) {
//1.创建ajax对象
var oAjax = null;
/**
* 此处必须需要使用window.的方式,表示为window对象的一个属性.不存在时值为undefined.
* 进入else若直接使用XMLHttpRequest在不支持的情况下会报错
**/
if (window.XMLHttpRequest) {
//IE6以上
oAjax = new XMLHttpRequest();
} else {
oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}
//2.连接服务器
//open(方法,url,是否异步)
oAjax.open("GET", url, true);

//3.发送请求
oAjax.send();

//4.接收返回
//OnRedayStateChange事件
oAjax.onreadystatechange = function () {
if (oAjax.readyState === 4) {
if (oAjax.status === 200) {
// alert("成功" + oAjax.responseText);
fnSucc(oAjax.responseText);
} else {
// alert("服务器响应失败!");
if (fnFaild) {
fnFaild();
}
}
}
};
}

为什么要继续进行Ajax函数封装?
原因如下:

  1. 目前方法只能使用get请求,而不能使用post请求,而在用户注册时必须使用POST,因为POST,现在不够完整。
  2. 目前请求参数只能直接写在url里,不利于动态获取数据,应该使用参数解析的方式,便于使用。
  3. get请求方式请求缓存问题。
  4. 学习封装方法,

改造目标

1
2
3
function ajax(url, options) {
// your implement
}

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

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

改造开始(三步)

(一)原函数的改造

形参中,删除fnSuccfnFaild 添加options。使成功与失败执行的函数变成options对象的 onsuccessonfail两个方法对应的值。
主要是在4、接收返回部分进行更改,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//4.接收返回
oAjax.onreadystatechange = function () {
if (oAjax.readyState === 4) {
if (oAjax.status === 200) {
//请求成功。形参为获取到的字符串形式的响应数据
options.onsuccess(oAjax.responseText);
} else {
//先判断是否存在请求失败函数
//存在时,形参为XMLHttpRequest对象,便于进行错误进行处理
if (options.onfail) {
options.onfail(oAjax);
}
}
}
};

(二)请求参数的处理

首先我们要知道的是在使用请求参数存在时,GET方式的请求参数特别简单。直接在url后面添加?参数名=参数值&参数名二=参数值二

实现思路:

  1. 首先判断options.data是否存在,不存在时使用"?timestamp= + new Date().getTime();链接在url后,以清除缓存。
  • 这里只是我使用的方法,这里的timestamp可以随意更改
  • new Date().getTime();也可以用Math.random();主要是保持每次请求的url都不一样。
  • 还有许多别的方法参考Ajax缓存问题怎么解决?。有兴趣的自己再多google一下吧。
  1. 存在options.data时,应该限制请求data格式便于处理,设定为JSON(当然没必要像JSON那么严格,但是应该保持键值对的格式)。
  2. 使用for in 遍历data,使用=来连接键与值,使用&来连接多个请求参数
  3. 只需要对原函数中的2.连接服务器进行更改

实现如下:

原:

1
2
3
//2.连接服务器
//open(方法,url,是否异步)
oAjax.open("GET", url, true);

现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//open(方法,url,是否异步)
var param = "";//请求参数。
//判断data存在时缓存它,不存在时,设为0
var data = options.data ? options.data : 0;
if(typeof(data)==="object"){//只有data为对象使才执行
for (var key in data){//请求参数拼接
if (data.hasOwnProperty(key)) {
param += key+"="+data[key]+"&";
}
}
param.replace(/&$/,"");//去除结尾的&。
}else{
param= "timestamp=" + new Date().getTime();
}
//2.连接服务器
oAjax.open("GET", url+"?"+param, true);

(三)请求类型选择

使用post发送数据,模拟form提交。在url看不到请求参数,更加安全。

实现思路:

  1. 判断是否type是否存在,存在时转为大写,默认为GET请求。
  2. 判断请求的类型,GET 或 POST 。
  3. 在使用post请求提交数据时,请求参数不跟在url后面。
  4. 使用post请求数据必须添加在open()send()直接添加头信息。
  • xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  1. 使用post请求数据,对2.连接服务器、3.发送请求部分进行处理

实现如下:

原:

1
2
//2.连接服务器
oAjax.open("GET", url+"?"+param, true);

1
2
3
4
5
6
7
8
9
10
//3.发送请求
var type = options.type ? options.type.toUpperCase() : "GET" ;
if(type ==="GET"){
oAjax.open("GET", url+"?"+param, true);
oAjax.send();
}else{
oAjax.open("POST", url, true);
oAjax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
oAjax.send(param);
}

最终完成

结合之前写的,集合起来。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* AJAX函数封装
* @param {string} url 请求地址(必须)
* @param {object} options 发送请求的选项参数
* @config {string} [options.type] 请求发送的类型。默认为GET。
* @config {Object} [options.data] 需要发送的数据。
* @config {Function} [options.onsuccess] 请求成功时触发,function(oAjax.responseText, oAjax)。(必须)
* @config {Function} [options.onfail] 请求失败时触发,function(oAjax)。(oAJax为XMLHttpRequest对象)
*
*@returns {XMLHttpRequest} 发送请求的XMLHttpRequest对象
*/
function AJAX(url, options) {
//1.创建ajax对象
var oAjax = null;
/**
* 此处必须需要使用window.的方式,表示为window对象的一个属性.不存在时值为undefined,进入else
* 若直接使用XMLHttpRequest,在不支持的情况下会报错
**/
if (window.XMLHttpRequest) {
//IE6以上
oAjax = new XMLHttpRequest();
} else {
oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}

//2.连接服务器
//open(方法,url,是否异步)
var param = ""; //请求参数。
//只有data存在,且为对象使才执行
var data = options.data ? options.data : -1; //缓存data
if (typeof (data) === "object") {
for (var key in data) { //请求参数拼接
if (data.hasOwnProperty(key)) {
param += key + "=" + data[key] + "&";
}
}
param.replace(/&$/, "");
} else {
param = "timestamp=" + new Date().getTime();
}

//3.发送请求
var type = options.type ? options.type.toUpperCase() : "GET";
if (type === "GET") {
oAjax.open("GET", url + "?" + param, true);
oAjax.send();
} else {
oAjax.open("POST", url, true);
oAjax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
oAjax.send(param);
}

//4.接收返回
//OnRedayStateChange事件
oAjax.onreadystatechange = function () {
if (oAjax.readyState === 4) {
if (oAjax.status === 200) {
//请求成功。形参为获取到的字符串形式的响应数据
options.onsuccess(oAjax.responseText, oAjax);
} else {
//先判断是否存在请求失败函数
//存在时,形参为XMLHttpRequest对象,便于进行错误进行处理
if (options.onfail) {
options.onfail(oAjax);
}
}
}
};
return oAjax;//发送请求的XMLHttpRequest对象
}

最终完成就是这样了。当然还远远算不上完美,比如try catch的使用 ,但是通过这样的封装,还是学到很多知识。

Ajax入门(一)从0开始到一次成功的GET请求

续作Ajax入门(二)Ajax函数封装
传统表单在提交后整个页面都刷新

什么是服务器

网页浏览过程分析

一个完整的HTTP请求过程,通常有下面7个步骤

  1. 建立TCP连接
  2. Web浏览器向Web服务器发送请求命令
  3. Web浏览器发送请求头信息
  4. Web服务器- 应答
  5. Web服务器- 发送应答头信息
  6. Web服务器- 向浏览器发送数据
  7. Web服务器- 关闭TCP连接

如何配置自己的服务器程序(AMP)

Ajax必须在服务器环境下才能正常使用

  • 我使用的WampServer程序.(支持中文)官网连接.可能速度不行,不过科学上网,大家应该都会.
  • 网上的使用教程:如何安装使用
  • XAMPP–我试了一下,还是wamp简单,大家有兴趣就自己去测试了
  • 推荐使用firefox浏览器进行AJAX的调试。

Ajax原理

什么是Ajax?

  • 无刷新数据读取
    • 用户注册/在线聊天室
    • 理解同步和异步(基本都用异步请求).

      同步: 客户端发起请求–等待–>服务器端处理—等待–>响应–>页面载入 (请求错误时全部重新载入).
      异步: 客户端发起请求—>服务器端处理—>响应—>页面载入(填写时,即时更新,部分返回).

HTTP请求

  • 一个HTTP请求一般由四部分组成
  1. HTTP请求的方法或动作如是GET还是POST请求
  2. 正在请求的URL,总得知道请求的地址是什么吧?
  3. 请求头,包含一些客户端环境信息,身份验证信息等
  4. 请求体,也就是请求正文,请求正文中可以包含客户端提交的查询字符串信息,表单信息等等.

HTTP请求

HTTP响应

  • 一个HTTP响应一般由三部分组成:
  1. 一个数字文字组成的状态码,用来显示请求是成功还是失败
  2. 响应头,响应头和请求头一样包含许多有用的信息,例如服务器类型,日期时间,内容类型和长度等.
  3. 响应体,也就是响应正文.

HTTP响应

HTTP请求方式

GET POST
用于信息获取/查询(如:浏览帖子) 用于修改服务器上的资源(如:用户注册)
安全性低(使用url传递参数所有人可见) 安全性一般(至少不可见)
容量低(2000个字符) 容量几乎无限

常见的HTTP状态码

状态码 描述 原因短语
200 请求成功.一般用于GET和POST方法 OK
301 资源移动.所请求资源移动到新的URL,浏览器自动跳转到新的URL Moved Permanently
304 未修改.所请求资源未修改读取缓存数据 Not Modified
400 请求语法错误,服务器无法理解 Bad Request
404 未找到资源,可以设置个性”404页面” Not Found
500 服务器内部错误 internal Server Error

编写Ajax

类比打电话理解Ajax编写步骤

打电话 ajax请求
1.打电话 1.创建Ajax对象
2.拨号 2.连接服务器
3.建立连接 3.发送请求
4.听 4.接受返回

1.创建Ajax对象

  • IE6:ActiveXObject("Microsoft.XMLHTTP");//IE6已死,可以不考虑了
  • XMLHttpRequest();

例:var request = new XMLHttpRequest();

2.连接服务器

  • open(method,url,async);
  • open(发送请求方法”GET/POST” ,(请求地址”文件名”) ,是否异步传输)

例: request.open("GET","get.json",true);

3.发送请求

  • send(string)
  • 在使用GET方式请求时无需填写参数
  • 在使用POST方式时参数代表着向服务器发送的数据
1
2
3
4
5
6
7
8
9
10
//完整的GET请求
var oAjax = new XMLHttpRequest();//创建Ajax对象
oAjax.open("GET","create.php",true);//连接服务器
oAjax.send();//发送请求

//完整的POST发送请求
var oAjax = new XMLHttpRequest();//创建
oAjax.open("POST","create.php",true);//"POST"
oAjax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");//设置HTTP头信息.必须在open与send之间,否则出现异常.
oAjax.send("name=陈二狗&sex=男");//发送给服务器的内容
### 4.接收返回-请求状态监控
  • XMLHttpRequset取得响应
属性
responseText 获得字符串形式的响应数据
responseXML 获得XML形式的响应数据
statusstatusText 以数字和文本方式返回HTTP状态码
getAllResponseHeader() 获取所有的响应报头
getResponseheader() 查询响应中的某个字段的值
  • onreadystatechange事件

    通过监听onreadystatechange事件,来判断请求的状态.

  • readyState属性:响应返回所处状态

状态码 状态 所处位置
0 (未初始化) 还没有调用open()方法
1 (载入) 已调用send()方法,正在发送请求
2 (载入完成) send()方法完成,已经收到全部响应 内容
3 (解析) 正在解析响应内容
4 (完成) 响应内容解析完成,可以在客户端调用了

例:

1
2
3
4
5
6
7
8
9
10
11
12
//基本完整的一个Ajax请求
var request = new XMLHttpRequest();
request.open("GET","get.json",true);
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
//响应成功,做一些事情
} else {
//响应失败,做一些事情
}
}
};

使用函数简单的封装一个get请求

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
31
32
33
34
35
/**
* 一个简单的异步get请求
* @param {String} url 请求地址,文件名
* @param {Function} fnSucc 请求成功时执行的函数,形参为为获取的字符串值.
* @param {Function} fnFaild 请求失败执行的函数,可选参数
*/
function get(url, fnSucc, fnFaild) {
//1.创建ajax对象
var oAjax = null
//此处必须需要使用window.的方式,表示为window对象的一个属性.不存在是值为undefined,进入else/若直接使用XMLHttpRequest在不支持的情况下会报错
if (window.XMLHttpRequest) {
oAjax = new XMLHttpRequest();
} else {
//IE6以上,现在应该不需要考虑IE6了
oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}
//2.连接服务器
//open(方法,url,是否异步)
oAjax.open("GET", url, true);
//3.发送请求
oAjax.send();
//4.接收返回
//OnRedayStateChange事件
oAjax.onreadystatechange = function () {
if (oAjax.readyState === 4) {
if (oAjax.status === 200) {
fnSucc(oAjax.responseText);
} else {
if (fnFaild) {
fnFaild();
}
}
}
};
}

使用Ajax

基础:请求并显示静态TXT文件

  • 字符集编码:不一致时会出现乱码
  • 缓存,阻止缓存,(使用时间对象添加)

动态数据:请求JS(或JSON)文件

注:**并不推荐使用eval,并不推荐使用eval,并不推荐使用eval**。因为eval解析数据时会有一系列问题出现。这里是因为只是学习就随意点了。
在需要解析请求数据时,推荐使用JSON的方法JSON.parse()可以将一个 JSON 字符串解析成为一个 JavaScript 值。参考MDN-JSON

  • eval的使用
    例:
1
2
3
4
5
6
7
8
9
10
var str = "54-8*6+4";
alert(eval(str)); //10;

var str1 = "[1,2,3]";
var arr = eval(str1);
alert(arr[1]);//2

var str3 = "function show(){alert('abc');}";
eval(str3);
show() //abc

一次成功的get请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//已经引入get函数
//数据文件放在同级目录下就行了。
get("json.js", function (str) {
var arr = eval(str);
alert(arr[0].a);
}, function () {
alert("服务器请求失败!");
});
//json.js存放的文件如下
/*
[{
a: 12,
b: 5
}, {
a: 2,
b: 56
}]
*/
//服务器响应成功时,将返回12.

  • DOM创建元素
  • 局部刷新:请求并显示部分网页文件,使用for循环.
  • 这里是属于DOM操作的范围,在这里就不过多讲述了。

task0002(二)- DOM + 事件

DOM

添加class、移除class、是否同级元素、获取元素位置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 为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
}

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

思路:

  1. 其实这里可以先定义一个hasClass函数。用来判断该节点是否含有某个className。
  • addClass添加样式。调用hasClass函数,判断element是否含有待添加的新className,若没有则添加,否则什么都不做。
  • removeClass删除样式。调用hasClass函数,判断element是否含有该指定样式,若含有的话删除该className。没有的话什么都不做。
  1. 判断siblingNode和element是否为同一个父元素下的同一级的元素。这里直接判断parentNode就可以了吧

  2. 获取element相对于浏览器窗口的位置,返回一个对象{x, y}。

  • 这个题应该是这几个中比较复杂的一个了。因为不能直接使用offsetLeft/Top。**offsetLeft/Top所获取的是其相对父元素的相对位置。**当多层定位嵌套时想要获取到当前元素相对网页的位置就会不对。

  • 并且由于在表格iframe中,offsetParent对象未必等于父容器,所以也不能直接利用该元素的parent来获取位置,因为其对于表格iframe中的元素不适用。

  • 通过查询知道有一个Element.getBoundingClientRect()方法。它返回一个对象,其中包含了left、right、top、bottom四个属性,分别对应了该元素的左上角和右下角相对于浏览器窗口(viewport)左上角的距离。

  • 但是用该方法获取到的是元素的相对位置,在出现滚动时,距离会发生改变,要获得绝对位置时,还需要加上滚动的距离。因为Firefox或Chrome的不兼容问题需要进行兼容性处理,参考document.body.scrollTop or document.documentElement.scrollTop

  • 最终根据两个值,得到绝对位置。

1
2
3
//其实也简单,只需要获取到两个值,取其中的最大值即可。
var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);

实现:

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
31
32
//判断element中是否含有className为sClass。
function hasClass(element, sClass) {
return element.className.match(new RegExp("(\\s|^)" + sClass + "(\\s|$)"));
}

// 为element增加一个样式名为newClassName的新样式
function addClass(element, newClassName) {
if (!hasClass(element, newClassName)) {
element.className += " " + newClassName;
}
}

// 移除element中的样式oldClassName
function removeClass(element, oldClassName) {
if (hasClass(element, oldClassName)) {
var reg = new RegExp("(\\s|^)" + oldClassName + "(\\s|$)");
element.className = element.className.replace(reg, "");
}
}

// 判断siblingNode和element是否为同一个父元素下的同一级的元素,返回bool值
function isSiblingNode(element, siblingNode) {
return element.parentNode === siblingNode.parentNode
}

// 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
function getPosition(element) {
var position = {};
position.x = element.getBoundingClientRect().left + Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);//获取相对位置+滚动距离=绝对位置.
position.y = element.getBoundingClientRect().top + Math.max(document.documentElement.scrollTop, document.body.scrollTop);
return position;
}

参考资料:(还没看完)

挑战mini $

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 实现一个简单的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的对象

实现思路:
嗯,这个题思考了很久,网上找了很多资料但还是不怎么会,还达不到想要的效果,有点钻牛角尖了。尽量来写一下吧。(我果然是个弱鸡)。感谢秒味课堂的免费课程。

  1. 题目要求获取到所有的节点中的第一个,所以不需要用数组来储存获取到的节点。
  2. 额。。想了半天,还是使用函数包装来实现后代选择器比较好,所以VQuery函数返回是获取到的完整节点对象数组,$函数用来达到题目要求。
  3. 所以在VQuery函数中就不需要考虑空格了,直接使用switch分支,来判定不同的情况。#.[[=]
  4. $函数中,判断字符串中是否含有空格,有空格的话需要分割成数组,数组的前一项是为父选择符,后一项为子选择符。分不同的情况来调用VQuery函数,并返回对象。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* $函数的依赖函数,选择器函数
* @param {string} selector CSS方式的选择器
* @param {object} root 可选参数,selector的父对象。不存在时,为document
* @returns {Array} 返回获取到的节点数组,需要注意的是使用ID选择器返的也是数组
*/
function VQuery(selector, root) {
//用来保存选择的元素
var elements = []; //保存结果节点数组
var allChildren = null; //用来保存获取到的临时节点数组
root = root || document; //若没有给root,赋值document
switch (selector.charAt(0)) {
case "#": //id选择器
elements.push(root.getElementById(selector.substring(1)));
break;
case ".": //class选择器
if (root.getElementsByClassName) { //标准
elements = root.getElementsByClassName(selector.substring(1));
} else { //兼容低版本浏览器
var reg = new RegExp("\\b" + selector.substring(1) + "\\b");
allChildren = root.getElementsByTagName("*");
for (var i = 0, len = allChildren.length; i < len; i++) {
if (reg.test(allChildren[i].className)) {
elements.push(allChildren[i]);
}
}
}
break;
case "[": //属性选择器

if (selector.indexOf("=") === -1) {
//只有属性没有值的情况
allChildren = root.getElementsByTagName("*");
for (var i = 0, len = allChildren.length; i < len; i++) {
if (allChildren[i].getAttribute(selector.slice(1, -1)) !== null) {
elements.push(allChildren[i]);
}
}
} else {
//既有属性又有值的情况
var index = selector.indexOf("="); //缓存=出现的索引位置。
allChildren = root.getElementsByTagName("*");
for (var i = 0, len = allChildren.length; i < len; i++) {
if (allChildren[i].getAttribute(selector.slice(1, index)) === selector.slice(index + 1, -1)) {
elements.push(allChildren[i]);
}
}
}
break;
default: //tagName
elements = root.getElementsByTagName(selector);
}
return elements
}
/**
* 模仿jQuery的迷你$选择符。
* @param {string} selector CSS方式的选择器,支持简单的后代选择器(只支持一级)
* @returns {object} 返回获取到的第一个节点对象,后代选择器时,返回第一个对象中的第一个符合条件的对象
*/
function $(selector) {
//这里trim处理输入时两端出现空格的情况,支持ie9+。但是这个函数实现起来也特别简单,可以参考我task0002(-)前面有trim函数的实现。稍微修改一下,这样就没兼容性问题了。
if (selector == document) {
return document;
}
selector = selector.trim();
//存在空格时,使用后代选择器
if (selector.indexOf(" ") !== -1) {
var selectorArr = selector.split(/\s+/); //分割成数组,第一项为parent,第二项为chlid。
//这里没去考虑特别多的情况了,只是简单的把参数传入。
return VQuery(selectorArr[1], VQuery(selectorArr[0])[0])[0];
} else { //普通情况,只返回获取到的第一个对象
return VQuery(selector,document)[0];
}
}

事件

事件绑定、事件移除

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 给一个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
}

这里慕课网的视频讲的特别清楚,就不赘述了。

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
31
32
/**
* 事件添加函数
* @param {object} element 需要绑定事件的对象
* @param {string} event 事件类型
* @param {function} listener 事件触发执行的函数
*/
function addEvent(element, event, listener) {
if (element.addEventListener) { //标准
element.addEventListener(event, listener, false);
} else if (element.attachEvent) { //低版本ie
element.attachEvent("on" + event, listener);
} else { //都不行的情况
element["on" + event] = listener;
}
}

/**
* 事件移除函数
* @param {object} element 需要移除事件的对象
* @param {string} event 事件类型
* @param {function} listener 需要被移除事件函数
*/
function removeEvent(element, event, listener) {
// your implement
if (element.removeEventListener) { //标准
element.removeEventListener(event, listener, false);
} else if (element.detachEvent) { //低版本ie
element.detachEvent("on" + event, listener);
} else { //都不行的情况
element["on" + event] = null;
}
}

click事件、Enter事件

利用上面写好的事件绑定函数就很简单了。

  • click事件,这个简单,直接函数封装一层就行。
  • Enter事件,这里主要考察的键盘的事件的触发。
    1. keydown事件:在键盘按下时触发.
    2. keyup事件:在按键释放时触发,也就是你按下键盘起来后的事件
    3. keypress事件:在敲击按键时触发,我们可以理解为按下并抬起同一个按键
    4. keyCode属性:在键盘事件触发时,按下的键的值。值=13时,为Enter键。(需进行兼容处理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 实现对click事件的绑定
function addClickEvent(element, listener) {
addEvent(element, "click", listener);
}
// 实现对于按Enter键时的事件绑定
function addEnterEvent(element, listener) {
// your implement
addEvent(element, "keydown", function (ev) {
//兼容性处理。
var oEvent = ev || window.event;
if (oEvent.keyCode === 13) {
listener();
}
});
}

接下来我们把上面几个函数和$做一下结合,把他们变成$对象的一些方法

  • 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
//在js中万物皆对象(原谅我这么浅显的说),所以实现就特别简单了
$.on = function (element, type, listener) {
return addEvent(element, type, listener);
};
$.un = function (element, type, listener) {
return removeEvent(element, type, listener);
};
$.click = function (element, listener) {
return addClickEvent(element, listener);
}
$.enter = function (element, listener) {
$.enter addEnterEvent(element, listener);
};

事件代理

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

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

1
2
3
4
5
6
7
8
9
<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);
}
1
2
3
4
5
6
7
8
9
10
11
12
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结构或者内容后,都需要重新绑定事件呢?当然不会这么笨,接下来学习一下事件代理,然后实现下面新的方法:

1
2
3
4
5
6
7
8
9
// 先简单一些
function delegateEvent(element, tag, eventName, listener) {
// your implement
}

$.delegate = delegateEvent;
// 使用示例
// 还是上面那段HTML,实现对list这个ul里面所有li的click事件进行响应
$.delegate($("#list"), "li", "click", clickHandle);

实现思路:

写到这里,刚好前几天CSS魔法写的《前端进阶之路:点击事件绑定》有提到“事件代理/委托”,不过是直接使用jQuery来实现的。所以地址有兴趣的自己搜索吧-_-。

  • “事件代理” 的本质是利用了事件冒泡的特性。当一个元素上的事件被触发的时候,比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;

  • 这个事件从原始元素开始一直冒泡到DOM树的最上层。任何一个事件的目标元素都是最开始的那个元素,在我们的这个例子中也就是按钮,并且它在我们的元素对象中以属性的形式出现。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。

  • 这里就不细说事件冒泡与事件捕获了(阻止默认行为也会用到,有兴趣去网上找找看),但是要理解事件代理就必须先知道它们。下面这张图可以先看看。(图片来自网络,侵删)

事件捕获与事件冒泡原型图

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 事件代理
* @param {HTMLElement} element 需要进行事件代理的父元素。
* @param {string} tag 需要触发事件的标签名
* @param {string} eventName 触发的事件类型
* @param {function} listener 事件执行的函数
*/
function delegateEvent(element, tag, eventName, listener) {
// your implement
return addEvent(element, eventName, function (ev) {
var oEvent = ev || event; //兼容处理
var target = oEvent.target || oEvent.srcElement; //兼容处理
if (target.tagName.toLocaleLowerCase() === tag) {
listener.call(target, oEvent); //使用call方法修改执行函数中的this指向,现在this指向触发了事件的HTML节点(可直接使用this.innerHTML返回该节点内容)
}
})
}

封装改变

估计有同学已经开始吐槽了,函数里面一堆$看着晕啊,那么接下来把我们的事件函数做如下:(这里应该是把前面的$.on$.click$.un$.delegate都改写一下。比较简单,就拿一个出来作例子吧。)

1
2
3
4
5
6
7
//和上面的函数一样,原来第一个参数是传入获取到的父HTMLElement对象,现在直接传入选择器名称就行
$.delegate = function (selector, tag, event, listener) {
//这里的`$(selector)`,是用的自己封装的选择器函数,愿意的话可以换成标准支持的`document.querySelector()`
return delegateEvent($(selector), tag, event, listener);
};
// 使用示例:
$.delegate('#list', "li", "click", liClicker);

task0002(一)- JavaScript数据类型及语言基础

1. 第一个页面交互

  • 这里最需要学习的老师的代码中,每一部分功能都由函数控制,没有创建一个全部变量。且最后有一个函数来控制执行代码。这个更多的是思想上的学习吧!
  • 在chrome上相加时,直接两个数拼接到一起了,而不是数值相加。因为输入的值,在获取时,默认是字符串类型的。
  • 在IE8下提示对象不支持“addEventListener”属性或方法。原因是IE8不支持标准的DOM事件绑定函数,它使用attachEvent

1.1 了解JavaScript是什么

JavaScript,一种直译式脚本语言,是一种动态类型、基于原型的语言,内置支持类。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML网页上使用,用来给HTML网页增加动态功能。然而现在JavaScript也可被用于网络服务器,如Node.js。

上面是维基百科上的解释。不过让JavaScript流行起来的原因应该是JavaScript 是目前所有主流浏览器上唯一支持的脚本语言。下面是MDN对其核心功能的解释

核心的 JavaScript 中包含有一组核心的对象,包括 Array,Date 和 Math,以及一组核心的语言要素,包括操作符,控制结构和语句。出于多种目的,可以通过为其增补附加的对象,对核心 JavaScript 加以扩展;例如:

  • 客户端: JavaScript 提供了用于控制浏览器(Navigator 或者其它浏览器)以及其中的文档对象模型(DOM)的对象,从而扩展了核心 JavaScript。例如,客户端扩展允许应用程序在 HTML 的表单中加入元素,以便响应用户事件,比如鼠标点击,表单输入和页面导航。
  • 服务器端: JavaScript 提供了服务于在服务器上运行 JavaScript 的对象,从而扩展了核心 JavaScript。例如,服务器端扩展可以允许应用程序访问关系型数据库,在应用程序的不同调用间提供信息的连续性,甚至于处理服务器之上的文件。

1.2 如何在HTML页面加载JavaScript代码

可以有三种方法加载在HTML页面中引入JavaScript代码:

  1. 内联式: 在HTML标签的style属性中定义样式,在onclick这样的属性中定义Javascript代码;
  2. 嵌入式: 在页面中使用<script>标签定义Javascript代码;
  3. 引用外部文件: 在<script>标签中定义src属性引入Javascript文件.

注意:在<head>或者<body>中都可以创建<script>标签来创建或者引入JavaScript代码。

  • 搜索一下,为什么我们让你把<script>放在</body>前。
  1. 浏览器在解释HTML页面时,是按照先后顺序的,所在放在前面的JS代码就会先被执行。正是因为这种特性,所以放在<head>中的<script>代码会阻塞页面的渲染。
  2. 其实有些JS代码可以放在<head></head >之间,比如IE9以下浏览器兼容HTML5标签的js代码,这是一个底层的兼容脚本,不涉及任何页面逻辑。那么它应该放在<head></head >间。
  3. 新版浏览器<script>标签可以使用defer属性来延迟加载。

最简单的不就是把能放在body中的代码放进去吗?扩展阅读中有详细介绍。

1.3 扩展阅读:

2. JavaScript数据类型及语言基础

  • 创建一个JavaScript文件,比如util.js
  • 实践判断各种数据类型的方法,并在util.js中实现以下方法:

2.1 判断各种数据类型的方法

这里比较简单,可以参考我的另一篇博客JavaScript类型识别.

1
2
3
4
5
6
7
8
9
10
// 判断arr是否为一个数组,返回一个bool值
function isArray(arr) {
return typeof (arr) === "object" && Object.prototype.toString.call(arr) === "[object Array]";
}

// 判断fn是否为一个函数,返回一个bool值
function isFunction(fn) {
return typeof (fn) === "function";
}

在ECMAScript5中,判断数组类型可以直接使用Array.isArray()

1
2
Array.isArray([]); //true
Array.isArray(function(){}); //false

2.2 值类型和引用类型的区别.各种对象的读取、遍历方式

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

2.2.1 深度克隆

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

// 测试用例:
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);
console.log(abObj.b.b1[0]);

console.log(tarObj.a); // 1
console.log(tarObj.b.b1[0]); // "hello"

思路如下

  1. 题目考的主要是有些对象的使用=直接赋值,并不是真正的复制,而是将一个新的变量指向了当前对象,共享同一个地址。在修改原对象时,新对象也会跟着改变。
  2. 经过测试,数字、字符串、布尔、日期、可以直接赋值,修改不会产生影响。所以就思考了在使用typeof值为对象或者是原始类型时的情况。且对象类型为Date对象时,也使用直接赋值的方式。
  3. 再考虑对象类型为Array或者Object的情况。对于结果声明其类型。
  4. 接着往下走,在遍历对象时,只考虑其自身的属性,而不考虑继承来属性。若其自身值还是对象,那么 就递归调用,进一步解析、赋值,否则直接赋值。

实现:

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
function cloneObject(src) {
var result ;//返回的复制后的结果。
if (typeof(src)==="object"){
//对象为日期对象时也直接赋值。
if(Object.prototype.toString.call(src)==="[object Date]"){
result = src;
}else{
//判断对象的类型是Array还是Object,结果类型更改。
result = (Object.prototype.toString.call(src)==="[object Array]")? [] : {};
for (var i in src){
if (src.hasOwnProperty(i)) { //排除继承属性
if (typeof src[i] === "object") {
result[i] = cloneObject(src[i]); //递归赋值
} else {
result[i] = src[i]; //直接赋值
}
}
}
}
}else{
//对于原始类型直接赋值。
result = src;
}
return result;
}

2.3数组、字符串、数字等相关方法

  • 学习数组、字符串、数字等相关方法,在util.js中实现以下函数

2.3.1 数组去重操作

1
2
3
4
5
6
7
8
// 对数组进行去重操作,只考虑数组中元素为数字或字符串,返回一个去重后的数组
function uniqArray(arr) {
// your implement
}
// 使用示例
var a = [1, 3, 5, 7, 5, 3];
var b = uniqArray(a);
console.log(b); // [1, 3, 5, 7]

思路如下:

  1. 新建一下新数组
  2. 循环原数组
  3. 判断新数组内元素,原数组是否含有.含有则跳过
  • 这里使用了数组的indexOf方法,找到某个元素在数组中的索引。简化了查找过程,若使用最简单的遍历寻找的话需要嵌套循环,是这样的,先在循环中取原数组的值,再循环在新数组中查找,若有相等的情况就不添加。(这是我的第一想法,显然比现在复杂很多).
  • 参看来自MDN的Array对象.里面介绍了一下数组的方法.
  1. 返回新数组.

实现:

1
2
3
4
5
6
7
8
9
10
11
function uniqArray(arr) {
// your implement
var result = []; //创建一个新数组。
for (var i = 0, l = arr.length; i < l; i++) {
if (result.indexOf(arr[i]) === -1) { //查找是否已经含有该元素
result.push(arr[i]); //添加到新数组
}
}
return result; //返回新数组

}

2.3.2 实现trim函数,去除字符串首尾空白

  • 实现一个简单的trim函数,用于去除一个字符串,头部和尾部的空白字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.字符串查找
// 假定空白字符只有半角空格、Tab
// 练习通过循环,以及字符串的一些基本方法,分别扫描字符串str头部和尾部是否有连续的空白字符,并且删掉他们,最后返回一个完成去除的字符串
function simpleTrim(str) {
// your implement
}
//2.正则
// 很多同学肯定对于上面的代码看不下去,接下来,我们真正实现一个trim
// 对字符串头尾进行空格字符的去除、包括全角半角空格、Tab等,返回一个字符串
// 尝试使用一行简洁的正则表达式完成该题目
function trim(str) {
// your implement
}
// 使用示例
var str = ' hi! ';
str = trim(str);
console.log(str); // 'hi!'

实现如下:

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
//1.字符串查找
//这里就是利用两个循环,找到头尾第一个不是空格且不是tab符的元素。记录它们的索引,之后截取字符串。
function simpleTrim(str) {
// your implement
var result = "";
for (var i = 0, il = str.length; i < il; i++) { //从头查找
if (str[i] != " " && str[i] != "\t") {
break; //查找到第一个不为空格及tab符的元素
}

}
for (var j = str.length - 1; j >= 0; j--) { //从尾查找
if (str[j] != " " && str[j] != "\t") {
break;
}

}
result = str.slice(i, j + 1); //截取需要的字符串。
return result;
}
//2.正则
function trim(str) {
// your implement
var result = "";
result = str.replace(/^\s+|\s+$/g, ""); //使用正则进行字符串替换
return result;
}

2.3.3 遍历数组,对每一个元素执行fn函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 实现一个遍历数组的方法,针对数组中每一个元素执行fn函数,并将数组索引和元素作为参数传递
function each(arr, fn) {
// your implement
}

// 其中fn函数可以接受两个参数:item和index

// 使用示例
var arr = ['java', 'c', 'php', 'html'];
function output(item) {
console.log(item)
}
each(arr, output); // java, c, php, html

// 使用示例
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

思路如下:

  1. 这里的实现有点类似ECMA5中数组的forEach()方法
  2. 由示例可知:item为必须参数,index为可选参数.
  3. 且item为数组项,index为数组索引.
  4. 这样就简单了,循环传参.

实现:

1
2
3
4
5
6
function each(arr, fn) {
// your implement
for (var i = 0, l = arr.length; i < l; i++) {//遍历传参
fn(arr[i], i);
}
}

2.3.4 获取对象里第一层元素的数量,返回整数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取一个对象里面第一层元素的数量,返回一个整数
function getObjectLength(obj) {}

// 使用示例
var obj = {
a: 1,
b: 2,
c: {
c1: 3,
c2: 4
}
};
console.log(getObjectLength(obj)); // 3

实现:

1
2
3
4
5
6
7
8
9
10
11
//使用for in遍历时,直接获取到的就是第一层的结果
//排除继承来的属性,使用外部变量保存循环次数
function getObjectLength(obj) {
var count = 0;
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
count++;
}
}
return count;
}

正则表达式

1
2
3
4
5
6
7
8
9
// 判断是否为邮箱地址
function isEmail(emailStr) {
// your implement
}

// 判断是否为手机号
function isMobilePhone(phone) {
// your implement
}

实现思路

这里参考我对于正则表达式入门的两篇博客

  1. 这里匹配的情况是最简单的情况,并没有特别完美,比如限制开头第二位数字的范围:188,158通过,而123,191,不通过等等。
  2. 可利用多选分支,例如:/^1[3|5][0-9]{9}$|^18\d{9}$/。不过现在虚拟运营商的加入,号码段变多了,所以直接用最简单的方法,也没事。
  • 邮箱的匹配
  1. 在@前能出现哪些东西?这里使用(\w+\.)*来匹配出现.的情况,表示出现0次或多次因为.后不能紧跟@,所以后面紧跟\w+匹配普通的字母数字情况。
  2. @后出现的邮箱后缀并不固定所以使用\w+来匹配。
  3. 最后考虑域名结尾的级联情况所以用(\.\w+)+

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断是否为邮箱地址
function isEmail(emailStr) {
// your implement
var reg = /^(\w+\.)*\w+@\w+(\.\w+)+$/;
return reg.test(emailStr);
}

// 判断是否为手机号
function isMobilePhone(phone) {
// your implement
var reg = /^1\d{10}$/;
return reg.test(phone);
}

2.4 参考资料

使用搜索:谷歌必应百度