书名:B/S项目开发实战 HTML+CSS+jQuery+PHP
ISBN:978-7-115-47691-3
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 周 菁
责任编辑 赵 轩
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
HTML、CSS和JavaScript是每个网页前端开发人员所必备的基础知识,jQuery更是JS的标配工具,而PHP则是服务器端的数据交互操作必不可少的语言。因此,一个完整的B/S项目,需要前端、后端工程师相互配合完成。这么多的知识点,对于初学者来说,确实有点无所适从。
本书从大家最喜闻乐见的“记事本”入手,采用“项目实例驱动”的方法,让每一个从未接触过B/S开发的初学者可以无障碍地学习开发Web页面。本书非常适合网站开发者、大中专院校师生、培训班学员以及业余爱好者阅读。
首先非常感谢人民邮电出版社在很短的时间内就决定出版这本书。在B/S方面的基础书籍非常多,且各种在线资源异常丰富的今天,本书有什么与众不同的地方?
笔者在过去的近十年间一直都在从事报业数据分析方面的工作。2010年前后的10年可以说是报业发展的黄金十年,报社动辄花费数百万采购一套软件是很常见的,但报社的工作人员仍然经常抱怨这些软件在使用中的各种不便,其中不乏一些大的品牌软件。我们本来的主业是报业数据咨询服务,听到他们的抱怨久了,就产生了一个想法:为什么我们就不能来开发一个更贴近报社使用实际的应用软件呢?和专业的开发人员相比,我们在代码能力上确实欠缺,但作为职场从业人员,我们却拥有着对行业深刻的理解和经验,而这正是我们最大的财富和竞争力!
说干就干。我们先用Excel开发了一套可以快速生成日报、周报、月报的“一键报表系统”,几家报社试用后反响非常好;后来又借助VBA和其他辅助工具将该系统正式升级为专业化的报业数据软件——“广告通”。这款软件在全国报业最发达的珠三角地区一度成为覆盖面最广的同类软件,服务的客户范围包括广州日报、南方都市报、羊城晚报等全国著名媒体及佛山日报、东莞日报、中山日报、惠州日报等地方媒体。
后来,在帮助一家报社定制C/S数据库应用项目时,客户提出要同时支持浏览器远程访问。C/S毕竟只是本地局域网使用比较方便。为了接下这个预算达数十万的“大单”,尽管我们还没有这方面的开发经验,但仍然硬着头皮先口头答应了下来。紧接着我们就到网上购买各种所谓的“从入门到精通”教材。这些书一般都是先从一个个的基础知识点讲起,最后再以一个或多个实例做综合讲解。由于B/S涉及的知识点是非常多的,这个学习的过程非常枯燥。坚持了两三个星期后,却根本看不到任何的“开发成果”。
怎么办?传统的老路走不通了!答应客户的事,必须要做到啊,不然数十万的单子可能就会飞掉!我们开始上网查资料、找工具,想快速开发,最后发现了EasyUI这个东东。当时它最吸引我们的是:再不用为那些多的想吐的CSS样式烦神了,依照自身带的各种应用实例就可以非常快地搭建好自己的B/S项目,而且拥有非常强大的后台数据交互能力。好在自己还有一些数据库方面的基础,最终不到一个月就拿出了测试版,并得到了客户的高度认可。
项目做完之后,回头再看当时的开发过程,有一点触动很大:那就是越早体验到开发的成就感,这种坚持下去的动力就会越大。自学编程难吗?说难不难,说易也不易,关键是要掌握里面的各种“套路”。是的,就是“套路”,一通百通!本书就是自己多年来的实战经验总结,希望能给新手或者尝试打算向IT方面转型的职场同仁们一点帮助。
对于一个从未接触过B/S项目开发的初学者来说,最大的问题在于不知从何处下手。按照传统的做法,一般是首先用HTML的各种标签写出网页框架,然后再用CSS来设计页面样式;设计页面样式时,还要考虑与传统PC端、手机、平板等不同设备的兼容;当需要和用户、数据库进行交互时,则要用JavaScript、PHP等编程语言来写一系列的程序处理代码。因此,一个完整的B/S项目开发一般都是需要前端、后端工程师相互配合完成的。这么多的知识点,对于初学者来说,确实有点无所适从。
虽然国内已经有了多个类似于w3school这样的网站,可以提供关于Web技术方面的学习内容和日常所需资料的查询,但我们真的不建议初学者像查字典一样地逐条去学习。这主要是因为,此类网站仅仅相当于一个庞大的知识库,逐条学习难免会分不清重点,而且非常耗时间,学习到最后的结果可能还是没有头绪,具体到项目开发时仍然无从下手。就像小学生学语文一样,你见过谁是拿着一本《新华字典》一个字一个字地去学吗?肯定是要结合一些场景来学习比较好!
因此我认为,很多传统的、按部就班式的基础类书籍并不一定是初学者的最佳选择。例如,市面上有大批分别讲解HTML、CSS、JavaScript和PHP的专业书,但很少有混合型的,它们无一例外的都是从“点”讲起,最后才扩展到“面”。你有见过将CSS选择器和JS选择器、将PHP语法和JS语法混合在一起比较学习的书籍吗?不能说没有,但很少见。这就可能带来一个问题:一个毫无基础的初学者,辛辛苦苦学习几个月,即便是把那些多如牛毛的标签、选择器等都学“吐”了,可能还不会在脑海中形成一个网页开发的整体概念。一旦坚持不下去,其最终的结果就是半途而废。
因此,我的建议是:从“面”开始学,有了一种总体性的概念认知后,你才会清楚地知道每个具体的“点”将应用到哪里,这个“点”是应该浅尝辄止还是要继续追本溯源。只有这样,才不会把时间浪费在很多琐碎的、有些甚至永远都用不到的知识点中,也才能以最具效率的方式帮助自己确定最终需要努力的方向。当以这样的方式来学习时,你眼中的JavaScript和PHP可能都不再是独立的语言,也许只是页面中普通的和<?php>标签元素而已。
正是出于这样的考虑,我才决定换一种思路来写这本书。当然,从“面”开始学习,并不代表着可以没有任何基础,只是这种基础的学习并不是面面俱到的。例如,HTML只要大概掌握div、p、span、a等几个常用标签的用法即可,尤其是标签的属性知识一定要搞清楚,这是后期继续学习CSS和JavaScript的关键;CSS则要了解一些基本的选择器及样式声明方法。有了这些简单的基础后,其实就可以选择一些偏样式的前端框架来尝试做些静态页面。之所以要强调使用前端框架,是因为它可以让初学者暂时摆脱漫长且繁杂的样式学习过程,快速看到开发成果,从而增强继续学习的信心。至于JavaScript和PHP也是同样的道理,初学者暂且将它们看成是页面中的标签元素好了,而且它们的语法非常相近,会了这个自然就懂了那个,一起学习非常合适。
本书主体部分共包括4章。它从大家最常见的“记事本”入手,采用“项目实例驱动”的方法,让每一个从未接触过B/S开发的初学者都可以无障碍地切入到Web页面的学习中。
第1章“初步认识B/S”:以一个文本文档为例,手把手教大家如何创建、运行最基本的网页,并大致了解静态网页中的HTML标签、CSS样式、JavaScript信息交互等方面的知识。
第2章“项目开发准备”:学习JavaScript及其标配工具jQuery方面的知识,对JavaScript中各种对象的属性和方法、jQuery中的各种事件都做了详细说明。本章内容主要用于客户端页面的开发。
第3章“数据交互操作”:学习服务器端编程语言PHP方面的知识,以及服务器端数据的Ajax交互技术。本章内容主要用于服务器端的数据操作。
第4章“使用EasyUI框架实现快速开发”:有了前几章的知识储备之后,就可以使用一些现成的框架来快速搭建自己的应用项目。而所谓的框架,就相当于是一种二次开发工具,它可以让很多并未系统学习过相关专业知识但却拥有丰富行业经验的职场人士也能轻松开发出符合自身需要的管理系统。本章使用的框架是EasyUI,并提供了一个从用户登录到打开主页,再到退出登录的完整实例。
为不影响本书的主体结构,很多细节化的内容都以附录形式放到了本书的“B/S基本知识库”中。基本知识库具体包括6大部分:HTML、CSS、jQuery、MySQL、PHP和正则表达式。这些基本知识并不要求一下子全部掌握,仅做日常开发时备查使用。可以毫不夸张地说,新手要学习B/S项目开发,有了这本书就够了,它已涵盖常规B/S项目开发中绝大部分的知识点。
对于新手来说,快速获取成就感不仅能树立自己继续学习的信心,更能让整个学习过程充满乐趣。想快速体验这种成就感?请参考下面的“一周速成指南”。
第1天:做好开发准备。请认真阅读第1章第3节“代码编辑工具”和第3章第1节“Web环境搭建”,下载并安装代码编辑工具、配置服务器环境,然后用编辑器打开第4章第1节中的test.html示例文件代码,对照教程说明慢慢研究并体会页面的组成结构。
第2天:学习第4章中的第2节和第3节,完成登录窗口的界面设计。这个过程主要是体验CSS样式及对象属性的写法。
第3天:学习第4章中的第4节和第5节,完成用户输入验证以及通过回车键快速移动光标。这个过程主要是体会事件代码的写法。
第4天:学习第4章中的第6节,完成向服务器提交验证。这个过程主要是体会PHP如何返回数据及Ajax交互(也就是客户端与服务器端的交互)。
第5天和第6天:学习第4章中的第7节“用户会话控制”,继续体会主页面的页面结构和PHP的代码结构。
第7天:学习第4章中的第8节,将实例项目应用于局域网或移动端。
当然,学无止境。以上“一周速成指南”仅仅用于帮助读者快速了解B/S的项目开发流程。要想真正解决自己的应用需求,还是应该认真通览全书。即使以后想继续把项目做得更完善,最起码也会知道自己需要发力的方向。与本书同步推出的还有一本关于系统学习EasyUI的专业书。该书全面介绍了面板(Panel)、树(Tree)、数据表格(Datagrid)、菜单(Menu)等50多个插件的使用方法,两本配套使用,将使B/S项目开发变得更加轻松!
最后,再次感谢人民邮电出版社的赵轩编辑对本书得以顺利出版所给予的大力支持与帮助,同时感谢家人的理解和包容,使得我可以有大量的时间来完成写作!在编写本书的过程中,尽管已数易其稿、并力求精益求精,但错误、疏漏之处仍在所难免,恳请广大读者批评、指正。
周菁
2017年10月
所谓的B/S,就是Browser/Server的简称,中文解释就是“浏览器/服务器”模式。例如,我们平时经常浏览的网易、新浪、搜狐等新闻门户网站以及在移动端阅读的今日头条等,都是这种开发模式。
该模式在企业级的应用中更为广泛:技术人员只需把相关程序及数据库放到公司的服务器上,各部门工作人员通过PC或移动端自带的浏览器就能访问,包括数据的查询、修改、提交等。在这种模式下,浏览器就成为客户端最主要的应用软件,它省去了传统C/S模式下需要再安装专门客户端程序的麻烦,大大节省了项目的开发、维护和使用成本。尤其是在移动互联网如火如荼的今天,B/S的优势更加明显。
既然浏览器是B/S模式下最重要的客户端程序,那它是怎么运行项目的?现在我们就先从最常见的普通文档讲起。
请大家先打开操作系统自带的“记事本”,然后随便输入一些文字,并保存为TXT文件。如图1-1所示。
图1-1
现在再打开电脑上的浏览器,把刚才建立的那个文本文件拖拽进浏览器,如图1-2所示。
图1-2
这时你会发现,该文档中的内容已经在浏览器中显示了,如图1-3所示。
图1-3
此时,该TXT文件就相当于一个网页程序,它是通过浏览器运行的,输出的内容就是一行文字。浏览器地址以file:///开头,表示它访问的是本地绝对路径文件。
当然,你也可以不用拖拽文件而是直接在浏览器地址输入完整的文件路径。注意前面一定要以“file:///”开头。
打开“记事本”中的菜单【格式】—【字体】,将输入的文字改为粗体并保存。
刷新浏览器,会发现输出的文字还是原来的老样子。为什么?这是因为,每个程序都有自己的规范,在浏览器上运行的文档也不例外。
再比如,大家常用的Office办公软件,当设置某些样式时,其实就是给指定的内容加上标记,只不过为了不给使用者造成太多干扰,大部分的标记都被隐藏了而已。例如,Word文档中的换行标记默认是显示的,有了这个标记就表示开始一个新的段落。如图1-4所示。
图1-4
那么,浏览器上运行的页面文件有哪些规范呢?
例如,刚才编写的文本文件,其扩展名是.txt。不论在这个文件里写入什么内容,拖拽到浏览器运行时,都会按文本处理,不做任何解析的直接输出里面的文字。
就本套课程而言,涉及的扩展名类型只有两种:HTML和PHP。其中,PHP类型的页面文件需要服务器环境的支持,具体请参考下一章内容。
所谓的HTML标记,又被称为标签,它是用尖括号包起来的一种符号,以方便标记文档中的不同内容,并便于浏览器解析。
只有正确使用了标记的文档,才会被浏览器解析并输出自己所希望的内容。
仍以刚刚建立的TXT文件为例,我们先把文件的扩展名改成.html,然后使用<b>标签做标记。如图1-5所示。
图1-5
把该文件拖拽到浏览器运行,显示的文字已经变成了粗体。如图1-6所示。
图1-6
其中,b标签就表示对文字加粗。这里面共出现了两个,第一个<b>被称为开始标签,第二个</b>被称为结束标签。假如将两个标签的位置调整如下。
这是在<b>浏览器</b>上运行的项目
则浏览器运行时只有“浏览器”3个字变为粗体。如图1-7所示。
图1-7
在B/S项目开发中,HTML又被称为“超文本标记语言”。标记已经解释过了,那什么是超文本?
所谓的“超文本”,是指页面文档格式虽然是文本类型的,但却可以包含图片、链接,甚至音乐、程序等非文字的内容。怎么包含呢?当然还是使用标记。
例如,HTML中引用图片使用的是img标签,我们在上述文本文件中再加入图1-8所示的标签:
是不是觉得这个img标签的写法与之前的b有所不同?确实是不一样,现解释如下:
首先,任何标签都必须用尖括号包起来,因此img的常规写法应该是:<img></img>,但这里需要引入的是一幅图片,并不是文字。因此从文本的角度来说,它是没有内容的,这里只需要一个开始标签即可,无需再使用结束标签。
图1-8
其次,引入的图片需要有文件名,如果图片太大,可能还希望能缩小到指定宽度。这些信息必须以属性的方式写到开始标签中。例如,图1-8所示的代码(请注意,现在开始改称代码了,将慢慢地进入开发状态),src和width都是img标签的属性,一个用于指定文件名称,一个用于指定宽度。
浏览器运行效果如图1-9所示。
图1-9
关于属性方面的知识,后面还将专题讲解,目前先大概知道这个概念即可。
由之前的示例可知,对于内容的标记,一般使用<b></b>这样的标签,因为必须明确从哪里开始、到哪里结束。这类标签被称为双标签。
对于无内容的标记,如img,因为无需指定到哪里结束,因而只要使用一个开始标签即可。这类标签被称为单标签,或者叫空标签。
不知有读者注意到没有,图1.9所示中的代码明明是2行,可浏览器运行时为什么将图片和文字显示为一行呢?
这就是需要特别提醒大家的地方:在Office办公软件中,回车就是回车,但在html中写代码就不能这样“任性”了。为实现回车换行效果,当然是要使用标记的。代码如下:
这是在<b>浏览器</b>上运行的项目<br>
<img src="logo.png" width="300px">
其中,<br>就是换行标记,想换几行就加几个<br>。很显然,它同样是个单标签。
所谓的DOM,就是指我们当前正在使用的这个文档;而DOM元素则是指从开始标签到结束标签中的所有代码,开始标签与结束标签之间的内容叫作元素内容。
仍以刚才的代码为例:
这是在<b>浏览器</b>上运行的项目<br>
<img src="logo.png" width="300px">
以上代码中,<b>浏览器</b>是一个DOM元素,该元素内容为“浏览器”;<br>和<img src="logo.png" width="300px">也是DOM元素,但元素内容为空。
那上述代码中还有没有使用标记的部分呢?它们也是DOM元素吗?这就需要了解HTML的完整结构。
一个完整的页面结构如图1-10所示。
图1-10
如果以代码表示,具体如下:
<!DOCTYPE html> <!--声明解析类型-->
<html> <!--根标签(开始)-->
<head> <!--头标签(开始)-->
<meta charset="UTF-8"> <!--编码方式-->
<title>标题</title> <!--标题标签-->
</head> <!--头标签(结束)-->
<body> <!--主体标签(开始)-->
主体内容 <!--主体内容区域-->
</body> <!--主体标签(结束)-->
</html> <!--根标签(结束)-->
在上述结构中,最开始的一行<!DOCTYPE html>是用来声明解析类型的,它告诉浏览器这个页面是使用HTML编写的,因此必须写在所有代码的第一行。DOCTYPE可以大写,也可以小写;后面紧跟着的才是真正的HTML页面代码,所有的代码内容都被包含在<html></html>这对双标签中。
看到这里,可能有的读者又要问了:上一节的代码并没有用到这么多的标签,页面仍然能正常显示,又是为什么?确实,如果仅仅是做个静态页面,那样处理是可以的,但对于一个经常需要进行交互操作的企业级应用项目来说,以上这些标签就是必不可少的。
在上面的HTML代码结构中,最外围的<html></html>这对标签就叫作根标签,因为这个页面中的所有代码都要包含在这对标签中。
根标签元素(根元素)里面又包含两大组成部分:分别是头部和身体部。
头部元素,顾名思义就是放在页面的头部,使用的是<head></head>双标签,相当于所有头部元素的容器。在这个容器中,可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等,因此,它的作用主要是用于描述页面的相关属性以及与其他文档的关系等。绝大多数情况下,头部元素中所包含的信息都不会真正作为内容显示给浏览者。
头部元素必须紧跟在html标签的后面,同时处于body标签之前。如上面的示例代码中,就放了如下2行内容。
<meta charset="UTF-8">
<title>标题</title>
其中,第1行用的是meta标签,这是个单标签,通过charset属性设置了页面的编码方式为UTF-8;第2行用的是title双标签,指定了页面标题(是指显示在浏览器页签中的标题,并不会显示在页面正文中)。例如,上一节没有使用这样的html结构时,页面标题显示的是文件名称,如图1-11所示。
图1-11
如果将代码改成这样。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>职场码上汇</title>
</head>
<body>
这是在<b>浏览器</b>上运行的项目<br>
<img src="logo.png" width="300px">
</body>
</html>
由于把原来的内容放到了body标签中,而且加上了head元素(重新指定了页面标题),则运行效果如图1-12所示。
图1-12
除title外,现重点学习可以包含在头部元素中的两大类标签。
在上例中,通过meta标签的charset属性设置了页面编码方式为UTF-8。此编码方式非常重要,几乎可以对应页面上的所有文字,因此又被称为万国码。如不设置此属性,可能会导致页面乱码情况的出现。
除此之外,该标签还有另外两个很常用的属性:name和http-equiv,分别用于搜索引擎和更新频度。
常用的有keywords和description两个值,可用content指定相关联的具体内容。其中,keywords用于定义针对搜索引擎的关键词,description用于定义对页面的描述。如:
<meta name="keywords" content="职场码上汇">
<meta name="description" content="助您轻松打造企业级应用">
该属性常用的值有refresh,用于刷新页面。如,每5秒刷新一次当前页面:
<meta http-equiv="refresh" content="5">
也可以在content中指定刷新后跳转的页面。如:
<meta http-equiv="refresh" content="5;url=跳转地址">
该标签用于定义页面与外部资源的关系。
例如,很多网站在打开时会发现它连带着一个小图标。只要在头部加上以下代码,就能轻松实现这一功能:
<link href="favicon.ico" rel="icon">
这里引用的图片可以是ico图标,也可以是.png、.jpg等其他格式的图片文件。
运行效果如图1-13所示。
图1-13
需要注意的是,在使用href属性引用外部文件(有的标签为src属性,例如img图片)时,必须注意文件路径的写法。
文件路径有绝对和相对之分
绝对路径以file:///开头,然后是磁盘符和一个个的目录层次,直到需要的文件名称。如:
<link href="file:///D:/code/favicon.ico" rel="icon">
这种方式最致命的问题是,当整个目录转移到另外的盘符或其他电脑时,目录结构一旦出现任何变化,文件链接当即失效,因此不建议采用。
相对路径就是当前页面文件相对于要引用文件而言的路径。
当前目录以./表示(也可省略),上级目录以../表示。
例如,要引用的文件和当前页面在同一个目录时,可以直接:
favicon.ico 或 ./ favicon.ico。
身体元素就是用<body></body>包起来的部分,也就是浏览器可见的部分。
所有的可见内容,都应该在这里进行添加。
例如,对于上一节的示例代码,当改用完整的HTML结构时,把它们全部放到body中即可:
<body>
这是在<b>浏览器</b>上运行的项目<br>
<img src="logo.png" width="300px">
</body>
这样也就回答了上一节所遗留下来的问题:代码中没有使用标记的部分,它们同样是DOM元素,这个DOM元素是body的。
上一节我们已经讲了3个基本概念:双标签、单标签和DOM元素。现在重点强调以下几点。
标签必须用<>包起来,不管是单标签还是双标签。
标签属性不管多长,都必须写在用<>包起来的开始标签中。例如,上面的示例代码中,meta标签用到了charset、name、content属性,link标签用到了href、rel属性,img标签用到了src、width属性。
属性在代码编程中是个非常重要的概念,怎么理解呢?请大家先回忆一下在Word文档中给指定文字设置颜色或字体时的操作方法。假如要将某标题的字体大小改为20,颜色设置为红色,是不是要先选定这个标题然后再设置?设置完成后,对于这个标题而言,字号、颜色就是它的属性名称,20和红色就是这两个属性所对应的值。
与此同理,HTML的标签属性也分为两个部分:属性名称和属性值,两者用等号相连。当存在多个属性时,可以用空格将其间隔开来(前后顺序不限,效果相同)。
html的语法要求非常宽松,属性值可以用单引号、双引号包含起来,甚至不加引号也可以。但是,如果属性值中含有空格、单引号、双引号、大于号、小于号、等号、反引号等特殊字符时,就必须加引号。当属性值中含有单引号时,外面可以用双引号包含起来,反之亦然。为养成良好的习惯,建议初学者始终给属性值加双引号!
除标签提供的现有属性外,我们还可以自定义属性。自定义的属性名称推荐加上data-的前缀,后面接一个字母或者多个字母都可以,值可以随便写,不会对页面展示造成任何影响。凡以data- 为前缀的自定义属性,都属于关联数据。至于这个关联数据有什么用,后面的“B/S基本知识库”中会有相关说明。
DOM元素是指从开始标签到结束标签中的所有代码(含标签本身),而DOM元素内容是指包含在开始标签与结束标签之间的部分(不含标签)。很显然,任何标签都可创建DOM元素,但只有双标签才会有元素内容,单标签是不会有内容的(因而单标签也叫空元素)。
以下面的4行代码为例。
<body>
这是在<b>浏览器</b>上运行的项目<br>
<img src="logo.png" width="300px">
</body>
这4行代码合在一起构成了body标签元素,该元素的内容是中间的2行(不含<body></body>这对标签)。
再如,<b>浏览器</b>构成了b标签元素,但该元素的内容只有“浏览器”3个字。
<img src=”logo.png” width=”300px”> 构成了img标签元素。虽然看起来很长,但都是设置的标签属性,内容依然为空(千万不要将属性和元素内容混淆了)。
这几个概念一定要理解清楚,因为后面将有非常多的DOM元素操作。
按照标签创建的元素是否自动占有一行来区分,元素又可分为“块级元素”与“内联元素”。
块级元素:占掉页面中的整行长度,最典型、最常用的有div和p。其中,div是没有任何语义的标签,它的作用就是创建区块,一般作为容器使用;p表示段落,可以自动换行。
内联元素:只占元素内容的长度,也就是不会自动换行显示。最典型、最常用的有span,这也是一个没有任何语义的标签,它的作用就是用来标记内容,以方便后期操作。
上述示例中用到的b标签和img标签也都是内联元素。
为说明这种区别,我们将页面body中的代码修改如下。
<body>
<div style="background:red;width:200px;height:50px">这是一个区块</div>
<p>这是一个段落</p>
<span style="color:blue">这是在</span>
<b>浏览器</b>上运行的项目
</body>
其中,加粗的部分是新增加的代码。
第1行是div元素,其内容为“这是一个区块”,style为样式属性,设置该区块的背景(background)为红色,宽(width)、高(height)各为200像素和50像素;
第2行是p元素,没有设置任何样式;
第3行将原来的“这是在”3个字用span标签包起来,使其变为span元素的内容。用上标签之后,就方便对内容进行处理了:这里同样是使用style属性,将其颜色变为蓝色。
运行效果如图1-14所示。
图1-14
很显然,代码中的div和p后面都没有使用<br>,但它们都自动占有一行;使用了span标签和b标签的元素,尽管代码中不在同一行,但运行时仍然顺序排在同一行上。这就是块级元素与内联元素的区别。
随着代码越写越多,“记事本”已经显得越来越不方便。
既然要做项目开发,肯定还是使用专业一些的代码开发工具比较好。这里向大家推荐一款完全免费的代码编辑工具—SublimeText3。该软件在国内各大软件下载站都有下载,本书附赠源码中也有提供。这是v3.3126汉化特别版,一个纯绿色的版本,无需安装,解包后即可直接使用。
请注意,该软件下载解包后会有2个文件夹;一个是x86的版本,运行在32位的windows系统上;另一个是x64的版本,运行在64位的系统上,请根据需要选择。
为什么推荐使用SublimeText3?理由有3个:
该软件下载包只有37MB,而且还是包含两种版本的,解包即可使用。
和其他动辄数百兆的重量级编辑器相比,该软件实在太小巧了。
废话先不说,直观来感受一下它的方便吧。
打开SublimeText3,随便起个名字保存为一个文件,后缀名为html,然后输入内容“!”或者“html:5”,接着按Tab键,你会发现什么?
一个完整的html结构代码已经生成,如图1-15所示。
图1-15
是不是觉得很酷?有了这个代码编写神器,根本就不用再担心html标签中各种烦琐的尖括号了:只要输入标签名称,然后按Tab键,即可自动补齐尖括号等其他内容,双标签还会自动生成开始标签和结束标签。
如果要在双标签中输入内容,可以在标签名称后加上一对花括号。如:div{这是一个区块},输入完成把光标移到最后,再按Tab键即可自动生成代码。
按感叹号默认生成的html结构代码中,自动为html标签设置了lang属性为en。这是一个语言属性,en表示英文。如果需要将其改成中文,可以修改安装目录下的配置文件。
Data\Packages\Emmet\emmet\snippets.json:
将这里的en改成zh-cn,保存即可。重启SublimeText3,再次输入感叹号后按Tab键,生成的代码结构自动就变了。
同理,也可以修改各种快捷键所对应生成的内容,以更加符合自己的快速输入需要。网上有一大堆关于SublimeText3操作技巧的文章,可自行搜索!
SublimeText3可以使用各种第三方插件,以实现更方便的代码编辑操作。
例如,在网上搜索并下载AndyJS2.rar。这是一个JavaScript程序代码的智能提示插件,下载后解包到安装目录的Data\Packages文件夹即可生效。
注意
当使用SublimeText3编辑修改其他文本文件(如使用记事本创建的html文件)时,一定要将该文件的编码方式改为utf-8(菜单项为“文件—设置文件编码—UTF-8”),然后再保存运行,否则可能会导致乱码等各种问题。
在“页面结构”一节中,我们已经简单实现了元素样式的修改。其代码如下:
<body>
<div style="background:red;width:200px;height:50px">这是一个区块</div>
<p>这是一个段落</p>
<span style="color:blue">这是在</span>
<b>浏览器</b>上运行的项目
</body>
这里有两个DOM元素设置了样式:一个是div,另一个是span。两个元素样式的改变都是通过标签自带的style属性实现的。
在B/S项目开发中,用来处理样式的语言被称为CSS。它是页面开发过程中极其重要的一环。许多非常重要的页面显示效果都是通过CSS来完成的。
html中的每个标签元素都拥有style属性,可直接用来设置行内样式,例如上面的代码就是采用的这种方式。
这种方式一般用于对个别元素的微调,因为它仅对当前标签元素有效。当多个元素需要使用同一个样式时,这种方式显然就不太方便了。
这种方式可以写在html文件的头部,也可以写在body里面,完全根据自身需要而定。
例如,将上述两个元素的行内样式改用文档内嵌样式,代码如下:
<style type="text/css">
div{ /*作用于div元素*/
background:red;
width:200px;
height:50px
}
span{ /*作用于span元素*/
color:blue
}
</style>
<div>这是一个区块</div>
<p>这是一个段落</p>
<span>这是在</span>
<b>浏览器</b>上运行的项目
改用这种方式之后,原来body中的style属性就可以被去掉了。因为这种文档内嵌方式中,每个样式的前面已经指定了所作用的DOM元素。
如果仅有一个页面,使用文档内嵌样式还是不错的。但作为一个项目,并不建议这样使用,因为它不能充分发挥CSS代码的重用优势。
外部引用样式就是把写好的CSS代码保存在一个单独的文本文件中,文件扩展名必须是css。
这是最普遍使用的一种方式,它可以将CSS代码和页面代码有效分离,这样就能实现多个页面共享同一个样式。
例如,我们将上述文档内嵌样式代码复制并保存到test.css中(在SublimeText3
编辑器里同时按下Ctrl+N
即可新建文件,然后将CSS
代码复制过来再按Ctrl+S
保存文件名为test.css
)。注意,只复制style元素内容即可,不要复制style标签,因为CSS中并没有标签的概念。
保存完成后的test.css文件内容如图1-16所示。
图1-16
现在即可将页面文件中的style标签元素删除,改用link标签引入此CSS文件:
<link href="test.css" type="text/css" rel="stylesheet">
该代码可放在body或head中。一般来说,都是将其放到head里,因为这仅仅只是引用而已,CSS文件本身并不需要做任何输出。其中:
href为外部资源地址,这里是CSS文件;
type规定被链接文件的类型,这里的值为text/css;
rel定义当前文档与被链接文件之间的关系,这里是外部CSS样式表,即stylesheet。
运行效果与之前两种方式完全相同。
此种方式的最大好处在于:同一个样式表文件可以被多个网页重复引用。同时,一个网页文件也可以引用多个外部样式表(重复使用link元素链接不同的样式表文件即可)。当然,也可以将多个CSS文件全部导入其中的一个CSS文件,这样的话,html只要引用这一个CSS文件就行了。
在CSS中导入其他CSS文件的写法示例:
@import "test_1.css";
其实,在页面中也是可以以导入方式引用外部样式表的,示例代码如下:
<style type="text/css">
@import "test.css"
</style>
这样导入页面的样式表,和以link方式链接到页面的样式表,使用效果是一样的,只是这种用法比较少见而已。
CSS的基本语法为:
选择器 {属性:值;}
建议使用空格以及回车,这样可以让CSS更可读。以下是最好的写法。
选择器 {
属性:值;
}
由此可见,CSS就是由两个主要的部分构成:一个是选择器,就是指定要给谁加样式;另一个就是一条或多条声明。
声明由一个属性和一个值组成,它们之间用冒号分开。如果要定义不止一个声明,则需要用分号将每个声明分开,最后一个声明的分号可用可不用。如:
p {
color:red; /*该行声明将颜色设置为红色*/
font-size:30px; /*该行声明将字体大小设置为30px*/
}
务必注意CSS代码结构的几个特点。
声明都是紧跟着选择器的,并被花括号包含着;
每个声明的属性与属性值之间都是用冒号隔开、用分号结束;
花括号以及分号前后的空格可有可无;
如果属性值名称过长并带有空格,一定要将属性值用引号包含;当存在多个属性值时,属性值之间用空格隔开。
这里讲的语法仅适用外部引用样式和文档内嵌样式,元素内嵌样式直接写在style属性中即可,多个样式之间用分号分开。
按照上面的CSS语法,CSS必须指定选择器,也就是这个样式是作用于哪个DOM元素的。在学习选择器之前,必须先了解几个最常用的标签属性。
在之前的示例代码中,我们已经接触到了一些标签属性,例如:meta标签的charset、name、content属性,link标签的href、rel、type属性,img标签的src、width属性,以及div和span标签的style属性等。
这些属性中,除了style之外,其他属性都是各个标签私有的。以下这3个属性是最常用的全局属性(也就是所有标签元素都有的属性,具体请参考B/S基本知识库中的备查资料之html)。
每个标签都可以设置一个id属性,这个属性说白了就是该标签元素的唯一标识符。有了这个标识符之后,后面就会很方便地对这个元素进行操作。例如:
<p id="abc">这是一个段落</p>
需要注意的是,在同一个页面内,id名称不能重复出现。
在SublimeText编辑器中,可以使用“标签名称#id名称”的方式快速实现代码输入。
例如:p#abc。
class属性用来给标签元素进行归类,同一个页面内的多个标签可以使用相同的class名称。例如:
<p class="abc">段落1</p>
<p class="abc">段落2</p>
<p class="abc">段落3</p>
同一个标签元素还可以使用多个class名称,多个名称之间用空格隔开。如:
<p class="abc def">段落1</p>
在SublimeText编辑器中,可以使用“标签名称.class名称”的方式快速输入。
例如:p.abc.def。
这个属性在之前的示例代码中已经使用过,它用于设置当前DOM元素的行内样式。
关于选择器方面的知识非常多,其使用也非常灵活,目前只需了解几个最基本的选择器即可,其他的留到“B/S基本知识库”中与jQuery选择器一起备查。
就是直接将标签名作为选择器。例如,之前所写的2个CSS样式使用的都是标签选择器:
div{ /*作用于div标签元素*/
background:red;
width:200px;
height:50px
}
span{ /*作用于span标签元素*/
color:blue
}
标签选择器的优点是能够快速为页面中同类型的标签统一设置样式。例如,页面中有多个div或span元素时,它们都将统一使用上述设定的样式。
但缺点也是很明显的:当页面中的内容很丰富时,难免会用多个相同的标签,而这些标签有时并不需要使用相同的样式。
通过指定的id来选择DOM元素,选择方法为:#id。
例如,页面中有如下DOM元素:
<p id="abc">这是一个段落</p>
如果要通过id选择器来选中它,并修改其字体颜色,可以这样设置CSS:
#abc {
color:blue;
}
该选择器使用率非常高,因为同一个页面中各元素的id值不能相同,这种选择方法可以保证唯一性。
通过指定的class类名来选择DOM元素,选择方法为:.class。
例如,页面中有多个指定了同一个class名称的DOM元素:
<b class="abc">加粗</b>
<span class="abc">无</span>
<p class="abc">段落</p>
如果想给class都为abc的同一类DOM元素修改字体颜色,可以这样设置CSS:
.abc {
color: blue;
}
根据DOM元素的属性名称或属性值来选择DOM元素。如下所示。
[href] {
color: blue;
}
以上代码将包含href属性的所有元素设置为红色。注意,如果标签本身具备href属性,但在页面代码中却没有用到这个属性,仍然不会被选择到;同理,尽管有些标签默认不带href属性,但如果也指定了href属性,一样会被选择。
除了属性名称外,还可以以如下方法选择。
根据属性值选择,如:[id="abc"]。
根据属性值开头所匹配的内容选择:[href^="http"]。
根据属性值结尾所匹配的内容选择:[href$=".com"]。
根据属性值所包含的指定字符选择:[href*="baidu”]。
属性值具有多个时,匹配其中的一个:[class~=”edf”]。
这是一种特殊的选择器,用*符号表示,它可以定义文档中所有标签元素的样式。
例如,以下代码将删除页面中所有元素上默认的空白边界:
* {
padding:0; /*这些样式后面有专门介绍*/
margin:0;
}
这种选择器用起来虽然简单方便,但由于是把所有的标签元素都统一配置了,在实际应用中非常少见,因此不常用。
截止到目前,尽管页面可以做了一些样式的改变,但其本质上仍然是静态的,没有任何的交互功能。而要实现交互,就必须使用JavaScript,这是用来构建用户与页面间交互关系的脚本程序语言,简称为JS。
通过上一节知识的学习,我们已经知道,在页面中使用CSS样式共有3种方式。和CSS一样,JS也是同样的“套路”。
此方式将JS代码直接内嵌到页面中。
使用此方式时,必须将JS代码用script标签包起来。示例代码如下:
<script type="text/javascript">
alert('这是弹出的信息!')
</script>
此种写法和CSS的文档内嵌方式完全相同,只不过将style标签改为script,将标签的type属性由text/css改为text/javascript。
其中,alert是JS中的一个功能方法,作用是从页面弹出信息(下一章会专门学习)。
该代码可放在页面的任何位置,head或body中都可。
运行效果如图1-17所示。
图1-17
和CSS的外部引用方式一样,这也是在页面中使用JS程序代码的一种最普遍的方式,因为可以将JS代码和页面代码有效分离,方便实现多个页面共享同一个JS程序代码。
例如,我们将上述JS代码复制并保存到test.js中(在SublimeText3
编辑器里同时按下Ctrl+N
即可新建文件,然后将JS
代码复制过来,再按Ctrl+S
保存文件名为test.js
)。
同样需要注意,只复制script元素内容即可,不要复制script标签,并且务必将保存的文件扩展名设为.js。
保存完成后的test.js文件内容如下所示(只有1行代码)。
图1-18
现在即可将页面文件中的原有script标签元素删除,重新使用script标签引用此JS文件:
<script src="test.js" type="text/javascript"></script>
运行效果与上同。但请注意以下几点:
引用外部JS文件仍然使用的是script标签,而CSS使用的是link标签;
这里使用的是src属性指定引用的文件,而link使用的是href属性;type属性值也各不相同;
这里的script属性只需指定2个,而link属性是3个(多了rel属性);
script是双标签,后面必须加上</script>;而link是单标签,没有此限制;
尽管script双标签中没有任何内容,也不要尝试在这里面再写其他任何JS代码。即使在这里写了JS代码,也不会被执行,除非将src属性删除。当然,如果将src属性删除,就又变成了文档内嵌的代码使用方式。
例如,要想将上述信息在单击区块内容时才弹出,可以直接在相应的DOM元素上设置属性:
<div onclick="JavaScript:alert('这是弹出的信息!')">这是一个区块</div>
这里的onclick是该DOM元素的单击事件属性。当项目运行时,用户单击该元素就会触发所设置的事件。具体到本例来说,只要用户单击红色区块内的任何位置,都会弹出信息。
当然,给这样一个区块设置单击事件有点不伦不类,为了体验更好一点,我们在页面中再添加一个button标签,并设置其单击事件代码:
<button onclick="alert('这是弹出的信息!')">操作按钮</button>
由于事件默认执行的是JS代码,因此属性值中的JavaScript可以被省略,直接alert即可。
除了onclick之外,我们还可以使用ondblclick(双击)、onmouseover(鼠标进入)等。这些都是可以插入页面标签的事件动作属性,下一章还将有专门一节用于讲解“事件”。
同样,我们并不推荐使用这种方式,因为它违背了程序代码和页面代码相分离的原则,不利于代码的重用和后期维护。当然,在项目开发过程中临时使用此种方式来测试程序是可以的,毕竟比较方便。
可能有的读者会问:如果不采用此方式,那我们怎样才能实现这种单击效果呢?毕竟前两种方式都是在页面加载时就直接弹出来的信息,并没有用到单击。
其实,还是和CSS一样的套路,在JS中使用选择器来指定DOM元素就行了。
例如,将test.js中的代码改成这样:
var bt = document.getElementsByTagName('button')[0];
bt.onclick = function () {
alert('这是弹出的信息!');
}
运行效果如图1-19所示(只有单击“操作按钮”才会弹出提示信息)。
图1-19
上述代码中,getElementsByTagName是document中的一个方法,通过它可以获取指定标签名称的DOM对象。那么,什么是方法?document又是什么?以前总是说DOM元素,为什么这里又称作DOM对象?所有的这些疑问,都将在下一章给出答案。
不论是html、CSS,还是JS,我们都可以给代码添加注释。合理地添加注释,不仅便于自己和他人阅读源代码,更是提高代码编写效率、利于团队协作的重要手段。
注释不会对程序的运行造成任何影响。对于被注释掉的代码,系统不会执行,相当于被浏览器忽略了。
html代码中的注释,以“<!--” 开头,以“-->”结束。例如,以下是单行注释:
<!-- 我是被注释掉的内容 -->
这是多行注释:
<!-- <meta name="keywords" content="职场码上汇">
<meta name="description" content="助您轻松打造企业级应用"> -->
CSS中的注释以“/*”开头,以“*/”结束。
注释可以是单行的,也可以是多行的,而且可以出现在代码中的任何地方。
例如,以下是单行注释:
/*width:200px;*/
以下是多行注释:
/*width:200px;
height:50px*/
注意
对于以文档内嵌方式编写的CSS代码,注释如果从style标签开始,就相当于html的注释,使用的是html的注释符号(因为style是html中的标签);如果仅需注释CSS代码,请务必不要从style标签的那一行选择开始!
JS中的代码注释,单行和多行的处理方法是不一样的。
单行注释的写法,是在开始位置加上//。例如:
//这里是注释内容
多行注释以“/*”开头,以“*/”结束,和CSS的注释方法一致。例如:
/*var bt = document.getElementsByTagName('button')[0];
bt.onclick = function () {
alert('这是弹出的信息!')
}*/
注意
在SublimeText编辑器中,不论是html、CSS,还是JS,只要对选中行或选中内容同时按下Ctrl+/或者Ctrl+Shift+/,都会自动按相应的语言规则加上单行或多行注释,再按一次则取消注释。
对于单行注释,上述3种语言都还可以写在代码行的后面以便对当前行做出解释。例如:
html的单行注释:<meta charset="UTF-8"
> <!-- 编码方式必须指定 -->
CSS的单行注释:background:red;
/*背景为红色*/
JS的单行注释:alert
('这是弹出的信息!') //完整写法是在alert
前面加上window
截止到目前,我们已经知道:HTML是一种标记语言,它的作用是建造页面架构;CSS则是处理样式的语言,它的作用是美化页面。这两种语言都是描述性质的,如果要搭建一个静态的页面,学会HTML和CSS就足够了。但是,我们需要做的是企业级的项目开发。要让页面动起来,就必须使用JS。
本章重点学习JS的基础知识。
我们知道,HTML代码必须写在页面文件中(文件扩展名为html或者PHP等);CSS代码可以写在样式文件中(文件扩展名为CSS),也可以写在页面文件中。这两类代码毕竟都是描述性质的,还算不上是真正的程序代码。
和HTML、CSS相比,JS才是真正的编程语言。本章开始就要学习编写各种JS程序代码,这些代码应该写在哪里?如果用script标签直接写在页面文件中,肯定不太合适,因为这样就会和各种HTML标签混在一起,不符合程序代码与页面代码的分离原则。对于新手来说,可能更加容易混淆。
因此,最好的方法是把JS代码写在专门的JS文件中。仍以上一章的代码为例,现在开始学习的各种代码都可以写在如下所示的这个位置。
var bt = document.getElementsByTagName('button')[0];
bt.onclick = function(){
…这里可以填写各种测试代码…
}
当需要测试运行代码效果时,单击页面中的“操作按钮”,即可执行你输入的各种代码。至于这个function是什么,稍后的课程中就会学到。
但是,这种方法并不是最好的。在B/S项目的开发过程中,使用浏览器自带的“控制台”是一种最常用且最有效的代码调试方式。目前市面上各种主流的现代浏览器都自带这种控制台,例如Chrome(谷歌)、Firefox(火狐)、Safari(苹果)、QQ浏览器等。
例如,在火狐浏览器中,单击鼠标右键,选择【查看元素】,即可打开“控制台”,这就是专门为开发者提供的代码调试工具;再如,QQ浏览器右键菜单选择的是【检查】,同样也能调出控制台。
如图2-1所示,左侧是火狐的右键菜单,右侧是QQ浏览器的右键菜单。
在上一章用的很少的JS代码中,我们都是使用alert弹出信息来查看运行效果的,这是一种临时且效率不高的调试方法。使用alert时,每弹出一个信息就要停顿一下,单击确定后才能继续;如果需要输出的测试信息非常多,那岂不是非常麻烦?
图2-1
使用“控制台”之后,JS代码一般就会改用console.log来输出调试信息:随便多少行代码都没问题,都可以一次性输出出来。尤为重要的是,当代码出现错误时,控制台还会给出明确的提示!
例如,我们将之前的单击事件代码修改如下。
var bt = document.getElementsByTagName('button')[0];
bt.onclick = function(){
console.log('这是输出的信息!'); //原来用alert弹出的信息改用console输出
console.log(aaa); //这是随便输入一个变量名aaa,该变量事先并未声明
}
当单击执行页面中的“操作按钮”后,控制台将输出图2-2所示的信息(以火狐浏览器为例)。
图2-2
其中,第1行的“这是输出的信息!”就是使用console.log输出的内容。该行最后面还有个提示,表示这个信息是由test.js中的第4行代码输出的。
第2行输出的内容以红色背景给予突出显示,并提示信息为“aaa is not defined”,后面同时给出了该出错信息来自于test.js的第5行。
如果需要临时测试JS代码,可以在箭头所指处输入。代码输入过程中,将有智能提示;按Shift+回车换行,直接回车则执行代码。如图2-3所示。
图2-3
当然,也可以将你认为存在问题或者需要确认运行效果的代码片段复制过来进行测试。尤其是在学习JS基础语法的过程中,可以直接在这里输入并观察运行效果,非常方便。QQ浏览器的控制台如图2-4所示。
图2-4
除此之外,控制台还提供了查看和编辑页面文档、可视化调整CSS样式、监控网络行为等功能。这些就留待各位在以后的学习过程中慢慢摸索吧!
注意
console语句仅在项目开发时调试使用,它输出的信息不会显示在正常的浏览器页面中;而且很多老版本浏览器并不支持此语句,因此在开发完成并正式上线之前,务必将包含console语句的代码删除或注释屏蔽。
JS程序代码是由一条条的语句构成的。每条语句通常以分号(;)结尾,就像我们现实生活中写信或者写作文时的一句话结尾要加句号一样。其实,JS语言本身并不强制要求语句结尾加分号,但是语句结尾加分号是一个良好的习惯,既可以让代码更易于别人阅读,也可以实现在一行书写多条语句。
编写代码时特别要注意语句的结构层次,合理利用空白。空白是指英文状态下的tab、空格等,这些符号不会影响程序的执行,但却能让代码看起来更清晰。
正常的代码编写可在SublimeText等编辑器中进行,临时性的代码测试可以直接写在浏览器的“控制台”中!
变量就是程序运行过程中用来临时保存数据的容器。
JS是一种弱类型语言,并没有明确的数据类型。也就是说,在声明变量时,不需要像其他语言一样指定变量的类型,其类型由所赋予的值决定。
声明变量只要使用var关键字,后面再接上自定义的一个名字即可(中间用空白隔开)。给变量命名时必须注意以下规范:
第一个字符必须是字母、下划线(_)或美元符号($);
其他字符可以是字母、下划线、美元符号或数字,当然也可以没有;
不能使用JS中已经定义好并有一定用途的关键字,如new、if、int、true、this等;
变量是区分大小写的。比如,x和X就是两个完全不同的变量。
为变量赋值有3种方法,但都必须使用赋值操作符“=”。
var val;
val = '职场码上汇';
var val = '职场码上汇';
var x = y = z = 100;
var x = 100, y = 200, z = 300;
请注意同时给多个变量赋值的方法,如果写成这样:
var x,y,z = 100;
将只有最后一个变量z被赋值,x和y没有被赋值。
x = 100;
虽然JS并不强制要求一定要对变量先进行声明,但为了不至于混淆,建议还是养成先声明、再使用的习惯。
变量声明好之后,将变量名称放在需要的地方即可使用。例如:
var val = 1;
console.log(val);
输出内容为1。
当没有参数时,new关键字和括号只可以省略其一,但不能同时省略。如果需要设置指定的日期或时间,则必须同时使用new关键字和括号:
变量的数据类型由所赋予的值决定。例如:
var val = 1; //val变量是数字型
val = '欢迎来到"职场码上汇"'; //val变量又变成了字符型
var d = new Date(2008,11,20); //2008年11月20日
var dt = new Date(2008,11,20,13,18,20); //2008年11月20日13时18分20秒
以下是几种最常见的数据类型。
基本类型数据共有5种,分别是字符串、数字、布尔值、未定义和空类型。
字符串类型的数据由一个或多个字符组成,并包含在一对单引号或双引号内。换句话说,只要是写在一对引号内的,就可以称作字符串。
比如:"abc1234"或者'abc1234',引号内什么都不写也是可以的,表示空字符串。
当字符串本身含有引号时,外面的引号不能和它相同。例如:
var val = '欢迎来到"职场码上汇"';
实在无法避免时,字符串本身要使用转义符。例如:
var val = '欢迎来到\'职场码上汇\'';
字符串还包括以反斜杠(\)开头的不可显示的特殊字符(字面量)。例如:'\n'表示换行。
表2-1所示是常用的字符字面量:
表2-1
字面量 |
含义 |
---|---|
\n |
换行 |
\t |
制表符 |
\b |
空格 |
\r |
回车 |
\f |
换页符 |
\ |
反斜杠 |
\' |
单引号 |
\" |
双引号 |
没有小数的数字被称为整数,有小数的数字被称作为浮点数或者实数。
数字有个非常特殊的值——NaN,表示非数(Not a Number)。这个值本身属于数字类型,它一般用于数据类型转换失败时。
例如,要把单词blue转换成数值就会失败,因为没有与之等价的数值;NaN的另一个奇特之处在于,它与自身不相等,这意味着下面的代码将返回false:
console.log(NaN == NaN); //输出false
出于这个原因,在数据类型转换时不推荐使用NaN值本身来进行比较,而改用函数isNaN():
console.log(isNaN('blue')); //输出true
console.log(isNaN('666')); //输出false
布尔类型的数据只有两个值:true或者false。
这种类型的数据仅用在一些特定的场合。比如,考试是否及格、性别是男或女、表达式是否成立等只有两种可能的地方(非此即彼)。
此类型数据只有一个值,就是它本身:undefined。当声明的变量未初始化(赋值)时,该变量的默认值就是undefined。例如:
var val;
console.log(val) ; //输出undefined
此类型表示什么都没有,相当于是一个“占位符”,一般用来检测某个变量是否被赋值。
实际上,undefined就是由它派生出来的,JS把它们判断为相等的。例如:
console.log(null == undefined); //输出true
尽管这两个值相等,但它们的含义是不同的。undefined是声明了变量但未对其赋值,而null 则表示尚未存在的对象(至于什么是对象,后面再来学习)。
严格来说,日期时间并不是一种数据类型,但为了和后台数据库中用到的类型相统一,姑且先把它放到“数据类型”中一起学习吧。
例如,以下代码将获取当前的系统日期和时间:
var dt = new Date();
注意
isNaN()同样可以判断指定的变量是否为日期类型。例如:
var d = new Date('2008,11,20');
console.log(isNaN(d)); //false,表明变量d是日期
但是,如果将变量d的值改为:
var d = new Date('2008,13,20'); //这个日期显然是错的,因为没有13这个月份
console.log(isNaN(d)); //true,表明变量d不是日期
为什么用于判断非数的isNaN函数也可以用来判断日期?这是因为,日期型的数据可以转为时间戳,而这个时间戳就是数值。如果指定的变量不能转为数值,那么肯定就不会是日期(具体请参考第4节“本地对象的属性和方法”)。
数组是由一组数据组成的。
// 这里的new关键字可以省略;没有参数时,括号也可以省略。但是,new和括号不能同时省略!
var colors = new Array();
var colors = new Array('red', 'blue', 'green'); //创建的同时赋值
其他创建方法:
var colors = [];
var colors = ['red', 'blue', 'green'];
colors[0] = 'orange';
数组里面存放的数据是有序号的,这些序号可以被称作索引,一般从0开始。如上面的语句就是修改数组中第一个数据的值。
var arr1 = new Array();
arr1[2] = new Array(); //将arr1中第3个值存放为数组
arr1[2][0] = 100;
arr1[2][1] = 120;
console.log(arr1[2][1]); //输出第3个数组元素中的第2个值
对象和数组有点类似,但又有很明显的不同:数组的值是用[]包起来的,而对象用的是{};数组里面的每一个元素都是具体的数据,而对象里面则是一个个的“键值对”(name:value)。
那么,怎么创建Object(对象)呢?
// 这里的new关键字可以省略;没有参数时,括号也可以省略。但是,new和括号不能同时省略!
var zhangsan = new Object();
zhangsan.name = '张三';
zhangsan.age = 18;
zhangsan.info = '张三是一名学生';
对一个对象进行赋值,只需要在对象名后面用点接上一个类似于变量名的单词即可(相当于key键);然后再使用“=”进行赋值。
var zhangsan = {name:'张三',age:18,info:'张三是一名学生'}
这种方法比较简洁,很常用。为便于阅读,一般这样书写代码:
var zhangsan = {
name:'张三',
age:18,
info:'张三是一名学生'
}
如要使用或修改对象中的某个键值,可以这样书写:
zhangsan.name = '李四';
这两种数据类型其实都属于对象,使用typeof输出它们的数据类型都是object。因此,严格来说,它们应该分别被称为Array数组对象与Object自定义对象。
例如,以下这种形式很常见,通过服务器后台获取的返回数据记录一般都是这样的:
var arr = [
{id:1,name:'江苏'}, //第1条记录
{id:2,name:'浙江'}, //第2条记录
{id:3,name:'上海'} //第3条记录
];
console.log(arr[0]['name']); //输出第1条记录中键名为name的值,结果为:江苏
例如,通过后台数据库同时返回的记录总数及记录明细:
var obj = {
nums:3,
rows:[
{id:1,name:'江苏'},
{id:2,name:'浙江'},
{id:3,name:'上海'}
]
};
console.log(obj.nums); //输出3
console.log(obj.rows[0]['name']); //输出:江苏
请务必注意,对象中的“键名”和变量一样,是区分大小写的,如果写成这样就会出错:
console.log(obj.rows[0]['Name']);
运算符typeof可以获取指定变量或值的数据类型。例如:
console.log(typeof '职场码上汇'); //输出string
console.log(typeof 86); //输出number
console.log(typeof NaN); //输出number
console.log(typeof null); //输出object
console.log(typeof new Date()); //输出object
console.log(typeof []); //输出object
console.log(typeof {}); //输出object
对变量或值调用typeof运算符,将返回下列值之一。
undefined:未定义类型
boolean:布尔类型
number:数字类型
string:字符串类型
object:对象类型,包括日期、数组、对象和null类型。
也许你会问:为什么null会返回Object?这实际上是JS最初实现中的一个错误,后来就一直被沿用了。现在,null已被普遍认为是对象的占位符,从而解释了这一矛盾。
任何数据都可通过String()强制转换为字符串。例如:
var s = null;
console.log(typeof s); //输出object
var str = String(s);
console.log(typeof str); //输出string,变为'null'
任何数据都可通过Number()强制转换为数字。例如:
Number(false) //0
Number(true) //1
Number(null) //0
Number([]) //0
Number(undefined) //NaN
Number(new object()) //NaN
Number('1.2.3') //NaN
Number('1.2') //1.2
Number('12') //12
Number(50) //50
日期类型的数据也可以转为数字,而这个数字就是时间戳(具体请参考第4节“本地对象的属性和方法”)。也正是由于这个原因,才可以通过isNaN()来判断指定的变量是否为日期。
var d = new Date('2008,12,20');
console.log(Number(d)); //输出:1229702400000
console.log(Number('2008,12,20')); //输出:NaN
除此之外,还可通过以下两种方法将指定值转换为数字。只不过Number()转换的是整个值,而以下两种方法转换的是部分值,而且仅对字符串类型的数据有效(如果转换其他类型数据则全部返回NaN):
parseInt():把值转换成整数。
parseFloat():把值转换成浮点数。
例如:
parseInt(true); //输出NaN
parseInt('abc'); //输出NaN
parseInt('1234'); //输出1234
其中,parseInt首先查看位置0处的字符,判断它是否为有效数字;如果不是,该方法将返回 NaN,不再继续执行其他操作;如果是,该方法将查看位置1处的字符,进行同样的测试。这一过程将持续到发现非有效数字的字符为止,此时再把该字符之前的字符串转换成数字。例如,如果要把字符串“12345red”转换成整数,那么将返回12345,因为当它检查到字符r时,就会停止检测过程。不过,字符串“22.5”将被转换成22,因为对于整数来说,小数点是无效字符。
parseFloat与parseInt的处理方式相似,从位置0开始查看每个字符,直到找到第一个非有效的字符为止,然后把该字符之前的字符串转换成浮点数。不过,对于这个方法来说,第一个出现的小数点是有效字符。如果有两个小数点,第二个小数点将被看作无效的,parseFloat会把这个小数点之前的字符转换成数字。这意味着字符串“11.22.33”将被解析成11.22。
当要转换的值是至少有一个字符的字符串、非0数字、对象或数组时,Boolean()将返回 true;如果值是空字符串、数字0、undefined或null,它将返回 false。
例如:
Boolean(''); //false - 空字符串
Boolean('0'); //true – 尽管是0,但是非空字符串
Boolean('职场码上汇'); //true - 非空字符串
Boolean(50); //true - 非零数字
Boolean(null); //false - null
Boolean(0); //false - 零
Boolean(new object()); //true - 对象
Boolean([]); //true – 数组
这种转换虽然用得很少,但很有趣,在一些特殊场合还是可以用到的。
例如:
var str = '职场码上汇';
console.log(Array(str));
console.log(Object(str));
浏览器运行后,Array()将变量值变为数组中的一个元素,Object()则将变量值中的每一个字符变成了对象中的一个个键值对。
运算符是用来将变量或者数据进行某种运算的一系列符号,具体包括算术运算符、比较运算符、逻辑运算符和三元运算符。
例如:
var a = 100;
var b = 10;
console.log(a+b);
console.log(a%b); //取余数
注意,如果两个变量的值是字符串,或者 + 运算符前后有任意的一个值为字符串类型,则该运算符默认为字符串连接而非加法运算。例如:
var a = '职场';
var b = '码上汇';
var c = a + b; //将两个字符串连接起来,返回“职场码上汇”
+(加)、-(减)、*(乘)、/(除)运算符可以与等号连用,表示在原来的数值基础上进行运算。例如:
var total = 10;
total = total - 3;
console.log(total); //输出7
total -= 3;
console.log(total); //输出4
所谓的自增,就是在原来的基础上加1;自减就是在原来的基础上减1。其中,自增分为前++和后++,自减分为前--和后--。此运算符一般常用于循环语句中。
自增和自减的道理都是一样的,现仅以自增为例说明如下。
前++:在参与其他运算(操作)的时候,先进行自增运算,此时,变量的值已经立刻发生变化;然后再把变化后的值参与其他操作。
例如:
var a=10;
console.log(++a); //返回11,这时变量a的值为11
console.log((++a)+1); //返回13。自增前a的值是11,自增后变成12,再加1就返回13
console.log(a); //返回12
后++:在参与其他运算(操作)的时候,先使用原来的值,操作完毕后再将变量值自增。例如:
var a=10;
console.log(a++); //输出10。因为a原来的值是10;此语句执行完毕后,a自增变成11
console.log(a++); //输出11。因为a原来的值是11;此语句执行完毕后,a自增变成12
console.log(a); //输出12
console.log((a++)+1); //输出13。因为参与运算的是a原来的值12,此语句执行后,a变成13
console.log(a); //返回13
以下代码是前++与后++的比较:
var a=10;
console.log((++a)+1); //返回12
var a=10;
console.log((a++)+1); //返回11
比较运算符用于比较表达式的值,并返回一个布尔值。
例如:
console.log(11<10); //false
console.log(1<=10); //true
等于运算符在进行判断时会自动进行数据类型转换,将前后数据转为兼容的类型后再进行判断;而全等则要求数值和类型都要相等才会返回true。例如:
console.log(10==10); //true
console.log(10=='10'); //true
console.log(10==='10'); //false
逻辑运算符用于比较两个布尔值(或者是可以返回布尔值的表达式),然后再返回一个布尔值。比较的顺序是先左边、再右边。
左右两边的结果全部为true,那么就返回true,否则就返回false。当左边为true时,会继续比较右边;当左边为false时,则直接返回false。例如:
console.log((10<100) && (200<100)); //false
左右两边只要有一个为true,那么结果就是true。当左边为true时,就直接返回true;当左边为false时,会继续比较右边。例如:
console.log((10<100) || (200<100)); //true
原来为true时,使用!后变为false;原来为false时,变为true。请注意,==(等于)、===(全等)的逻辑非分别是!=、!==,并不是!==、!===。例如:
console.log((!(10<100))); //false
console.log(10!=10); //false
console.log(10!=='10'); //true
使用逻辑运算符&&、||时需要注意的两点:
第一,短路问题。
先看下面的代码,该代码的b输出结果为21,这个是正常的:
var a = true;
var b = 20;
a && ++b;
console.log(b);
但是,如果将a改为false,则b的输出结果仍然是20。它的处理方式是这样的:先判断a的值,如果a为true,那么&&后面的代码会继续执行,因为它要继续看后面的代码是否也是true;如果a是false,那么出于效率考虑就可直接判断出这个逻辑运算符表达式的返回值是false,因此后面的代码被直接忽略。
同样的,逻辑或也存在这样的短路问题。如:
var a = true;
var b = 20;
a || ++b;
console.log(b);
“逻辑或”的处理方式是:只要有一个为true,结果就为true。既然a为true,那么结果肯定是true,因此,||后面的++b就不再执行,b的返回值还是20。如果将a改为false,则b的返回值变为21。
第二,当参与逻辑运算的表达式返回值不是布尔值时,将自动按布尔值的转换规则转换为布尔值后再进行判断并返回内容。其中,数字0、空字符串以及undefined或null类型的数据,在比较时都会以false处理;其他数值(包括空的数组或对象)都以true处理。
例如,以下是使用“逻辑与”进行数值比较的结果:
console.log(20 && 30); //30。当两边都为true时,输出右边内容
console.log(null && 30); //null。一边为true,另一边为false时,输出false的内容
console.log('' && null); //''。两边都为false时,输出左边内容
以下是使用“逻辑或”进行数值比较的结果:
var str; //声明了变量但未赋值,其值为undefined
console.log('0' || 'aa'); //'0'。两边都为true时,输出左边内容
console.log(str || 30); //30。一边为true,另一边为false时,输出true的内容
console.log('' || undefined); //undefined。两边都为false时,输出右边内容
三元运算符即?:,如果?前面的表达式为真,就执行:前面的代码,否则执行:后面的代码。
例如:
var a = 100;
var b = 100;
a<b ? console.log('真的') : console.log('假的'); //输出“假的”
a==b ? console.log('真的') : console.log('假的'); //输出“真的”
注意
当多个运算符在一起运算的时候,为了增加可读性并且确保运算的顺序,可以使用()来增加执行的优先级别。合理的运用括号还可增加代码的可读性。
如果()里面的条件结果为true,则执行后面的那条语句;如果为false就不执行。如:
var a=80;
if(a==100) console.log('恭喜您!考试得到满分!'); //if可以控制后面的一条语句
var a=80;
if(a==100){ //花括号可以控制后面的多条语句
console.log('恭喜您!考试得到满分!');
……
}
var a=70;
if(a==100){
console.log('恭喜您!考试得到满分!');
}else if(a>=85){
console.log('您的考试成绩优秀!');
}else if (a>=60){
console.log('恭喜您!考试过关!');
}
var a=50;
if(a==100){
console.log('恭喜您!考试得到满分!');
}else if(a>=85) {
console.log('您的考试成绩优秀!');
}else if (a>=60) {
console.log('恭喜您!考试过关!');
}else{
console.log('很遗憾!您考试不及格,等待来年补考!');
}
switch语句需要配合case语句使用,例如:
var a=4;
switch(a){
case 1:
console.log('一');
break;
case 2:
console.log('二');
break;
case 3:
console.log('三');
break;
//...
default:
console.log('没有执行任何一个case分支语句');
}
在switch语句中,一旦表达式的值和某条case语句匹配,就会执行那个case后面的语句,并且,在这条case之后的所有case子语句将不再进行判断,全部直接执行。因此,为避免无关语句的执行,一般在每个case语句的后面都会加上break语句。
另外,switch语句内最后面一个分支可以放default,当其他case条件都不符合时,可以执行这条默认语句。如果没有匹配的case语句,同时也没有default,则什么也不执行。
for循环语句的作用是重复执行代码,直到循环条件为false为止。例如:
for(var i=0;i<10;i++){
console.log(i);
}
for语句的执行流程为:首先执行一次初始化语句(给i变量赋值0),执行条件判断语句(i<10),如果为true则执行一次下面的语句块(花括号里面的代码);执行完毕再回到for的圆括号内,执行自增语句(i++),然后再执行条件判断语句(i<10),如果为true则再执行下面的语句块。如此循环。当自增的变量值不满足条件判断语句时,就退出循环。
while循环语句和for循环语句一样,当条件为真时,重复循环,否则退出循环。例如:
var a=10;
while (a>0) {
console.log(a);
a--;
}
与此相类似的还有一种do...while语句。它与while语句的区别在于,do...while至少会执行一次。例如,下面的代码是while循环,不会输出任何结果:
var a = 10;
while (a<10) {
console.log(a++);
}
如果将代码改成这样,就能输出一行信息:
var a = 10;
do{
console.log(a++);
}while(a<10)
也就是说,while语句是先判断后执行,do...while是先执行再判断,因此,do...while要比while多循环一次。
var a=10;
while (a>0) {
if (a==8) {
break;//跳出循环语句
}
console.log(a);
a--;
}
再如:
for(var i=0;i<10;i++){
if (i==1) {
break;
}
console.log(i);
}
注意,以上语句只要语法正确,就可以互相嵌套。
for(var i=0;i<10;i++){
if ((i==1) || (i==3)) {
continue; //跳出这一次循环
}
console.log(i);
}
for遍历语句专门用于遍历数组或对象,请注意不要和前面的for循环语句混淆了。例如:
var zhangsan = {
name:'张三',
age:18,
info:'张三是一名学生'
}
for(var key in zhangsan){
console.log(key + ':' + zhangsan[key]);
}
上述代码中,key是指遍历到的属性名称,zhangsan[key]是指该名称所对应的值。
如果zhangsan变量的值是数组,如:
var zhangsan = ['a','b','c','d'];
则遍历时的key分别是0-3,zhangsan[key]是a-d。
函数是一组可以随时随地运行的语句,它是由这样的方式进行声明的:关键字function、函数名、一组参数,以及置于花括号中的待执行代码。格式如下:
function 函数名(参数) {
各种JavaScript语句
}
也可以这样创建:
var 函数名 = function(){
各种JavaScript语句
};
正常情况下,浏览器在加载script元素后,就会立即逐条执行JS语句,这在上一章第5节“页面信息交互”中已经讲述了。为避免这种情况的发生,可以将语句封装在函数内,只要这个函数没有被执行,那么它里面的所有语句就会一直安静地“躺”在那里。
例如,以下代码就创建了一个名称为“oc”的自定义函数:
function oc() { //给函数起个名字为oc,或者写成:var oc = function(){
console.log('这是输出的信息!');
};
要执行该函数,只要使用这样的代码:oc()。
自定义函数也可以不设置函数名,这样它只能绑定在相应的事件名称上,具体请参考上一章第5节“页面信息交互”中的示例。
函数可以有参数,在调用函数的时候顺便传值过去,以实现更多的功能。其中,定义函数时,圆括号里面的参数被称为“形式参数”,简称“形参”;调用函数时,圆括号里面的参数被称为“实际参数”,简称“实参”。
定义函数时,可以为参数设置默认值。但必须根据参数列表,从右往左给默认值。例如:
var oc = function(a,b=1000,c=10000){
console.log('传过来的参数是:' + a + ',' + b + ',' + c);
};
//调用函数时传数据给函数,多个数据用逗号隔开。如果省略第二、第三个值,则使用默认值
oc(10,100,1000); //按oc函数里的格式输出信息
参数传递有2种方式:按值传递和按引用传递。在学习具体的参数传递方式之前,先来看一下不同类型数据在赋值时的差别。
将基本类型数据变量赋给另外一个变量的时候,其实相当于是重新赋值。也就是说,新赋值的变量和原来的已经没有关系了:
var a = 100;
var b = a;
a = 10;
console.log(a); //a变成了10
console.log(b); //b还是100
将数组或对象赋给另外一个变量的时候,其实只是相当于起了一个别名。例如:
var arr1 = [1,2,3];
var arr2 = arr1;
arr2[1] = '我是修改后的';
console.log(arr1[1]);
尽管修改的是arr2数组中的元素内容,但arr1中的相应内容同样被修改。再如,以下是对象的例子:
var obj1={
name:'zhangsan',
age:18
};
var obj2 = obj1;
obj1.name = 'lisi'; //将obj1对象的name改为lisi
obj1.info = '我是张三'; //在obj1对象中增加属性info
console.log(obj2.name); //obj2的name也变成了lisi
console.log(obj2.info); //给obj1增加的info属性在这里也一样有,返回'我是张三'
这就说明,基本类型数据复制的是值,新旧变量之间不存在任何关系;而数组和对象复制的是内存地址(或者称之为引用),只要是来自于同一个数组或对象的,其中任意一个变量的修改都会同时影响其他变量。
我们再来看看它们在数据比较时的差别。例如,基本类型数据比较:
var a = 100;
var b = 100;
console.log(a==b); //true。这个很正常,因为比较的是值!
数组或对象比较:
var obj1 = {name:'zhangsan'};
var obj2 = {name:'zhangsan'};
var obj3 = obj1;
console.log(obj1==obj2); //输出false。因为比较的是引用地址,数组也同样的情况
console.log(obj1==obj3); //输出true。因为来自同一个地址
正是由于上述问题的存在,函数中的参数传递才会有“按值传递”和“按引用传递”的说法。
按值传递仅对基本类型数据的传递有效。例如:
function oc(a){
a += 10;
console.log(a);
}
var num = 10;
oc(num); //执行函数后输出的内容是20
console.log(num); //num变量仍然为10,说明传递过去的参数与原来的已经断开了
按引用传递仅对数组和对象有效。例如:
function oc(a){
a[3] += 10;
console.log(a[3]);
}
var num = [1,2,3,4,5];
oc(num); //执行函数后,传递过去的数组第4个元素值变为14
console.log(num[3]); //原数组的第4个元素值也是14,说明传递的是引用
函数还可以有返回值,也就是将一个值返回到调用它的地方。需要注意的是,不管函数有没有返回值,函数内部语句执行完就会返回到调用它的地方,然后继续执行。例如:
var oc = function(a,b=1000,c=10000){
return a + b + c;
console.log('计算完毕') //函数内部return之后的语句不会再执行,这个语句就是多余的!
};
var sum = oc(10,100,1000); //函数执行完毕后继续回到此语句,将返回值赋给变量sum
console.log(sum); //输出的信息是1110
根据变量定义在函数外部还是内部的不同,变量的作用域也不同:在函数外面定义的变量被称为全局变量,在变量定义之后的整个页面都可以使用;函数内部的变量被称为局部变量,仅能在该函数内部使用。
例如(请注意和上述有返回值代码的区别):
var oc = function(a,b=1000,c=10000){
var sum; //在函数内部声明的sum变量
sum = a + b + c;
};
oc(10,100,1000);
console.log(sum);
由于上述sum变量是在oc函数内部声明的,因此,函数执行完毕后,console.log无法取得sum变量的值,因而也就无法输出信息。
但是,如果将var sum放到声明oc函数的外部,如:
var sum;
var oc = function(a,b=1000,c=10000)……
这样就能正常输出信息了,输出的内容为“1110”。
如果执行函数时传入的是全局变量,当接收参数名称与全局变量名称相同时,在函数内使用该变量时视同局部变量;当两个名称不同时,仍作为全局变量使用。例如:
function oc(num){ //形参名称为num
num = 80; //这里的num变量被作为局部变量使用,对num的改变不会影响全局变量num
}
var num = 20; //全局变量名称为num
oc(num);
console.log(num); //仍然输出20
现将该函数的接收参数名称改为a:
function oc(a){ //形参名称为a,和全局变量名称不同了
console.log(a); //输出的是接收到的num的值:20
num = 80; //这里的num继续作为全局变量使用
}
var num = 20; //全局变量名称为num
oc(num);
console.log(num); //由于全局变量num的值在函数中被修改,因此输出80
尽管各主流浏览器都提供了功能强大的代码调试功能(控制台),但JS本身还是提供了错误处理语句,可在需要时选用。语法如下:
try{
……
}catch(e){
……
}
其中,try后面的语句块存放你认为可能存在问题的代码。如果该代码没有错误,就会正常执行,catch中的语句块自动被忽略;如果try中的语句出现错误,则会立即终止,并将错误信息以参数形式传递给catch,然后执行catch里面的代码(这个错误信息是个对象,具体包括message、name和number)。
如果无论发生错误与否,都有一些语句需要执行,可放在catch后面的finally子句中。例如:
try {
console.log(aa); //并没有声明过aa这个变量
} catch(e) {
console.log(e.message); //输出错误信息
}finally{
console.log('不管try里的代码是否执行出错,我都是要输出的!');
}
JS是基于对象的语言。在JS中,所有具体的东西都可看成一个对象。例如,在页面文件中,通过标签创建的代码被称为DOM元素,但在JS中需要对这些元素进行操作时,它们就被称为DOM对象。
为什么要强调对象的概念?因为只有对象才会有属性和方法,而事件驱动更是对象的基本特征。
我们所要做的JS程序开发,其实就是根据用户的动作与要求,来给这些对象设置属性、执行方法并编写相应的事件响应代码。把项目中需要用到的这些对象的属性、方法、事件设置好了,一个完整的应用项目基本就完成了,所谓的程序开发就是这么回事。
对象的属性、方法和事件,也可以统称为对象成员。
什么是属性、方法和事件呢?以现实生活中的一辆家用汽车为例来说明。
品牌、型号、颜色、座椅个数、发动机排量等等,就是汽车的属性,它用于描述汽车当前的状态。
有些属性是只读的,也就是不能改变的。例如,对于一辆现成的汽车,其品牌、型号、发动机排量等是你购买时就选定好的,对这辆车来讲已经无法改变。如果强行改变(比如私自改装),不仅交警会按照相关法规来进行处罚,偷偷上路更会带来安全隐患。如果对于电脑程序来说,就是导致各种问题的发生,项目无法正常运行。
而有些属性是可以改变的。比如,速度指针属性:当加速时,指针的值会变大;减速时会变小。油量指针等也是同样的道理。
启动、熄火、转弯、掉头、刹车就是汽车的方法,汽车通过执行这些方法来完成日常工作。当调用方法时,这个对象本身就知道该如何去做来满足需求(比如,汽车在出厂时就已经过各种性能的测试,启动、刹车等都是汽车产品本身就具备的功能)。更确切地说,我们不需要知道它的实际工作情况,而只需要命令它就行了。比如,我们只要转动一下钥匙就可启动汽车,至于这个汽车是怎么启动的,我们无需关心。这就叫方法。
踩油门会加速,而踩刹车会减速……驾驶员的每一个操作,都会被当作一个“事件”,并通过传感装置通知到汽车的控制系统。这个事件如果在JS中怎么完成?就是写代码。在这个事件代码中,可以获取该对象的属性,也可以改变该对象的属性值,还可以直接调用该对象的方法。
为了让大家更好地理解对象的概念,我们以“家用汽车”这个生活中的对象为例,先给它声明一个变量名称car;在JS中,任何对象的属性和方法,都必须通过圆点符号才能调用和访问。
例如,生产厂家在研发汽车时,就相当于新建了一个对象。新建时自然可以给它设置各种属性(在程序中也叫实例化或初始化):
car.name = Fiat; //品牌
car.model = 500; //型号
car.weight = 850kg; //重量
car.color = white; //颜色
当消费者想咨询购买此商品时,就可以通过4S店或其他渠道获取此产品对象的相关属性信息。以代码表示如下:
console.log(car.name + car.model + car.weight + car.color)
这样就能输出该对象的name、model、weight、color等属性信息。
假如我们将该汽车对象的启动方法命名为start、驾驶为drive、刹车为brake,当需要启动汽车时,用程序代码表示就是:
car.start();
这里的括号表示可以传一个参数,也可以不传。假如希望以60码的速度驾驶,就可以传一个参数进去:
car.drive(60);
刹车也是同样的道理:
car.brake();
注意
对象中的属性和方法名称都是区分大小写的!访问对象中的属性时,直接用圆点符号接上属性名称即可;而要使用对象中的方法,不论是否传入参数,方法名称的后面都必须加上圆括号!
当在驾驶汽车的过程中,需要加速或减速时,这就会触发相应的事件。如果以代码的方式表述,可以有两种写法。
car.SpeedNum = car.SpeedNum + 6;
也可以先将原来的指针属性值保存到一个变量中。例如:
var num = car.SpeedNum;
car.SpeedNum = num + 6;
car.Speed(-6);
如果该对象没有这样的方法,那么就需要发挥自己的聪明才智,自行编写代码解决,其中的function函数就可看成自定义的方法。
在上一节学习“数据类型”时,我们知道了有4种类型:基本数据类型、日期时间、数组和对象。如果使用typeof运算符来检查,除了基本数据类型外,后面3种的数据类型都是object,也就是说,日期时间和数组其实都是一种特殊类型的对象。那么,基本数据类型是对象吗?还有function函数呢?
在JS中,对象包括3大类:宿主对象、内置对象和本地对象。
要搞清楚什么是宿主对象,就要先了解JS的3大组成部分:第1部分是ECMAScript核心,也就是我们上一节学习的JS语法规范;第2部分是浏览器对象模型BOM;第3部分是文档对象模型DOM。什么是BOM和DOM?
BOM:中文全称“浏览器对象模型”,英文全称Browser Object Model(简称BOM),它主要用来控制和浏览器本身的一些交互操作。例如,弹出新的浏览器窗口、关闭浏览器、网址导航、页面的前进与后退、用户的会话控制等。
DOM:中文全称“文档对象模型”,英文全称Document Object Model(简称DOM),它是用来处理网页内容的方法和接口。例如,我们在上一章中所写的那些代码都属于DOM的范畴,大家之所以来看这本书,其目的也就是为了开发出企业应用所需要的各种页面文档。正因为如此,之前在示例代码中创建的各种标签元素才被称为DOM元素。
关于BOM和DOM,如果从可视的角度来看,如图2-5所示(以火狐浏览器为例)。
在这个浏览器窗口中,上方的矩形区域(包括菜单、访问地址、前进和后退按钮、窗口最大化、最小化及关闭按钮等)、右侧的垂直滚动条、下方的滚动条及状态栏,这些都是属于BOM的控制范围。不论用户访问的是哪个网址,也不论打开的是什么页面,这些区域都是始终由浏览器控制的,它与访问的网站无关。
图2-5
在图2-5中,中间以椭圆标示出来的区域才是DOM,它随用户访问的页面不同而不同。尽管我们学习的重点是DOM,但它却离不开BOM的环境。例如,我们开发了一个页面A,然后又开发了一个页面B,当从页面A访问页面B时,有时就需要使用BOM中的对象。这就是BOM与DOM的关系。
凡是包含在BOM和DOM中的对象就是宿主对象,这部分对象在后面的章节中还将继续学习。
所谓的内置对象是指由ECMAScript提供且已经被实例化了的对象。这就意味着,开发者在使用这些对象时,无须再对它进行实例化。
本课程重点学习的内置对象主要包括:Global、Math和JSON。
所谓的本地对象是指由ECMAScript提供且未被实例化的对象。这就意味着,开发者要使用这些对象,必须先对它实例化。这些对象就包括我们在上一节所学习的字符串、数字、布尔、日期时间、数组、对象和函数。
按照“本地对象”的定义,它们其实就是我们在第2节所学习到的那些数据类型,如:字符串、数字、布尔值、日期时间、数组和对象等。例如以下代码:
var val = '职场码上汇';
console.log(val.length); //这里的length就是字符串对象的属性,用于返回字符串的长度
这里的变量val是一个字符串类型的数值,如果用typeof来检测,它的类型是字符串而不是对象。既然不是对象,那么从逻辑上来讲它就不应该有属性或方法。但是,上述代码却能正常运行,其输出的值为5,表示已经准确获取了这个字符串数值的长度。
在这个过程中,JS其实已经做了自动转换:先将原始数据实例化为字符串对象,然后再使用字符串对象的属性length输出其长度。这种转换过程完全是自动的,用户根本感觉不到。
为了给大家一个更显性的认识,我们可以再测试一下以下代码:
var val = '职场码上汇';
var s1 = String(val);
var s2 = new String(val);
alert(s1 + ',' + typeof s1 + ',' + s1.length + '\n' +
s2 + ',' + typeof s2 + ',' + s2.length);
运行效果如图2-6所示。
图2-6
其中,变量s1在使用函数String时没有加上new关键字,这时,String就被当成转换函数来使用,不管原始数值是什么内容,都会被自动转为字符串类型的数据,因此其typeof为string;尽管s1的值是字符串,但由于代码中使用了length属性,故JS在内部会自动处理,先将其实例化为对象再输出长度值。
而变量s2由于在包装函数String前面加上了new关键字,s2就变成了对象,因此它的typeof为object;既然已经是object,那么输出其length属性的值当然就是顺理成章的事。
由此可见,在使用String()函数对字符串数据进行处理时,前面加上new关键字,就会被包装为字符串对象;不加new关键字,就是纯粹的字符串转换函数。
如果将上述代码中的val换成另外2种特殊类型的基本数据,其输出效果分别如下。
var val; //没有赋值,也就是undefined类型的数据
var s1 = String(val);
var s2 = new String(val); …alert的代码略,与上同…
则输出的内容为undefined,长度是9。如图2-7左侧所示。
var val = null;
这里仅将val的值设为空,其他代码不变,则输出的内容为null,长度为4。如图2-7右侧所示。
图2-7
很显然,以上2种类型的数据同样可以转换。
常用的本地对象构造函数如表2-2所示。
表2-2
函数 |
说明 |
---|---|
String() |
构造字符串对象 |
Number() |
构造数字对象 |
Boolean() |
构造布尔对象 |
Date() |
构造日期对象 |
Array() |
构造数组对象 |
Object() |
构造自定义对象 |
Function() |
构造函数对象 |
当然,在实际的项目开发过程中,我们一般无须通过new关键字先将其实例化为对象后再使用相应的属性或方法,而是直接简写并把它们当成对象来处理。例如:
var str = '职场码上汇';
var v = 88.88;
var bl = true;
var dt = Date(); //如要指定具体日期,则必须加new关键字
var arr = [];
var obj = {};
var ft = function(){alert('我是函数')}; //这里的function必须小写
上述代码就自动创建了7个类型的本地对象,比较特殊的是日期和函数:当需要指定具体日期时,必须使用new关键字;而函数更是很少使用new。如果要用new的话,则应该这样创建函数对象:
var ft = new Function("alert('我是函数')"); //这里的function第一个字母必须大写
字符串对象的常用属性如表2-3所示。
表2-3
属性 |
描述 |
---|---|
length |
字符串长度 |
字符串的常用方法如表2-4所示。
表2-4
方法 |
参数 |
描述 |
---|---|---|
concat() |
stringX |
连接一个或多个字符串。例如: |
trim() |
移除字符串首尾空白,无参数 |
|
charAt() |
index |
返回指定位置的字符,首字符从0开始 |
indexOf() |
searchvalue, fromindex |
检索字符串。其中: |
lastIndexOf() |
searchvalue, fromindex |
从后向前搜索字符串 |
search() |
substr/regexp |
用于检索字符串中指定的子串,或者与指定正则表达式相匹配的子串。该方法返回的是第一个与指定参数相匹配的子串起始位置,没找到时返回-1 |
match() |
searchvalue/regexp |
该方法和indexOf()、lastIndexOf()、search()相似,但它返回的是匹配值,而不是位置 |
replace() |
substr/regexp, replacement |
将指定子串或与正则表达式匹配的子串,替换为新的字符(replacement) |
slice() |
start,end |
提取从start开始(包括start)到 end结束(不包括end)的所有字符。其中:第1个参数为起始位置,如果是负数,则从尾部开始算起(-1指字符串的最后一个字符,-2指倒数第二个字符,以此类推);第2个参数为结束位置,若未指定此参数,则提取到尾部(为负数时,是从字符串尾部开始算起的位置)。例如: |
substring() |
start,end |
用法与效果与slice()完全相同,只是不接受负数 |
substr() |
start,length |
提取从start(包括start)开始的length个字符。如果没有指定第2个参数,将返回到结尾处的所有字符。这里的start可以是负数,表示从尾部开始算起:-1为最后一个字符,-2指倒数第二个字符,以此类推 |
toLowerCase() |
把字符串转换为小写 |
|
toUpperCase() |
把字符串转换为大写 |
|
toString() |
返回字符串原始数据。此方法一般不用 |
|
split() |
separator,howmany |
把字符串分割为字符串数组。其中,第1个参数是必需的,指定分割位置或标记;第2个参数可选,用于指定可以返回的数组最大长度 |
上述方法中,有2类方法需要重点说明。
这3个方法用来匹配的参数,可以是指定的字符串,也可以是正则表达式。如果是字符串,就比较简单,和其他方法使用起来无异。例如:
var str='职场码上汇';
console.log(str.search('码上')); //返回的是匹配字符的位置。输出:2
console.log(str.match('码上汇')); //返回的是匹配内容。输出:码上汇
console.log(str.replace('码上汇','生涯')); //将匹配内容替换为指定内容。输出:职场生涯
如果匹配的是正则表达式呢?就需要先对正则有所了解(具体请参考B/S基本知识库中的“正则表达式”)。在JS中,RegExp表示正则表达式,它是对字符串执行模式匹配的强大工具。其语法为:
/pattern/
其中,参数pattern是一个字符串,用于指定匹配字符串或正则表达式。这里还可以使用修饰符g、i或m,分别用于指定全局匹配、区分大小写的匹配和多行匹配。
例如,下面的代码:
var str = 'Visit DiyOfficeCode!';
console.log(str.search(/DiyOfficeCode/)); //输出:6
console.log(str.search(/diyofficecode/)); //输出:-1,因为对大小写敏感,所以匹配不到
console.log(str.search(/diyofficecode/i)); //输出:6,因为加了i修饰符,表示不区分大小写
再以match为例,以下是根据指定字符串的匹配效果:
var str = 'Hello world!';
console.log(str.match('world')); //输出:world
console.log(str.match('World')); //输出:null
console.log(str.match('worlld')); //输出:null
console.log(str.match('world!')); //输出:world!
现在再改用正则表达式:
var str = '1 + 2 = 3';
console.log(str.match(/\d/g)); //输出的是数组:1,2,3
这里的\d是元字符,表示查找数字;还同时使用了修饰符g。
如果没有使用g,那么该方法就只执行一次匹配(没找到匹配文本时,返回 null;在本例中,如不使用g,将只输出1,也就是找到第一个匹配文本后即停止)。
如果使用g,将执行全局匹配,把所有符合条件的文本都找到为止,并返回一个数组,用于存放与它找到的匹配文本有关的信息。
如果要将所有的数字都替换为字符a,可以使用replace方法。例如:
console.log(str.replace(/\d/g,'a'))
例如以下代码:
var str = 'How are you doing today?';
console.log(str.split(' ')); //How,are,you,doing,today?
console.log(str.split('')); //H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,?
console.log(str.split(' ',3)); //How,are,you
也可以使用正则表达式来分割。例如:
console.log(str.split(/\s+/));
这种分割效果和str.split(' ')是完全一样的:\s表示空白字符,也就是按空格分割。
数字对象两个最常用的方法如表2-5所示。
表2-5
方法 |
参数 |
描述 |
---|---|---|
toString |
radix |
转换为字符串。参数省略时,默认转换规则为十进制;当然也可以转换为其他进制数(需指定参数为2~36)。例如: |
toFixed |
num |
转为指定小数位数的字符串。参数省略时默认为0。例如: |
布尔值对象最常用的方法只有一个,就是toString。例如:
var v = false;
console.log(v.toString()); //输出字符型的'false'
以下方法都是对具体的日期时间对象进行操作的。除了这些方法之外,还有一种Date对象的静态方法。这方面的静态方法有两个。
例如:var dt = Date();
这里不用带任何参数。即使带了参数也无效,例如:
var dt = Date(2008,11,20); //得到的仍是系统日期时间
除非加上了new关键字,但这就变成了创建新的日期对象。
该方法需采用Date.parse()的形式来调用。例如:
var d = Date.parse('2005,7,9');
由于这里的参数是字符串,写成这样的格式也是可以的:2005-7-9。输出的结果为:
1120838400000
该方法与表2-6中的getTime()作用类似,但表2-6中的方法都必须通过具体的Date对象调用:
表2-6
方法 |
描述 |
---|---|
getDate() |
返回一个月中的某一天(1~31)。例如: |
getDay() |
返回一周中的某一天(0~6),星期日为0。例如: |
getMonth() |
返回月份(0~11),分别表示1-12月。例如: |
getFullYear() |
返回包含世纪值在内的完整年份(4位数) |
getYear() |
1900~1999之间的年份,返回两位数;其他年份4位数 |
getHours() |
返回小时(0~23) |
getMinutes() |
返回分钟(0~59) |
getSeconds() |
返回秒数(0~59) |
getMilliseconds() |
返回毫秒(0~999) |
getTime() |
返回自1970年1月1日至今的毫秒数。例如: |
getTime() |
毫秒数同样可以作为参数来生成日期时间。例如: |
setDate() |
设置对象中的日期(1~31)。例如: |
setMonth() |
设置对象中的月份(0~11) |
setFullYear() |
设置对象中的年份(4位数字) |
setYear() |
该方法很少使用,请用setFullYear()方法代替 |
setHours() |
设置对象中的小时(0~23) |
setMinutes() |
设置对象中的分钟(0~59) |
setSeconds() |
设置对象中的秒钟(0~59) |
setMilliseconds() |
设置对象中的毫秒(0~999) |
setTime() |
以毫秒设置日期时间对象。例如: |
toString() |
把对象转换为字符串。例如: |
toDateString() |
把对象的日期部分转换为字符串。转换结果如:Sat Dec 20 2008 |
toTimeString() |
把对象的时间部分转换为字符串。转换结果如: |
toLocaleString() |
根据本地时间格式,把对象转换为字符串。转换结果如: |
toLocaleDateString() |
根据本地时间格式,把对象的日期部分转换为字符串。如:2008/12/20 |
toLocaleTimeString() |
根据本地时间格式,把对象的时间部分转换为字符串。如: |
数组对象的常用属性如表2-7所示。
表2-7
属性 |
描述 |
---|---|
length |
设置或返回数组中元素的数目。 |
该属性可以获取,也可以重新设置。例如:
var arr = ['a','b','c','d']; //下表中的常用方法也用此数据对象进行操作
console.log(arr.length); //输出4
arr.length = 3; //将长度设置为3,则最后一个元素被截断
注意
如果设置的值比其当前长度小,数组将被截断,其尾部的元素将丢失(如上例,元素d将丢失);如果设置的值比当前长度大,那么数组自动增大,新的元素被添加到数组的尾部,其值为undefined。
数组对象的方法非常多,其中有多个和字符串对象名称相同、用法也相同的方法。例如,indexOf、lastIndexOf、concat等。常用方法如表2-8所示。
表2-8
方法 |
参数 |
描述 |
---|---|---|
concat() |
arrayX |
连接多个值或数组,并返回新的数组。参数可以多个。如: |
join() |
separator |
以指定分隔符将数组连接成字符串。参数省略时用逗号 |
pop() |
删除并返回数组的最后一个元素。如果数组已经为空,则不改变数组,并返回undefined值。例如: |
|
shift() |
删除并返回数组的第一个元素。用法与pop()相同 |
|
push() |
newelementX |
向数组的末尾添加一个或多个元素,并返回新的长度。如: |
unshift() |
newelementX |
向数组的开头添加一个或更多元素,并返回新的长度; 用法与push()完全相同 |
reverse() |
颠倒数组中元素的顺序。如: |
|
sort() |
sortby |
对数组的元素进行排序。例如,将原数据元素顺序打乱: |
slice() |
start,end |
提取从start开始(包括start)到 end结束(不包括end)的子数组。此方法与字符串中的slice用法完全相同 |
splice() |
index,howmany, itemX |
在指定位置添加或删除元素。3个参数的作用分别是: |
toString() |
把数组转换为字符串,并返回结果(返回结果与没有参数的 join方法返回的字符串相同) |
关于sort方法的其他应用举例。
例如,有下面的数组:
var arr = [10,5,40,25,1000,1];
console.log(arr.sort());
由于sort方法默认是按字母顺序对数组中的元素进行排序的(也就是按照字符编码的顺序进行排序),尽管上述数组中的元素值都是数值,但其排序结果仍然是:1,10,1000,25,40,5。
那么,如何让它们按数值大小排序呢?这就需要使用排序参数sortby。该参数是一个比较函数,它需要比较前后两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应传入两个参数,表示相邻的两个值。假如给它们分别以a、b表示(也可以用其他自定义的变量名),其返回值如下:
若a小于b,在排序后的数组中,a应该出现在b之前,这时返回一个小于0的值;
若a等于b,则返回0;
若a大于b,则返回一个大于0的值。
按这样的原理,我们可以编写比较函数如下:
function sortNumber(a,b){
return a-b;
}
然后在sort方法中加入此排序参数。如:
console.log(arr.sort(sortNumber));
则输出结果如下:1,5,10,25,40,1000,这样就达到了我们的预期效果。
实际应用中还有这样的一种情况:如何实现按职位排序?例如:
var arr = ['处长','厅长','副处长','副厅长'];
中文的职务名称是不能直接比较的。在排序之前,将每个职位先生成一个对应的序号就好处理了。例如:
function sn(str) { //这里传入的是职务名称
switch(str){
case '厅长':
return 0;
break;
case '副厅长':
return 1;
break;
case '处长':
return 2;
break;
case '副处长':
return 3;
break;
}
}
然后将排序函数里的return a-b改成:
return sn(a) - sn(b)
这样就是拿每个职务所对应的序号来进行比较,最后输出的结果为:
厅长,副厅长,处长,副处长
这里所说的对象,其实指的是object自定义对象。例如:
var s = {
nums:3,
rows:[
{id:1,name:'江苏'},
{id:2,name:'浙江'},
{id:3,name:'上海'}
],
msg:function () {
console.log(this.nums); //this表示当前的s对象,nums就是s对象的属性
}
};
在这个自定义的对象中,所有的键名都可以称为该对象的属性;当属性值为function函数时,又可以称之为方法。例如,上面这个对象中的nums和rows都是对象s的属性,msg则是对象s的方法。
当在对象内部的方法中需要使用该对象的某个属性时,可以使用this来表示该对象。
既然msg是对象s中的一个方法,那么就可以直接使用以下语句执行该方法:
s.msg(); //执行后将输出值为3
再如,在function中使用以下代码可输出rows属性所包含的元素长度(个数):
console.log(this.rows.length); //rows的属性值是数组,因而可以使用length返回其长度
以上几个知识点非常重要,请务必掌握并认真体会!
除此之外,我们也可以使用Object中的keys方法来返回指定对象中的所有成员。例如:
var arr = Object.keys(s);
console.log(arr); //输出数组:["nums", "rows", "msg"]
console.log(arr.length); //输出数组长度:3
对象中的属性可以重新赋值或删除,也可以判断某个属性是否存在。例如:
s.nums = 5; //将nums属性的值修改为5
delete s.nums; //删除nums属性
console.log(s.nums); //将输出undefined
console.log('nums' in s); //检测对象是否拥有指定属性,这里返回false
当keys方法中放入其他本地对象时,将返回索引数组。例如:
var arr = Object.keys('职场码上汇');
console.log(arr); //输出内容为数组:["0", "1", "2", "3", "4"]
直接使用Object时,将转为对象:
var obj = Object('职场码上汇');
console.log(obj[1]); //转换后的对象以序号作为键名,这里的用[1]将输出:场
本节学习的内置对象主要包括Global、Math和JSON。
Global对象是JS中最特别的对象,因为它根本就是不存在的。如果尝试编写下面的代码,将得到错误信息(提示Global未定义):
var obj = Global;
但我们为什么又称Global对象?这里需要理解一个概念:在JS中并不存在独立的函数,所有的函数都必须是某个对象的方法。例如,我们在第2节“JS基础语法”中,就已经用到了isNaN、parseInt、parseFloat、String、Number等方法。这些方法看起来都像独立的函数,但实际上都是Global对象中的方法,而且远远不止这些。
简而言之,Global对象最常见的就是一些可以直接拿来就用的全局函数,无须像本地对象那样再经过实例化的环节。现在我们再来补充学习一个非常有用而且功能非常强大的全局函数:eval。此函数用于计算指定的字符串,并可执行其中的JS代码。现仅举几例:
例如:eval('var x=10;var y=20;console.log(x*y)');
执行后直接弹出值:200。
再如:
var x = 10;
var y = eval(x+17);
console.log(y); //输出27
例如,以下代码就声明了bl0~bl99的变量,同时给它们赋值0~99:
var str = '';
for(var i=0;i<100;i++){
str += 'var bl' + i + ' = ' + i +';';
}
eval(str);
console.log(bl99); //输出值99。如果没有上一行的eval,那么该变量就是未定义的,程序出错!
例如,有一个类似于对象的字符串:
var str = '{name:"张三",age:28,office:"职场码上汇"}';
这种字符串在后面即将学习的Ajax交互中很常见,有时服务器返回的就是这样的字符串。
如果想获取其中office所对应的键值,最方便的方法就是先将它转为对象。代码如下:
var obj = eval('(' + str + ')');
console.log(obj.office);
console.log(typeof obj); //输出类型为object
为什么eval函数里要添加括号呢?由于收到的字符串是以“{}”开始与结束的,这与JS中的语句块一致。如果不加外层的圆括号,那么eval会将花括号识别为JS代码块的开始和结束标记,并将里面的内容作为语句来执行,这样就会导致错误;一旦加上圆括号,就会让eval函数强制性地将括号内的内容转化为表达式对象。
注意
str变量外面有单引号,表示这是一个字符串。如果把外面的单引号去掉,就直接变成object对象了。
JSON的英文全称为JavaScript Object Notation,它是一种轻量级的数据文本交换格式,可被任何编程语言读取并作为数据格式传递。
既然是个对象,那么它肯定也是用大括号保存的。其语法规则为:
数据为“键:值”对,且键名必须包含在一对双引号中(不能是单引号);
数据由逗号分隔;
方括号保存数组;
花括号保存对象。
仍以刚才的字符串变量str为例,如果改为JSON对象,其写法为:
var obj = {
"name":"张三",
"age":28,
"office":"职场码上汇"
};
由此可见,JSON对象和普通的object对象是很相似的,它们的最大区别就在于:object中的属性名称不需要用双引号,而JSON中必须用双引号。除此以外,还有以下两点要注意。
第一,属性值(除了数字和布尔值之外)也必须用双引号括起来,且必须用双引号(自定义的object可用单引号也可用双引号);
第二,当定义到最后一个数据时,千万不能再用逗号。例如,在上面的代码中如果给“职场码上汇”后面再加上逗号就是错误的(普通的object无所谓)。
以上就是JSON格式的语法规则,非常严谨,稍有不正确都会导致无法解析。
尽管这是一个JSON格式的对象,且键名都加上了双引号,但它毕竟还是一个对象,因此,仍然可以通过键名对其进行访问。例如:
console.log(str.name); //输出:张三
再如:
var obj = {
"name":"张三",
"age":28,
"office":"职场码上汇",
"rows":[
{"id":1,"name":"江苏"},
{"id":2,"name":"浙江"},
{"id":3,"name":"上海"}
]
};
console.log(str.rows[0]['name']); //输出:江苏
该方法用于将一个JSON格式字符串转换为Object。例如:
var str = '{"name":"张三","age":28,"office":"职场码上汇"}';
console.log(JSON.parse(str));
输出结果为:
Object {name: "张三", age: 28, office: "职场码上汇"}
此时,JSON.parse()方法的作用和eval效果相同。但本方法还有个可选参数,这是用来转换结果的函数。例如:
var str = '{"name":"张三","age":28,"office":"职场码上汇"}';
JSON.parse(str,function(k, v) {
console.log(k); // 依次输出对象中每个成员的属性:name、age和office
console.log(v); // 依次输出对象中每个成员的值:“张三”、28和“职场码上汇”
});
再比如:
var str = '{"name":"张三","age":28,"office":"职场码上汇"}';
var result = JSON.parse(str,function(k, v) {
if (k!='office'){
return data[k] = v; //将需要的内容放到一个临时的对象变量中返回
};
});
console.log(result);
输出结果为:
Object {name: "张三", age: 28}
该方法用于将指定的对象、数组等转换为字符串。请注意,本方法并不仅仅局限于JSON格式对象,对于数组和普通的Object都可以转换。例如:
var obj = {
"name":"张三",
"age":28,
"office":"职场码上汇",
"rows":[
{"id":1,"name":"江苏"},
{"id":2,"name":"浙江"},
{"id":3,"name":"上海"}
]
};
var str = JSON.stringify(obj);
console.log(str);
输出内容为:
{"name":"张三","age":28,"office":"职场码上汇","rows":[{"id":1,"name":"江苏"},{"id":2, "name":"浙江"},{"id":3,"name":"上海"}]}
很显然,此输出内容为字符串。
如果将上述代码中的双引号改为单引号,或者将属性名称中的引号去掉,仍然可以正常转换,转换后的字符串内容与此完全相同。
该方法还有以下两个可选参数。
参数为函数时,JSON.stringify将调用该函数,并传入每个成员的键和值。
仍以上面的obj对象为例,执行以下代码:
var str = JSON.stringify(obj,function(k,v){
if (k!='rows') {
return v
}
});
console.log(str);
输出内容为:{"name":"张三","age":28,"office":"职场码上汇"}。
参数为数组时,则仅转换具有键值的成员。成员的转换顺序与键在数组中的顺序一样。如:
var str = JSON.stringify(obj,['age','name']);
console.log(str);
输出内容为:{"age":28,"name":"张三"}。
此参数省略时,转换后的字符串不会换行也不会有分隔符;如果是数字,就表示换行后的每行缩进字符数量;如果是字符串,就会在换行时同时把这些字符串附加上去(可以使用“\n”等转义字符)。不论指定的是数字还是字符,其数量不能超过10个。例如:
var str = JSON.stringify(obj,['age','name','office'],1000);
console.log(str);
以上代码的第3个参数尽管为“1000”,但每行缩进的字符数仍然取最大的10个。输出效果如下:
{
"age": 28,
"name": "张三",
"office": "职场码上汇"
}
如果第3个参数为字符串:
var str = JSON.stringify(obj,['age','name','office'],'这是添加进去用来测试的字符');
console.log(str);
输出的结果仍然取最大的10个字符:
{
这是添加进去用来测试"age": 28,
这是添加进去用来测试"name": "张三",
这是添加进去用来测试"office": "职场码上汇"
}
如果用转义符,则会在原来已经换行的基础上再多一次换行:
var str = JSON.stringify(obj,['age','name','office'],'\n');
console.log(str);
当仅需使用第3个参数而无须使用第2个参数的时候,第2个参数须使用null代替。
该对象用于处理数学任务,其属性和方法还是比较多的,但绝大部分都是和数学相关的专业用法,这里仅学习几个日常开发中常用的功能。
Math对象的属性全部是和数学相关的,例如:
console.log(Math.PI); //输出圆周率(约等于3.141 59)
console.log(Math.E); //输出算术常量e,即自然对数的底数(约等于2.718)
还有和对数、平方根等相关的各种属性,此略。
Math对象的方法如表2-9所示。
表2-9
方法 |
参数 |
描述 |
---|---|---|
abs() |
x |
返回数的绝对值。例如: |
ceil() |
x |
对数进行向上取整。例如: |
floor() |
x |
对数进行向下取整。例如: |
round() |
x |
对数进行四舍五入取整。例如: |
max() |
x,y,…… |
返回x和y中的最高值。参数可以为2个或更多 |
min() |
x,y,…… |
返回x和y中的最低值。参数可以为2个或更多 |
random() |
返回0~1之间的随机数。例如: |
宿主对象包括BOM和DOM。虽然它们都不属于JS语言的核心,但却给我们提供给了很多的操作接口,这些操作接口同样是通过属性和方法来实现的。
BOM是一个很抽象的概念,如果从直观的可视角度来说,它的“兜底对象”其实就是window,因为我们所看到的就是一个浏览器窗口。
现在我们先来看一下BOM结构,如图2-8所示。
图2-8
由图2-8可以看出,window对象是整个BOM的核心:在浏览器被打开后,首先看到的就是浏览器窗口,即顶层的window对象。
在window对象中,又包含如下5个对象。
浏览器信息对象(navigator):该对象提供了有关浏览器的信息,通过它可以识别客户端的浏览器。
屏幕对象(screen):该对象提供客户端屏幕的相关信息。
位置对象(location):该对象提供了与当前窗口中加载的文档有关的信息,以及重定向(跳转)功能。
历史对象(history):该对象提供了与历史清单有关的信息。
文档对象(document):该对象包含了与DOM元素一起工作的对象。
window对象处于对象层次的最顶端,一旦打开浏览器,window对象就创立了。
由于window是顶级全局对象,因此这里的属性可以直接作为全局变量来使用。例如,下表的window属性document,不必写成 window.document,直接用document即可;方法也是同样的道理。
window对象的常用属性如表2-10所示。
表2-10
属性 |
描述 |
---|---|
screen |
对screen对象的只读引用 |
location |
对location对象的只读引用 |
history |
对history对象的只读引用 |
navigator |
对navigator对象的只读引用 |
document |
对document对象的只读引用 |
以上5个属性其实就是window所包含的对象。
window对象的常用方法如表2-11所示。
表2-11
方法 |
描述 |
---|---|
alert() |
显示带有一段消息和一个确认按钮的警告框 |
confirm() |
显示带有一段消息以及确认按钮和取消按钮的对话框 |
prompt() |
显示可提示用户输入的对话框 |
setInterval() |
按照指定的周期(以毫秒计)来调用函数或计算表达式 |
clearInterval() |
取消由setInterval()设置的timeout |
setTimeout() |
在指定的毫秒数后调用函数或计算表达式 |
clearTimeout() |
取消由setTimeout()方法设置的timeout |
open() |
打开一个新的浏览器窗口或查找一个已命名的窗口 |
close() |
关闭浏览器窗口 |
例如,上表中的第一个方法alert之前就被频繁使用过,其完整的写法应该是:
window.alert('这是弹出的信息!');
由于window是顶级全局变量,可以省略,故一般直接写为:
alert('这是弹出的信息!');
如果输出的信息需要换行,可以这样:
alert('这是弹出的信息!' + '\n' + '这是换行');
如图2-9所示。
现重点学习其他几个方法。
图2-9
如果用户单击确定按钮,返回true;否则返回false。在用户单击确定或取消按钮把对话框关闭之前,它将阻止用户对浏览器的所有输入。在调用该方法时,将暂停对JS代码的执行,在用户做出响应之前,不会执行下一条语句。仍以上一章的“操作按钮”代码为例,如果改用confirm方法,可以这样:
var bt = document.getElementsByTagName('button')[0];
bt.onclick = function () {
var r = confirm('您确定要退出吗?');
if (r) {
console.log('您点击了确定按钮!');
}else{
console.log('您点击了取消按钮!');
}
}
运行效果如图2-10所示。
图2-10
语法为:prompt(text,defaultText)。
这两个参数都是可选的。其中,text表示要在对话框中显示的文本,defaultText为默认的输入文本。
如果用户单击的是取消按钮,则返回null;如果单击确认按钮,则返回对话框中当前输入的文本。
和confirm一样,在用户单击确定或取消按钮关闭对话框之前,将阻止用户对浏览器的所有输入。在调用该方法时,将暂停对JS代码的执行。在用户作出响应之前,不会执行下一条语句。
例如,将上面的单击事件代码改为:
var name = prompt('请输入姓名:');
if (name!=null && name!='') { //如果输入的内容不为null也不为空字符串
console.log('欢迎,' + name);
}
运行效果如图2-11所示。
图2-11
其中,setInterval按照指定的周期(以毫秒计)来调用函数或计算表达式,clearInterval则用来取消由setInterval方法设置的计时器。
例如,继续将上面的事件代码改为:
setInterval('bt.innerHTML = new Date()',50); //bt是DOM对象,innerHTML是它的属性
该代码的意思是,每隔50毫秒就将按钮的DOM元素内容修改为当前的系统时间。原来的按钮内容如图2-12所示。
图2-12
单击该按钮后将执行上面修改后的计时器代码,按钮内容还会不断变化,直观的感觉就是该按钮变成一个“秒表”了,如图2-13所示。
图2-13
setInterval方法也可以调用函数。例如,先自定义一个函数:
function clock() {
bt.innerHTML = new Date();
}
然后将上述事件代码改为:setInterval('clock()',50)
,运行效果是一样的。
当不需要计时时,可使用clearInterval方法。为方便取消计时,在使用计时器时,一般先将它赋给一个变量。例如:
var int = setInterval('clock()',50);
要取消计时,只需clearInterval(int);即可。
源代码文件中还增加了一个指定时间后取消计时的示例代码,具体请查看源文件。
这两个方法也是用于计时的,但它们是在指定的毫秒数后才调用函数或计算表达式,且仅执行一次。如要多次重复执行,需使用setInterval方法或者让代码自身再次调用setTimeout。
现将以上单击事件代码分别进行以下几种修改,来观察setTimeout的执行情况。
示例1:在指定的时间后才弹出信息
setTimeout("console.log('已经过了5秒钟!')",5000);
当单击操作按钮时,该警告框并没有立即弹出,而是等了5秒之后才弹出。
示例2:多次执行setTimeout方法
setTimeout("bt.innerHTML = '时间过去了2秒!'",2000);
setTimeout("bt.innerHTML = '时间过去了4秒!'",4000);
setTimeout("bt.innerHTML = '时间过去了6秒!'",6000);
单击执行后,该按钮的DOM元素内容会随着时间的流逝依次显示“过去了2秒、4秒或6秒”。
示例3:无限循环计时(秒表效果)
先在单击事件的外面设置计时函数:
var c = 0;
function timedCount(){
bt.innerHTML = c;
c += 1;
setTimeout("timedCount()",1000);
}
然后在单击事件中执行此函数:
timedCount();
这样即可实现无限循环的秒表计时效果。
请注意,这个示例用到了递归函数,也就是在函数体内继续调用本函数。
上述代码是一种传统的写法,一旦函数名称timedCount发生改变,函数体内通过setTimeout方法调用的函数名就要修改。因此,实际应用中,一般使用arguments.callee来获取当前调用函数的本体。其中,arguments是function中的一个内置对象。
修改后的代码如下:
var c = 0;
function timedCount(){
bt.innerHTML = c;
c += 1;
setTimeout(arguments.callee,1000);
}
运行效果与之前的代码完全相同。
示例4:时钟效果
先在单击事件的外面设置计时函数:
function clock() {
var today = new Date();
var h = today.getHours(); //时
h = (h<10) ? '0'+h : h;
var m = today.getMinutes(); //分
m = (m<10) ? '0'+m : m;
var s = today.getSeconds(); //秒
s = (s<10) ? '0'+s : s;
bt.innerHTML = h + ':' + m + ':' + s;
setTimeout('clock()',500)
}
然后在单击事件中执行此函数:
clock();
单击操作按钮后,该按钮的时钟效果如图2-14所示。
图2-14
当要结束计时时,可另外加个按钮,并执行clearTimeout方法。但前提是,需将setTimeout计时器赋给一个变量,然后再将该变量作为clearTimeout的参数进行清除。
关于clearTimeout,具体请参考clearInterval方法,此略。
这两个方法分别用于打开和关闭浏览器窗口。
打开窗口语法:window.open(url,name,features,replace);。
其中,url用于声明要在新窗口中显示的文档URL。如果它的值是空字符串,那么新窗口就不会显示任何文档;
name用于声明新窗口的名称,可使用数字、字母和下划线。如果该参数指定了一个已经存在的窗口,那么就不会再创建新窗口,而是返回对指定窗口的引用(features将被忽略);
features用于声明新窗口的显示特征。如果省略该参数,将采用标准特征创建新窗口;
replace用于规定装载到窗口的URL是否在窗口的浏览历史中创建一个新条目。如果为true,将替换浏览历史中的当前条目;false将在浏览历史中创建新条目。
以上4个参数全部是可选的。例如,将之前的单击事件代码修改如下:
window.open(); //打开一个新的浏览器窗口,不含任何文档内容
window.open('http://www.diyofficecode.com'); //打开指定url地址的浏览器窗口
window.open('http://www.diyofficecode.com','new');
window.open('http://www.163.com','new');
执行单击后,将同时打开3个新窗口:第1个窗口不含任何文档内容,第2个是打开指定url地址的浏览器窗口,第3个是名称为new的窗口。
尽管代码中是打开4个窗口,但由于第3、第4行代码指定的窗口名称相同,因而会引用之前的同名窗口(在原来的名称为new的窗口中继续打开www.163.com),因而最终打开的是3个窗口。
需要注意的是,在后面即将学习的DOM对象中也有open方法,但它们的功能完全不同。为避免混淆,这里在使用open方法时,最好加上window的对象名称。
再如,打开窗口时要求其按照指定的特征来显示:
window.open('','','width=200,height=100');
这样将打开一个内容为空的新窗口,其宽为200px,高为100px。
与之相关的其他显示特征还有:titlebar(是否显示标题栏)、menubar(是否显示菜单栏)、toolbar(是否显示工具栏)、status(是否显示状态栏)、location(是否显示url地址栏)、scrollbars(是否显示滚动条)、resizable(是否可调节窗口尺寸)、fullscreen(是否使用全屏模式显示)、channelmode(是否使用剧院模式)等。
例如下面的代码:
window.open('http://www.diyofficecode.com','new','width=200,location=no,menubar=no');
但遗憾的是,这些窗口特征参数在不同的浏览器,其兼容性差别巨大,一般都会出现部分甚至全部无效的情况。因此,请在window对象的open方法中慎用这些特征参数。
如果需要通过JS程序关闭浏览器窗口,那么在打开窗口时应先给其声明变量,然后再对这个指定的窗口变量执行关闭操作。例如:
var myWindow = window.open();
myWindow.close();
该对象用于识别客户端浏览器,其常用属性如表2-12所示。
表2-12
属性 |
描述 |
---|---|
appCodeName |
返回浏览器的代码名 |
appName |
返回浏览器的名称 |
appVersion |
返回浏览器的平台和版本信息 |
cookieEnabled |
返回浏览器是否启用cookie |
onLine |
返回是否处于脱机模式 |
platform |
返回运行浏览器的操作系统平台 |
userAgent |
返回浏览器的用户代理字符串 |
例如,将之前示例中的单击事件代码修改如下:
var x = navigator;
var str = '浏览器代码名:' + x.appCodeName + '\n' +
'浏览器名称:' + x.appName + '\n' +
'浏览器平台版本信息:' + x.appVersion + '\n' +
'是否启用Cookie:' + x.cookieEnabled + '\n' +
'是否处于脱机模式:' + x.onLine + '\n' +
'客户端操作系统:' + x.platform + '\n' +
'用户代理字符串:' + x.userAgent;
alert(str);
运行效果如图2-15所示。
图2-15
通过该信息可以很容易地就判断出,客户端的浏览器是火狐(Firefox)。其中最重要的就是userAgent属性(不同的浏览器,该属性值都不一样)。再如,QQ浏览器返回的userAgent属性值如下:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.3226.400 QQBrowser/9.6.11681.400
这里就包含了QQBrowser字样。
该对象存放着浏览器显示屏幕的相关信息,JS程序可利用这些信息来优化页面文档的输出,以达到用户的显示要求。screen对象有很多的属性,现仅列几个兼容性好而且很常用的属性,如表2-13所示。
表2-13
属性 |
描述 |
---|---|
height |
返回显示屏幕的高度 |
width |
返回显示屏幕的宽度 |
availHeight |
返回显示屏幕的高度(除 Windows 任务栏之外) |
availWidth |
返回显示屏幕的宽度(除Windows 任务栏之外) |
例如,输出显示屏幕的像素高度:
console.log(screen.height);
该对象存放着当前窗口所加载文档的web地址信息。
假如url地址为:http://www.diyofficecode.com:1234/test.html#part2,请以此地址对比查看表2-14所示的各常用属性的值:
表2-14
属性 |
描述 |
---|---|
href |
设置或返回完整的URL地址 |
hostname |
设置或返回当前URL的主机名 |
pathname |
设置或返回当前URL的路径部分 |
host |
设置或返回主机名和当前URL的端口号 |
port |
设置或返回当前URL的端口号 |
protocol |
设置或返回当前 URL 的协议 |
hash |
设置或返回从井号(#)开始的锚部分 |
search |
设置或返回从问号(?)开始的查询部分 |
表2-15
方法 |
描述 |
---|---|
assign() |
加载新的文档 |
replace() |
用新的文档替换当前文档 |
reload() |
重新加载当前文档。该方法有个可选参数: |
除了上述3种方法外,重新设置本对象的href属性也可以在当前窗口加载指定文档。例如:
location.href = 'http://www.diyofficecode.com';
运行效果与assign方法完全相同。
history对象的常用属性如表2-16所示。
表2-16
属性 |
描述 |
---|---|
length |
返回浏览器历史列表中的URL数量 |
例如:console.log(history.length);
history对象的常用方法如表2-17所示。
表2-17
方法 |
描述 |
---|---|
back() |
加载history列表中的前一个URL,相当于执行浏览器中的后退按钮 |
forward() |
加载history列表中的下一个URL,相当于执行浏览器中的前进按钮 |
go() |
加载history列表中的某个具体页面 |
例如,以下代码的执行效果与单击后退按钮的执行效果是一样的:
history.back();
而下面这行代码与单击两次后退按钮执行的操作效果是一样的:
history.go(-2)
所谓的DOM对象,通俗地说,就是日常看到的网页页面。它是BOM的重要组成部分(或者说是分支)。
上一章“初步认识B/S”中所涉及的知识就全部是DOM中的内容,并在第2节绘制了一个“页面结构”,如图2-16所示。
图2-16
通过该示意图可以清晰看出,一个完整的文档(Document)是由一个个具体的节点元素(Element)构成的。当在JS中需要对这些节点元素进行操作时,它们就被称为DOM对象;当需要对整个页面进行操作时,则整个文档就变成了DOM对象。
因此,不论是整个文档还是其中的某个元素,在JS中都可统称为DOM对象。
浏览器载入的每个页面文档都会成为document对象。通过该对象,可以帮助我们在JS中对该文档中的所有元素进行操作。
document对象的常用属性如表2-18所示。
表2-18
属性 |
描述 |
---|---|
title |
返回当前文档的标题(页面标题) |
url |
返回当前文档的URL地址 |
domain |
返回当前文档的域名 |
例如:
console.log(document.title); //输出页面文件head元素中的title设置内容
console.log(document.url); //url地址
console.log(document.domain); //如果是本地文件(url以file:///开头),输出为空
document对象的常用方法如表2-19所示。
表2-19
方法 |
描述 |
---|---|
open() |
打开新文档,并擦除当前文档的内容;必须和close方法成对使用 |
write() |
向文档写入内容 |
close() |
关闭open方法打开的文档,并强制显示所有写入的内容 |
getElementsByTagName() |
返回带有指定标签名的数组 |
getElementsByName() |
返回带有指定名称的数组 |
getElementById() |
返回对拥有指定 id 的第一个对象的引用 |
例如,打开一个新文档,并向文档中写入内容:
document.open(); //window对象的同名方法还可用于打开指定url的浏览器窗口
document.write('职场<b style="color:red">码上汇</b>');
document.close(); //此方法必须和open成对使用
上述代码执行后,当前文档将写入新内容。图2-17左侧所示为初始页面,右侧为执行代码后的页面。
图2-17
在初始页面中,单击“操作按钮”,将执行上述代码。该代码在打开新文档的同时,擦除当前文档内容,并写入新内容,最后关闭文档。在新的页面中,按返回键还是可以回到原来的文档页面的。
open方法有2个可选参数。
mimetype:用于指定文档类型,默认值是text/html。
replace:当此参数设置后,新文档将从父文档继承历史条目。
例如,将上述代码的第一行改为:
document.open('text/html','replace');
这样在打开新文档后就无法通过后退返回到原来的页面了。
write方法可以同时写入多个参数。例如,下面的代码就写入了3个参数:一个页面标题、一行文字和一个按钮:
document.write('<title>新文档标题</title>','职场<b style="color:red">码上汇</b><br>',
'<button onclick="confirm(\'您确定要退出吗?\')">点击</button>');
执行效果如图2-18所示。
图2-18
很显然,标题已经变为指定的内容,而且写入的按钮可以正常执行JS代码。
在JS中,要对指定的DOM对象进行操作,首先必须要选定该对象。
例如,从上一章到现在我们一直在使用的“操作按钮”单击事件,就是使用docment对象中的getElementsByTagName方法来实现指定对象的。示例代码如下:
var bt = document.getElementsByTagName('button')[0];
bt.onclick = function () {
…各种代码…
}
这里的getElementsByTagName()方法指定返回的是带有button标签的数组,尽管页面中只有这一个元素,但由于返回值是数组,后面还是必须加上[0],用于指定第一个button标签对象。
与该方法功能相近的还有2个方法。
getElementsByName():此方法根据指定名称name来返回对象数组,它一般用于指定表单元素对象。也就是说,页面中的相关DOM元素必须要设置name属性才有可能被选中。此方法的返回值不唯一,后面需加上序号以指定具体的某个元素对象;
getElementsById():此方法根据指定id来返回第一个元素对象。原则上,同一个页面中的DOM元素在设置id属性时是不能重复的,但为了避免意外情况的发生,此方法还是仅返回指定id号的第一个元素对象。因此,该方法返回的对象是唯一的,后面无须再加序号。
在DOM页面中,每一个标签所创建的节点都被称为DOM元素。当在JS中需要对指定的DOM元素进行操作时,它们就变成了DOM元素对象。
如上例中的代码,通过以下语句就将“操作按钮”指定为DOM元素对象,其变量名为bt:
var bt = document.getElementsByTagName('button')[0];
假如要想修改该对象的显示内容,可使用innerHTML属性。该属性在学习BOM的时候已经使用过,它的作用就是用来返回或设置指定DOM元素对象的内容。
例如,将“操作按钮”的显示内容修改为“新的操作按钮”:
bt.innerHTML = '新的操作按钮';
以上代码在控制台输入完成并回车执行后,页面中的按钮显示内容立即发生改变,如图2-19所示。
图2-19
此时,如果使用下面的代码输出其内容,将输出“新的操作按钮”:
console.log(bt.innerHTML);
同样的道理,如果要给该元素设置单击事件,只需将function赋值给onclick属性即可:
bt.onclick = function () {
this.innerHTML = '新的操作按钮';
}
这里的this就是指的当前所操作的DOM对象。
上述单击事件代码和以下在页面中设置单击属性效果是一样的:
<button onclick="this.innerHTML='新的操作按钮';">操作按钮</button>
需要特别说明的是,在JS的原生环境中,涉及DOM元素对象的属性、方法非常多,使用起来也相当烦琐,而且还存在比较严重的浏览器兼容问题。
为简化JS程序的开发,一些JS程序库诞生了。这些程序库封装了很多预定义的对象和实用函数,可帮助使用者轻松建立高难度的客户端页面。
在接下来的学习中,本书将选用JS程序库中的典型代表也是JS的标配工具—jQuery来操作各种对象,JS的原生方法将不再学习。但之前所讲的这些基础知识还请务必掌握。
为避免混淆,本节先将之前学习的JS对象做个总结。
JS中包含的对象如图2-20所示。
图2-20
其中,BOM的顶级托底对象就是window,在window中又包含navigator(浏览器信息对象)、screen(屏幕对象)、location(位置对象)和history(历史对象)。为不至于将组织结构图搞得太复杂,图2-20将BOM中的5个对象全部并列排在了一起。
后面学习的重点就是各种对象的操作。请大家务必对照此结构图,在头脑中建立起一个总体的框架概念,千万不要混淆了!尤其要搞清楚一点:
DOM对象是用于页面文档操作的对象,它只是JS众多对象中的一种。
顾名思义,所谓的jQuery对象就是在jQuery中进行操作的对象。要使用这些对象,首先要做的必须是引入jQuery程序库。
jQuery库的官方下载地址为:http://jquery.com/download。
正常情况下,软件产品是越新越好,因为最新版代表了最先进的技术理念以及当前的最高技术水平。但对于jQuery来说,却要根据自身情况来定。
jQuery自2006年1月份推出以来,已经发布了3个系列的产品。
1.x系列:该系列兼容性比较好,是目前用户使用最多的版本,例如1.11版、1.12版等。我们开发出来的项目不是留着自己欣赏的,而是要提供给用户使用的,当然要考虑最大可能的兼容性。
2.x系列:该系列版本不再支持IE6、IE7、IE8,目前官方只做bug维护,不再进行功能更新。
3.x系列:该系列版本自2016年6月份开始推出,是目前的最新版本,它同样不兼容IE6、IE7、IE8,只支持最新的浏览器。
不论是哪个系列的版本,官方都提供2种类型的JS文件,我们只要下载其中的jquery.min.js即可。这是一个经过压缩的版本,可以直接在生产环境下(项目发布时)使用,体积更小,引入更快(本节附带的源码压缩包中已经包含此文件,大家可以不用再到官网下载)。
有了此文件之后,在页面头部使用script标签将其引入即可:
<script type="text/javascript" src="jquery.min.js"></script>
该文件引入之后,jQuery的环境就已建立。再次编写JS代码时,既可使用JS的原生方式开发,也可使用jQuery方式开发。
jQuery的重点是用来处理DOM,它和BOM的关系非常小甚至可以说基本没有关系。这就意味着,JS原生环境中的所有BOM对象,原来怎么用,现在还是怎么用;原来怎么称呼,现在还是怎么称呼,唯一需要改变的是JS原生环境中的DOM对象。
要将JS中的DOM对象转为jQuery对象,只需将原来的DOM对象作为参数赋值给$()函数即可。在这里,$()相当于是一个jQuery对象的“制造工厂”。其中,$就是jQuery的简写形式。
例如,上一节的示例代码采用的是JS原生环境中的写法,这里的变量bt被称为DOM对象:
var bt = document.getElementsByTagName('button')[0];
bt.innerHTML = '新的操作按钮';
现在给该DOM对象使用$()函数:
var bt = document.getElementsByTagName('button')[0];
bt = $(bt); //把$换成jQuery是一样的效果
bt.html('新的操作按钮');
虽然变量名称不变,但其性质已经变了,这时的bt已经变为jQuery对象。既然是jQuery对象了,那就只能使用jQuery中的方法(JS原生环境中的innerHTML属性对于jQuery对象而言已经无效),因此,相应的代码也必须做出修改(详见加粗的部分,html是jQuery中用于获取或设置元素内容的方法)。
如果将前面两行代码连在一起,也可以写成这样:
var bt = $( document.getElementsByTagName('button')[0] );
bt.html('新的操作按钮');
经测试,上述代码都能正常运行,且效果完全一样。
其实,在jQuery中,上述代码还有种更简洁的书写方式:
var bt = $('button'); //因为页面中只有一个button标签元素,jQuery中可以直接这样写
bt.html('新的操作按钮');
这种写法是根据页面中的标签名称来选择DOM元素的。如果该元素带有id属性,通过id选择更简单:
var bt = $('#abc');
bt.html('新的操作按钮');
如果使用JS的原生代码来写,通过id选择元素的代码是这样的:
document.getElementsById('abc')
哪一种更方便、简洁?当然是jQuery了!
综上所述,所谓的jQuery对象就是通过$(或者jQuery)对DOM对象进行包装后所产生的对象。DOM对象只能使用JS原生的属性和方法,jQuery对象只能使用jQuery中的方法,两者不能混用。
好在,关于DOM对象,我们之前只学习了document的常用属性和方法,还没有学习每个具体的节点元素(只提到了innerHTML属性)。后面我们将直接学习jQuery对象的操作,这样就可最大限度地避免混淆。
DOM对象与jQuery对象,两者的属性和方法是不能混用的。但是,如果对jQuery对象提供的方法不熟悉,或者jQuery没有封装想要的方法而不得不使用DOM对象的时候,可以将它们进行相互转换。
这个在前面已经学习过,只需在原来的DOM对象外面加个$符号即可。
当然,这里还可以使用更简洁的方法:$(DOM选择器)。例如:$('button')、$('#abc')等。
要将jQuery对象转为DOM对象,有两种处理方法。
第一种方法是使用[index]。jQuery是一个类似于数组的对象,它可通过加上[index]的方法得到相应的DOM对象。例如:
var bt = $('#abc'); //变量bt是jQuery对象,要输出其对应的DOM元素内容,需使用html方法
alert(bt.html());
bt = bt[0]; //变量bt现在已转为DOM对象,需使用JS中的innerHTML属性输出DOM元素内容
alert(bt.innerHTML);
第二种方法是使用get(index)。
例如,将上述代码的中的 bt = bt[0] 改成 bt = bt.get(0) ,效果是一样的。
在学习jQuery对象的时候,$()函数是作为jQuery对象的“制造工厂”来使用的。其实,$()函数的作用远不止于此。根据传入参数的不同,该函数实现的功能也不一样。从这个角度来说,$()是进入jQuery精彩世界的入口。
$()可传入的参数包括4种。
传入的htmlElement对象可以转为jQuery对象。此前已经学习。
传入的DOM元素以字符串形式表示(例如,'button'、'#abc'等),这种选择DOM的方式也被称为选择器(和之前的CSS选择器是一个道理),被选择的DOM元素就转为DOM对象。该参数此前已学习。
传入的html代码可作为页面中的新元素进行处理。例如,当需要在页面中增加DOM元素时,常规的做法是直接在页面文件中添加代码,但这种做法是静态的。如果需要通过JS方式在页面中动态添加元素,就可以使用$()函数来生成。
例如:
$('<br><br><a href="http://www.diyofficecode.com">打开链接</a>').appendTo('body');
或者写成这样也可以:
var m = $('<br><br><a href="http://www.diyofficecode.com">打开链接</a>');
m.appendTo('body');
运行效果如图2-21所示。
图2-21
上图中的矩形区域就是动态添加的DOM元素,其运行效果如箭头所示,在页面中单击将打开“职场码上汇·资源站”所对应的网址。
在这个例子中,$()函数传入的参数为:
<br><br><a href="http://www.diyofficecode.com">打开链接</a>
这是一段html代码,前面两个<br>表示两次换行,后面接着的是一个a标签元素。a标签在页面开发中使用频率非常高,建议大家在方便的时候多参阅一下本书后面的“B/S基本知识库”。
新的元素创建之后,还要把它添加到页面中才能显示出效果,本例使用的是appendTo方法。象这种对页面元素所进行的动态处理,就是所谓的DOM元素操作。关于这方面的知识,同样请参考本书后面的“B/S基本知识库”。
传入的函数将在页面就绪后执行。例如:
$(function(){
…各种jQuery代码…
});
由于jQuery操作的是DOM元素,因此,当需要使用jQuery进行操作时,最好的方法就是等页面元素全部就绪之后再执行,只有这样才能有效避免各种意外情况的发生。正因为如此,各种商业化的jQuery应用项目,其代码都是写在$()函数所传入的function中的。
注意
本书所提供的示例源码,有时在外围加了$(function),有时未加。当需要在正式项目中引用其中的代码时,请务必加上$(function),以保证项目运行的安全。
jQuery对象同样有自己的属性、方法和事件。
jQuery对象最常用的属性就是length,用于返回该对象中包含的DOM元素数目。该属性的重要作用之一在于判断指定的DOM元素在页面中是否真实存在。
仍以上一节的页面代码为例,假如我们想选择id为a1的标签元素:
var m = $('#a1');
很显然,页面中并没有id为a1的标签元素,这种选择是无效的。但是,如果将变量m转换为布尔值:console.log(Boolean(m))
,却发现输出的内容竟然为true!
这就是说,不论页面中是否存在某个指定的DOM元素,一旦把它作为参数赋给$()函数之后,该jQuery对象就已经有了。由此,我们也特别提醒大家:当要检查某个元素在页面上是否存在时,不能简单地使用if(m)这样的代码,而要用该jQuery对象的length属性值来判断。例如:
if (m) {……} //错误的判断,因为结果始终为true:尽管DOM元素不存在,但jQuery对象已创建
if ( m.length==0 ) //正确的判断:如果表达式成立,说明不存在此元素
if ( !m.length ) //正确的判断:如果长度不大于0,说明不存在此元素
jQuery对象的方法非常多,如上节示例中的appendTo就是方法之一。为不影响主体结构,这些方法全部放到本书后面的“B/S基本知识库”中,平时无须专门去学习,用到再查即可。
这里需要特别介绍一下jQuery对象的链式操作风格:对于同一个jQuery对象所应用的全部方法,都可以像链条一样连在一起,这样可以极大地简化代码量,而且阅读起来非常清晰。
例如:
$('#a1').next().show(); //对同一个jQuery对象,先使用next方法、再使用show方法
当然,做任何事情都要有度。链式操作虽然可以将同一个对象的所有操作代码都连在一起,但也应该考虑每个“链点”的层次结构,这样才能进一步改善代码的可读性和可维护性。一般来说,对于同一个对象不超过3个操作的,可以写在一行;如果有较多的操作或者代码比较复杂的,要适当换行。
如果附加数据是数组,那么数组中有几个元素,事件处理程序就要用几个参数来接收。例如,模拟操作代码为:$('#abc').trigger('click',[22,33,44]);事件处理程序就要用相应数量的参数来接收:
在上一节学习$()的时候,讲到了传入函数的问题。关于传入函数的完整代码,其实应该是这样的:
$(document).ready(function(){ //简写形式为$(function(){
…各种jQuery代码…
});
$('#abc').click(function(e,data1,data2,data3){……})
事件目标:这里的document指的是整个文档,在JS中它就是DOM对象的一种,应用了$()函数后就变成了jQuery对象。这个jQuery对象就是事件应用目标。
事件类型:它是用来说明发生了什么类型事件的字符串,上述示例代码的事件类型为ready。
事件处理程序(事件监听程序):这就是一个function函数,当事件触发时就执行这个函数,也被称为事件处理程序。请务必注意jQuery中的事件代码写法,其语法格式如下。
jQuery对象.事件类型(function(){ //function作为事件的方法参数被执行
…各种jQuery代码…
})
而在JS原生环境中,不同的事件动作是以属性的方式赋值的。例如,和上述示例代码相类似的JS事件代码写法如下:
window.onload = function{ //function作为属性值被执行
…各种JS代码…
}
由以上比较可知,jQuery中的事件代码是作为方法参数被执行的,而JS中则被作为属性值。
可能有的读者会觉得奇怪:ready明明是jQuery中的事件类型名称,它并不是一个方法,为什么里面还可以带参数?其实,上述写法本身还是简写的,具体请参考后面即将学到的“事件绑定”知识。
表2-20
事件 |
描述 |
JS对应事件 |
---|---|---|
ready |
文档就绪时触发,只能用于当前文档。以下3种写法都是可以的: |
|
load |
加载完成时触发,仅对window及带有url、src等属性的DOM对象有效(如图片加载等)。如: |
onload |
unload |
离开页面时触发(比如单击链接进入其他页面、地址栏输入其他url地址、使用了前进或后退按钮、重载页面、关闭浏览器等),仅对window对象有效。例如: |
onunload |
resize |
调整浏览器窗口大小时触发。例如: |
onresize |
scroll |
使用滑动条滚动的时候触发,仅对window及可滚动的元素对象有效(包括document及各种DOM节点对象)。例如: |
onscroll |
error |
当遇到错误(没有正确载入)时触发。例如: |
onerror |
其中,ready是jQuery中独有的,而其他在JS中都有对应的事件名称。由此对比表也能看出,jQuery中的事件名称一般都比JS中的同名事件少了两个开始的字母“on”。
为什么要增加一个ready?这正是jQuery的高明之处,因为它可以极大地提高web应用程序的响应速度。举个简单的例子。
有个大型图库网站,它在JS程序中为所有的图片都添加了一些行为(比如,单击后可以隐藏或显示)。如果按照JS的常规方式来处理,这些JS代码都应该写在window.onload事件中,这就意味着用户必须等到全部的图片都加载完毕后才能进行操作;如果使用jQuery的ready事件,就只需等到文档就绪即可操作。什么是“文档就绪”?就是只加载解析页面文档文件,不管其他。很显然,把网页解析为DOM树的速度要远远快于同时下载关联文件的速度。因此,凡是写在ready中的事件代码,只要DOM就绪就能执行。
当然,这种方法也有个弊端。例如,与图片有关的DOM已经下载解析完毕了,但很有可能图片还未加载完毕,这时如果使用ready中的事件代码来设置图片的高度或宽度等就未必有效。要解决这个问题,可使用jQuery提供的与JS原生功能完全等效的事件:load。
表2-21
事件 |
描述 |
JS对应事件 |
---|---|---|
keydown |
当键盘上的某个按键被按下时触发 |
onkeydown |
keyup |
当键盘上的某个按键被放开时触发 |
onkeyup |
keypress |
当键盘上的某个键被按下并且释放时触发(例如,插入字符) |
onkeypress |
以上触发的事件处理程序中可传入一个参数,用于获取按了哪个键。例如:
$(document).keydown(function(e) { //传入的参数是event事件对象,参数名可自定义
concole.log(e.which);
})
当页面运行时,先用鼠标在页面上单击以获取焦点,然后随便按下一个键(比如w),控制台将输出该键的键码值(比如,87)。关于event事件对象,稍后再做讲解。
表2-22
事件 |
描述 |
JS对应事件 |
---|---|---|
click |
单击鼠标时触发 |
onclick |
dblclick |
双击鼠标时触发 |
ondblclick |
mousedown |
按下鼠标时触发 |
onmousedown |
mouseup |
鼠标按下后松开鼠标时触发 |
onmouseup |
mouseenter |
鼠标进入时触发 |
onmouseenter |
mouseleave |
鼠标离开时触发 |
onmouseleave |
mouseover |
鼠标滑过时触发(该事件会冒泡) |
onmouseover |
mouseout |
鼠标离开时触发(该事件会冒泡) |
onmouseout |
mousemove |
鼠标移动时触发 |
onmousemove |
其中,有4个事件比较容易混淆,它们的区别就在于子元素上。
例如,我们在页面文件中先加入以下代码:
<div id="enter" style="background-color:lightgray;padding:20px;width:40%;float:left">
<p style="background-color:white;">mouseenter:<span></span></p>
</div>
<div id="over" style="background-color:lightgray;padding:20px;width:40%;float:right">
<p style="background-color:white;">mouseover:<span></span></p>
</div>
以上代码创建了2个div元素,每个div中又都有一个子元素p。为方便查看事件触发效果,在这个p元素里又加了一个span子元素。
JS中的测试代码如下(其中,#enter span和#over span使用的是后代选择器):
var x=y=0;
$('#enter').mouseenter(function(){ //对id为enter的元素应用mouseenter事件
$('#enter span').html(x+=1); //对id为enter元素中的span设置内容
});
$('#over').mouseover(function(){ //对id为over的元素应用mouseover事件
$('#over span').html(y+=1); //对id为over元素中的span设置内容
});
其中,mouseenter事件指定的jQuery对象是第一个div,mouseover事件指定的是第二个div。运行效果如图2-22所示。
图2-22
当鼠标指针从外面移入灰色的div区域时,事件都会触发(span中的数字内容发生变化),这时,两个事件的作用是一样的。但是,当鼠标进入灰色的div区域后,如果将鼠标在div及其子元素(p和span)之间移入移出的话,左侧的enter事件就不会触发,而右侧的over事件却频频触发。
这两个事件与mouseenter和mouseover是分别对应的。当鼠标指针从被选元素移出时,效果相同;当鼠标指针在被选元素的内部子元素间移入移出时,mouseleave事件不会触发,而mouseout却会频频触发(具体请参考源代码文件)。
类似于mouseover和mouseout这样的事件传播方式,专业一点的叫法为“事件冒泡”。所谓的事件冒泡,就是事件先由最具体的元素接收,然后逐级向上传播。
以mouseover为例,当鼠标指针从外部进入div时,会触发事件,也就是span的元素内容增加1(虽会冒泡,但div的上层元素body没有绑定事件,故只会给span显示的内容增加1);鼠标从div进入p时,由于事件冒泡,会同时触发它的上层元素div的事件,因而span的内容又加了1;将鼠标从p移到它的下级span时,还是会冒泡,span的内容再次加1。
关于“事件冒泡”,目前只需简单地了解这么多(稍后的事件对象还会有专门讲解)。如果觉得mouseover和mouseout因为冒泡问题不是很爽,可以改用mouseenter和mouseleave。
表单的作用就是让用户在页面上填写内容,完成之后可以提交到服务器端进行处理。
在企业级的B/S项目开发中,表单的作用非常重要。关于表单标签方面的知识,请参考后面的“B/S基本知识库”,这里不再赘述。与表单相关的事件如表2-23所示。
表2-23
事件 | 描述 | JS对应事件 | 备注 |
---|---|---|---|
focus | 得到焦点时触发 | onfocus | 这4个事件比较特殊,还可以直接作为方法使用。例如: `$('#ip').focus(); //得到焦点` |
blur | 失去焦点时触发 | onblur | |
select | 选中文本框中的一个或多个字符时触发 | onselect | |
submit | 提交表单数据时触发 | onsubmit | |
change | 输入的值或选择的值发生改变时触发 | onchange |
例如,页面中有如下form元素:
<form action="index.html">
<label>请输入工作单位:<input id="ip" type="text"></label><br>
<input type="submit">
</form>
运行效果如图2-23所示。
图2-23
当把鼠标指针移到输入框时,该input元素就得到了输入焦点,此时将触发focus事件;离开时又将触发失去焦点事件blur。例如:
$('#ip').focus(function(){
console.log('您已经将光标移动到我身上了!');
})
$('#ip').blur(function(){
console.log('您已经将光标从我身上移开了!');
})
当用户选中文本框(input或texterea)中的一个或多个字符时,可触发select事件:
$('#ip').select(function(){
console.log('您已经选择了我身上的文本!');
})
对于<input>或<textarea>元素,在它们失去焦点且value值发生改变时,将触发change事件;对于<select>元素,在其选项改变时也将触发change事件:
$('#ip').change(function(){
console.log('您已经改变了我的值!');
})
当单击submit按钮向服务器提交数据时,将触发submit事件:
$('form').submit(function(){
console.log('数据即将提交到服务器!');
})
jQuery有两个合成事件方法:hover和toggle。注意,这两个都是方法,不是事件类型!
该方法用于模拟光标悬停事件。当光标移动到指定的DOM元素上时,触发第一个事件函数;当光标移出这个元素时,触发第二个事件函数。
为说明问题方便,我们在页面中再增加以下DOM元素:
<div style="width: 160px;border:1px solid blue">
<div id="title" style="height:22px;background: silver">什么是“职场码上汇”</div>
<div style="border-top:1px solid blue;display:none">简介内容<br><br><br><br></div>
</div>
运行效果如图2-24所示。
图2-24
我们现在希望的效果是:当光标移到这个标题上面的时候,简介内容可以显示;光标离开标题的时候,内容隐藏。JS代码如下:
$('#title').hover(function(){
$(this).next().show(); //$(this).next()获取的是相邻的下一个元素,也就是内容div
},function(){
$(this).next().hide();
})
其中,$(this).next()获取的是指定元素(这里是id为title的div)相邻的下一个元素,也就是用来显示内容的div。show为显示元素方法,hide为隐藏元素方法。内容显示时的效果如图2-25所示。
图2-25
以上代码其实就是mouseenter和mouseleave两个鼠标事件的组合。它与以下代码的效果是相同的:
$('#title').mouseenter(function(){
$(this).next().show();
}).mouseleave(function(){
$(this).next().hide();
})
该方法用于模拟鼠标连续单击事件。第一次单击元素,触发指定的第一个函数(fn1);当再次单击同一元素时,则触发第二个函数(fn2);如果有更多函数,则依次触发,直到最后一个。随后的每次单击都重复对这几个函数的轮番调用。
仍以上面的代码为例,只要将hover改为toggle就能起到同样的效果。例如:
$('#title').toggle(function(){
$(this).next().show();
},function(){
$(this).next().hide();
})
需要注意的是,toggle方法的合成事件功能自jQuery1.9版已经取消,以上代码在1.9及以上版本无效;但toggle方法切换元素可见状态的功能是正常的。例如:
$('#title').click(function(){
$(this).next().toggle();
})
在id为title的div元素上单击,其相邻的下一个div元素就会在可见与不可见之间来回切换。
事件对象是与特定事件相关且包含有关该事件详细信息的对象,不同事件产生的事件对象内的参数也不太一样。
事件对象会作为参数传递给事件处理程序函数,实际应用时只需在事件处理函数中自定义一个变量来接收就可以了。仍以之前的keydown键盘事件处理函数为例:
$(document).keydown(function(e) { //传入的参数是event事件对象
console.log(e);
concole.log(e.which);
})
这里传入的参数e就是event事件对象。为帮助大家理解,我们在事件中加了一行代码用于输出event的内容。当页面运行时,先用鼠标在页面上单击以获取焦点(因为该事件操作的jQuery对象是document,只有在页面上按键才有效果),然后随便按下一个键(比如w),控制台的输出内容如图2-26所示。
图2-26
其中,标注为“test.js:24”的部分就是输出的event内容。很显然,这是一个数据对象,它涵盖了当前这个事件的很多信息。正是因为该事件对象中包含了which属性,所以代码concole.log(e.which)才会输出键码值:87(标注为“test.js:25”的部分就是输出的e.which内容)。
由图2-26可知,event事件对象包含以下常用属性:
altKey:false //altKey表示是否同时按下alt键;
bubbles:true //bubbles表示事件是否冒泡;
cancelable:true, //cancelable表示是否可以取消事件的默认行为;
charCode:0 //charCode表示按下的字符编码;
ctrlKey:false //ctrlKey表示是否同时按下ctrl键;
currentTarget:document //currentTarget表示绑定事件的当前DOM元素对象;
isDefaultPrevented:false //isDefaultPrevented表示是否调用了preventDefault()方法
key:"w" //key表示按下的键是w;
keyCode:87 //keyCode表示按下的键值是87(也就是键编码);
shiftKey:false //shftKey表示是否同时按下shift键;
target:body //target表示触发该事件的实际DOM对象;
type:"keydown" //type表示触发的事件类型是keydown;
which:87 //which表示按下的键值是87(鼠标事件中,则用于返回用户单击的鼠标左、中、右键:1表示鼠标左键,2表示鼠标中键,3表示鼠标右键)。
这里有2个属性可能特别容易混淆:target和currentTarget。仍以上述示例代码为例:
$(document).keydown(function(e) { //传入的参数是event事件对象
console.log(e);
concole.log(e.which);
})
currentTarget就是指绑定事件的当前DOM对象(这里是document),它始终不变,事件代码中的this也一直指currentTarget;而target却是可以变化的。以此代码为例,当页面运行且获得焦点后,如果直接输入w,那触发该事件的实际DOM对象就是body;如果在表单的文本框中输入w,那实际DOM对象就是input。
为了将这两者之间的区别看得更清楚一点,我们先在页面中增加以下代码:
<div id="abc" style="width:160px;height:80px;border:3px solid red;">
<p>这是div中的p段落</p>
</div>
然后给这个div元素使用单击事件(这里的tagName用于返回DOM对象的标签名称):
$('#abc').click(function (e) {
console.log(e.target.tagName + ',' + e.currentTarget.tagName + ',' + this.tagName);
})
运行效果如图2-27所示。
图2-27
如果在p段落的所在行单击,则输出的内容为:P,DIV,DIV;这就表示target对象是p,后两个不变;
如果在div的其他空白处单击,则输出的内容为:DIV,DIV,DIV;这就表示target对象是div,后两个仍然不变。
需要注意的是,不同的事件类型,可用的event属性和方法也略有区别。例如,在上面的keydown事件对象中,还有3个非常容易混淆的属性:charCode、keyCode和which。
在keydown事件中,仅仅需要知道按下了哪个键,因而其键值(keyCode,也就是键编码)属性值为87,which也为87,但其对应的字符编码属性(charCode)值为0;如果把事件类型改为keypress,那就需要获取按下的具体字符内容,此时charCode的值为119,keyCode和which也都为119。
可能有的读者会说,为什么同是按下w,keydown中的keyCode为87,而keypress中的keyCode和charCode却是119?请注意,在keypress事件中,输入的字母是区分大小写的,当输入小写的w时,event事件对象中的keyCode、charCode、which都是119;当输入大写的W时,它们都是87。
其中,which属性除了可以获取键盘中的键值或字符编码外,还可以获取用户单击的是鼠标中的哪个键(具体请参考上述代码中的注释说明)。
除此之外,还有2个很常用的属性。
pageX:返回鼠标相对于当前文档的x坐标。
pageY:返回鼠标相对于当前文档的y坐标。
例如,在上面的单击事件代码中,加上如下一行,将输出单击鼠标的位置相对于页面的x坐标和y坐标:
console.log(e.pageX + ',' + e.pageY);
event事件最常用的方法就是2个:preventDefault()和stopPropagation()。前者用于阻止默认的事件行为,后者用于阻止事件的冒泡。
例如,单击超链接后会跳转、单击submit后会提交表单数据、单击鼠标右键会弹出菜单等,这些都是默认行为。如要阻止,可使用preventDefault()方法。
仍以之前页面中的的表单代码为例:
<form action="index.html">
<label>请输入工作单位:<input id="ip" type="text"></label><br>
<input type="submit">
</form>
尽管该表单要提交到的目标文件index.html不存在,但在单击“提交”时仍然去访问了该文件,无非是显示错误而已,但至少说明确实去执行提交了。
如果在submit事件中加上以下代码:
$('form').submit(function(e){ //要阻止默认行为,必须在function中传入一个参数
console.log('数据即将提交到服务器!');
e.preventDefault(); //阻止提交
})
再次运行时,只输出了“数据即将提交到服务器”,提示文件不存在的错误信息却没了。这就表明,preventDefault()已经阻止了提交表单数据的默认行为,根本没有去访问action指定的文件。
关于事件冒泡问题,在学习事件mouseover和mouseout时已经粗略地提到过。这个事情如果处理不好,也可能会导致一些预料之外的问题。
现再以最常见的单击事件为例:
$('#abc').click(function (e) {
console.log(e.pageX + ',' + e.pageY);
})
$('body').click(function () {
console.log('这是在body上单击输出的');
})
$(document).click(function () {
console.log('这是在document上单击输出的');
})
其中,第一个单击事件绑定的是id为abc的div元素;第二个单击事件绑定的是body,也就是页面中的身体元素;第三个绑定的是document,也就是整个文档。
现在在div元素上单击,输出结果如图2-28所示。
图2-28
本来div所对应事件的输出内容只有鼠标单击时的位置(也就是93,127),可现在却连它上级的body及document中的单击事件也被触发了,所以一下子输出了3行内容,这就是“冒泡”导致的。如图2-29所示。
图2-29
如要阻止事件冒泡,可使用stopPropagation()方法,当前元素的事件代码仍能正常执行。例如,要停止div元素的单击事件冒泡,只需加上一行代码:
$('#abc').click(function (e) {
console.log(e.pageX + ',' + e.pageY);
e.stopPropagation();
})
同样的道理,在body的单击事件代码中加上stopPropagation,也能阻止body上的事件继续向document冒泡。
当需要同时阻止事件默认行为及冒泡时,可以直接返回false。例如,可以将上面代码中的加粗部分替换为:return false。
正常情况下,事件处理函数都是直接写在相应的目标对象上的。但有时为了便于管理和优化事件处理效率,也可利用事件冒泡特性,将事件处理函数绑定到它们的祖先对象上。这样就只需指定一个事件处理程序,即可管理某一类型的所有事件,这就是事件委托。
如下面的代码,尽管document文档中可能包含各种元素,但单击事件处理函数都是绑定在一个document上,然后通过判断再执行相应的代码:
$(document).click(function (e) {
switch (e.target.tagName) {
case 'HTML':
console.log('您点击了页面');
break;
case 'BODY':
console.log('您点击了body');
break;
case 'DIV':
console.log('您点击了div');
break;
case 'P':
console.log('您点击了P');
break;
default:
console.log('您点了:'+e.target.tagName);
}
})
注意
绑定的事件类型必须支持冒泡,而且最好将委托对象选择为距离事件目标最近的。例如,之前在学习mouseover事件时的触发对象也可绑定到document,但由于div距离事件目标最近,所以就绑定到了div,这样就能减少逐级向上冒泡查找的麻烦,提高了程序运行效率。
本节内容一开始,我们就留了一个疑问:像ready、click、keydown、mouseenter等明明都是jQuery中的事件类型名称,它们并不是方法,可为什么里面还可以带参数?其实,以上所写的全部事件代码都是采取简写方式。
以click事件为例。
简写格式为:$('#abc').click(function);
。
正常写法为:$('#abc').on('click',function);
。
这里的on就是用来绑定事件函数的方法。其中,click是触发的事件名称,function是事件处理程序。如果采取这样完整的写法,是不是就很好理解了?“写得更少、做得更多”一直是jQuery的理念,能简写就简写,反正效果一样,何乐而不为?也正因为jQuery中的事件在简写之后看起来很像方法,所以它们也被称为“事件方法”。当然,简写并不是万能的:如使用on方法来绑定函数,可实现的功能会更强大。
jQuery自1.7版开始启用新的on方法绑定事件,用于取代之前的bind、delegate、live方法(这些老的方法已经不再推荐使用)。通过on方法绑定事件的方式有两种。
事件类型:可以是一个或多个,多个之间用空格分隔。
选择器:用于指定所绑定对象中能够触发事件的后代元素。如果选择器是null或者忽略了该选择器,那么所绑定的jQuery对象总是能触发事件。
数据:此参数不是null或undefined时,将传递给处理程序。该参数可以是任何类型,当是字符串类型时,那么选择器参数必须提供,或明确地传递null,这样就不会把数据参数误认为是选择器。该参数最好是使用一个对象(键值对),这样就可以作为属性传递多个值。
事件处理函数:事件被触发时执行的函数。若该函数只是要执行return false,那么该参数位置可以直接简写成false。仍以之前页面中的div元素为例:
$('#abc').on('click',function () {
console.log('你点击我了!');
})
只要单击div中的任何位置都会触发。现在再给其增加一个选择器:
$('#abc').on('click','p',function……)
则只有点击到div中的p段落文字时才会触发,单击div中的其他位置不会触发。
如果想在单击或鼠标进入时都触发事件处理程序,可以直接在事件类型中添加。例如:
$('#abc').on('click mouseenter','p',function……)
如果在绑定事件时加上了数据参数,那么在事件处理程序中可通过事件对象中的data属性来获取:
$('#abc').on('click mouseenter','p',{name:'张三',age:18},function (e) {
console.log(e.data); //此行将输出data属性的值
console.log('你点击我了!');
})
当数据参数为字符形式时,为防止对前面的选择器产生干扰,即使无须指定选择器,也必须使用null或空字符串作为占位符。例如:$('#abc').on('click',null,'我是数据参数',function……)。
尤其重要的是,即使页面中的DOM元素发生变化,只要这些子元素都在on所指定的选择器范围中,仍然会触发事件。这时的选择器参数就相当于使用了事件委托。例如:
$('#abc').on('click mouseenter','p',{name:'张三',age:18},function (e){
console.log(e.data);
console.log('你点击我了!');
})
$('<p>我是新加进来的段落</p>').appendTo('#abc'); //在div中追加一个p元素
上述代码仅指定div中的p元素才能触发事件。在事件绑定之后,再往div中添加一个新的p元素,那么这个新加的p元素一样可以触发。如图2-30所示。
图2-30
对于只需要阻止默认行为和事件冒泡的处理函数,正常情况是这么写的:
$('#abc').on('click','a',function(){
return false; //a标签单击链接失效
});
对于这种只需要返回false的事件处理函数,可以直接简写为:
$('#abc').on('click','a',false);
需要注意的是,绑定事件时如果没有指定选择器,则处理程序内部的this就代表所绑定事件的元素对象。例如:
$('#abc').on('click',function(){
console.log(this); //这个返回的就是id为abc的DOM对象,也就是div
});
如果指定了选择器,这就相当于是事件委托,this则代表与选择器相匹配的元素对象。例如:
$('#abc').on('click','p,a',function(){
console.log(this); //单击p时就是p,单击a时就是a
});
事件对象的格式为:{事件名称:function函数, 事件名称:function函数, 事件名称:function函数……}。例如:
$('#abc').on({
click:function(){
console.log('你点击了我');
},
mouseenter:function(){
console.log('你触摸到我了!');
}
});
也可以这样写:
$('#abc').on({
'click mouseenter':function(){
console.log('你点击了我');
}
});
选择器和数据是可选的。如果指定选择器,相当于是事件委托。例如:
$('#abc').on({
click:function(e){
console.log(e.data.name+',你点击了我'); //输出“张三,你点击了我”
},
mouseenter:function(){
console.log('你触摸到我了!');
}
},'p',{name:'张三',age:18});
对于on绑定的事件,可以采用off方法解除。例如:
$('#abc').off(); //全部解除
$('#abc').off('click'); //只解除click事件
$('#abc').off('click','p'); //只解除对p元素有效的click事件
如果事件处理函数是以变量表示的,这里也可以解除指定的事件处理程序:
$('#abc').off('click',test); //假如处理函数名称为test
$('#abc').off('click','p',test);
注意
jQuery1.7之前版本的unbind、undelegate和die方法已被off方法取代,原方法不建议继续使用。
对于只需触发一次、随后就立即解除绑定的情况,可使用one方法。
该方法在使用上与on完全相同。例如:
$('#abc').one('click','p',{name:'张三',age:18},function (e) {
console.log(e.data);
console.log('你点击我了!');
})
该事件在第一次单击时将触发,再次单击就无效了,因为执行一次之后该事件就会立即被移除。
所谓的命名空间,其实就是给绑定的各种事件类型进行“人工分类”。具体操作时,只需在绑定的事件类型名称后面用点接上一个分类名字即可。注意,这个分类名字不能以下划线开头(以下划线开头的的命名空间是供jQuery本身使用的)。例如:
$('#abc').on('click.bj',function(){
console.log('你点击我了!');
});
$('#abc').on('mouseenter.bj',function(){
console.log('mouseenter');
});
要解除jQuery对象上所有命名空间为bj的事件,只需这样即可:
$('#abc').off('.bj');
这样执行后,与“bj”命名空间的事件被解除,不在“bj”命名空间的其他事件仍然存在。
同一个事件类型也可以属于多个命名空间,多个命名空间之间不存在任何的层次或隶属关系。例如:
$('#abc').on('click.a1.a2',function(){ //同一个click就分别属于a1和a2两个命名空间
console.log('你点击我了!');
});
有的时候,让系统模拟用户的操作来执行相关事件,会带来意想不到的效果。例如,某个操作按钮已经设置了一些事件代码,而另外一个事件正好也需要使用该按钮中的功能,难道要再写一份吗?除了可以自定义函数外,还可以通过事件模拟的方式解决;再比如,有时希望进入页面后,就立即触发指定的click事件,同样可以使用事件模拟操作而无须用户主动去单击。
该方法和真实的手工操作效果完全一样,不仅在触发事件后仍然执行浏览器的默认动作,该冒泡的事件仍会冒泡。该方法有2个参数。
第一个参数是要触发的事件类型,这是必选的;第二个参数为传递给事件处理函数的附加数据,可以是任何类型,例如数字、字符串、布尔值、数组或对象等,此参数可选。
例如,我们给页面中id为abc的div设置单击事件:
$('#abc').click(function(){
console.log('我被单击了!');
})
如果让系统模拟执行此单击操作,可以使用如下代码:
$('#abc').trigger('click');
以上代码也可以简写为:
$('#abc').click(); //对于绑定的自定义名称事件,不能这样简写
再如,希望模拟操作的同时再传递一些附加数据,可以先在事件处理函数中加上接收参数:
$('#abc').click(function(e,data) { //第1个参数固定为事件对象,第2个参数才是数据
console.log(data + 33);
})
以下是模拟操作代码:
$('#abc').trigger('click',22);
执行后输出的结果为55。
注意
很显然,这样比较麻烦,远不如使用对象作为参数方便。例如:
$('#abc').click(function (e,dt) {
console.log(dt.data1+dt.data2+dt.data3);
})
模拟操作代码为:
$('#abc').trigger('click',{
data1:22,
data2:33,
data3:44
});
执行后输出的结果与数组参数相同。
该方法在使用上与trigger完全相同,但也有以下两点区别:
该方法和真实的事件发生不一样,它仅仅是触发绑定的函数执行,会忽略默认行为和冒泡;
trigger会影响所有与jQuery对象相匹配的元素,而该方法仅影响第一个匹配到的元素。例如,绑定事件的jQuery对象为:$('#abc,#ip'),trigger方法会触发这两个DOM元素的事件,而triggerHandler仅触发第一个元素的。
基于以上两种方法,我们可以自定义一些事件类型。这些事件类型虽不会由浏览器通知给应用程序,人工也无法触发,但却可以通过上述两种方法模拟执行。例如:
$('#abc').on('diycofficecode',function(){
console.log('欢迎光临职场码上汇·资源站!');
});
$('#abc').trigger('diycofficecode');