一、JavaScript概要
JavaScript(JS)是一种轻量级、解释型、动态类型的高级程序设计语言。它诞生于1995年,是一门基于原型、函数优先的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持,如 Node.js、 Apache CouchDB 和 Adobe Acrobat。它已经由ECMA(欧洲计算机制造商协会)通过ECMAScript实现语言的标准化。
JavaScript非常重要。它是一种可以同时运行在服务器端、客户端和桌面应用的跨平台程序语言,当然现在你也可以使用JavaScript进行嵌入式开发。随着Node、V8引擎、React、Angular、Vue、Electron、UnityScript、ECMAScript、HTML5、CSSS3、前端工程化与模块化的出现或升级,JavaScript变得越来越重要,在大前端中JavaScript扮演举足轻重的角色。
1.1、JavaScript组成
JavaScript主要由三部分构成,分别是ECMAScript、DOM与BOM,如图1-1所示。
图1-1 JavaScript组成
ECMAScript定义了该语言的语法、类型、语句、关键字、保留字、操作符、对象等核心内容;ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。尽管JavaScript和JScript与ECMAScript兼容,但包含超出ECMAScript的功能。
文档对象模型(Document Object Model,简称DOM)定义处理网页内容的方法和接口,是W3C制定的标准接口规范,是一种处理HTML和XML文件的标准API。DOM提供了对整个文档的访问模型,将文档作为一个树形结构,树的每个结点表示了一个HTML标签或标签内的文本项。;
浏览器对象模型(Browser Object Model,简称BOM)定义了与浏览器进行交互的方法和接口,BOM与DOM不同,其既没有标准的实现,也没有严格的定义, 所以浏览器厂商可以自由地实现BOM。BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。
1.2、JavaScript特点
JavaScript主要被作为客户端脚本语言在浏览器上运行,能被用来设计和处理网页在事件发生时的行为。JavaScript不仅易学而且强大,因此广泛用于对网页的控制。JavaScript是一个动态脚本语言,支持基于原型的对象构造。其基本语法被设计得与Java和C#接近。
JavaScript以其跨平台、容易上手等优势而大行其道,同时,有些特殊功能(如AJAX)必须依赖JavaScript在客户端进行支持。随着引擎如V8和框架如Node.js的发展,及其事件驱动及异步IO等特性,JavaScript逐渐被用来编写服务器端程序。JavaScript的特点如下:
(1)、解释型的脚本语言:JavaScript是一种解释型的脚本语言,Java、C#等语言先编译后执行,而JavaScript是在程序的运行过程中逐行进行解释的;当然也有人认为使用了时编译器(JIT compiler,just-in-time compiler)进行预编译,但本质上是解释型的脚本语言。
(2)、基于对象:JavaScript是一种基于对象的语言,能运用自己已经创建了的对象,许多功能可以来自于脚本环境中对象的方法与脚本的相互作用。
(3)、事件驱动:JavaScript可以直接对用户或客户输入做出响应,无需经过web服务程序。他对用户的响应,是以事件驱动的方式进行的,所谓事件驱动,指的是在主页执行了某种操作所产生的动作,此动作称为"事件"。
(4)、跨平台:JavaScript依赖于浏览器本身,不依赖于操作系统。只要能运行浏览器的平台,并支持JavaScript就可以正确执行,目前几乎所有的浏览器都支持JavaScript。
(5)、安全性:JavaScript是一种安全性语言。它不允许访问本地的磁盘,并不能将数据存入服务器上;不允许对网络文本进行修改和删除,只能通过浏览器实现信息浏览或动态交互。可有效的防止数据丢失。
(6)、弱类型:JavaScript语言中采用的是弱类型的变量类型,对使用的数据类型未做出严格的要求, 弱类型语言是相对强类型语言来说的,在强类型语言中变量类型有多种,例如int、float、boolean等,不同的类型相互转换有时需要强制转换,而JavaScript在定义变量时可以不指定类型且不同的类型相互转换有时无需强制转换。
二、ECMAScript(JavaScript核心与语法)
2.1、ECMAScript介绍
ECMAScript(简称ES)是形成JavaScript语言的核心。ECMAScript是由Ecma国际标准组织以ECMA-262和ECMA-402规范的形式进行标准化的,虽然JavaScript和ECMAScript通常都被人们用来表达相同的含义,但JavaScript包含了更加多的内容。
(1)、ECMAScript是一个标准(欧洲计算机制造商协会),JavaScript只是它的一个实现,其他实现包括ActionScript(Flash脚本)、Jscript等。
(2)、ECMAScript可以为不同种类的宿主环境提供核心的脚本编程能力,即ECMAScript不与具体的宿主环境相绑定,如JavaScript的宿主环境是浏览器,AS的宿主环境是Flash。
(3)、ECMAScript描述了以下内容:语法、类型、语句、关键字、保留字、运算符、对象等
如表1-1所示ECMAScript存在多个版本,ECMAScript 2015(ES2015、或ES6)是变化较大的一个版本,ES6是继ES5之后的一次主要改进,语言规范由ES5.1时代的245页扩充至600页。ES6增添了许多必要的特性,例如:模块、类以及一些实用特性,例如let、const、Maps、Sets、Promises、生成器(Generators)等。尽管ES6做了大量的更新,但是它依旧完全向后兼容以前的版本。
版本 | 发表日期 | 与前版本的差异 |
---|---|---|
1 | 1997年6月 | 首版 |
2 | 1998年6月 | 格式修正,以使得其形式与ISO/IEC16262国际标准一致 |
3 | 1999年12月 | 强大的正则表达式,更好的词法作用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变 |
4 | 放弃 | 由于关于语言的复杂性出现分歧,第4版本被放弃,其中的部分成为了第5版本及Harmony的基础 |
5 | 2009年12月 | 新增"严格模式(strict mode)",一个子集用作提供更彻底的错误检查,以避免结构出错。澄清了许多第3版本的模糊规范,并适应了与规范不一致的真实世界实现的行为。增加了部分新功能,如getters及setters,支持JSON以及在对象属性上更完整的反射 |
5.1 | 2011年6月 | ECMAScript标5.1版形式上完全一致于国际标准ISO/IEC 16262:2011。 |
6 | 2015年6月 | ECMAScript 2015(ES2015),第 6 版,最早被称作是 ECMAScript 6(ES6),添加了类和模块的语法,其他特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets 和 weak maps),promise,reflection 和 proxies。作为最早的 ECMAScript Harmony 版本,也被叫做ES6 Harmony。 |
7 | 2016年6月 | ECMAScript 2016(ES2016),第 7 版,多个新的概念和语言特性 |
8 | 2017年6月 | ECMAScript 2017(ES2017),第 8 版,多个新的概念和语言特性 |
9 | 2018年6月 | ECMAScript 2018 (ES2018),第 9 版,包含了异步循环,生成器,新的正则表达式特性和 rest/spread 语法。 |
10 | 2019年6月 | ECMAScript 2019 (ES2019),第 10 版 |
表1-1 ECMAScript版本
常见的脚本引擎有Chakra(Microsoft Edge)、SpiderMonkey(Firefox)、Chrome V8(Google Chrome)与JavaScriptCore 或称Nitro(Safari 12),不同的引擎间对支持ECMAScript的程度是不一样的。
ECMAScript 6(ES6)是一个转变转大的版本,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书将侧重介绍该标准。
我们现在使用的(被多数浏览器兼容的)标准是ECMAScript 5,2009年12月,ECMAScript 5.0 版正式发布,与ES3兼容,ES6同样向下兼容,ES6到ES5之间的距离是15年左右。
考虑到读者已经有JavaScript基础了,书中只选择在JavaScript基础部分未提到但在开发中需要使用到的内容。
2.2、数据类型
在JavaScript中使用var关键词声明变量,变量的类型会根据其所赋值来决定(动态类型)。如图1-2所示JavaScript中数据类型分为原始数据类型(5种)和引用数据类型(Object类型),ES6中新增了原始数据类型(基本数据类型)Symbol。
(1)、JavaScript中的5种原始数据类型:Undefined、Null、Boolean、Number和String。需要注意的是JavaScript中字符串属于原始数据类型。
(2)、typeof运算符用于查看变量类型,对变量或值调用typeof运算符将返回下列值之一:
undefined – 如果变量是 Undefined 类型的
boolean – 如果变量是 Boolean 类型的
number – 如果变量是 Number 类型的
string – 如果变量是 String 类型的
object – 如果变量是一种引用类型或 Null 类型的
代码语言:javascript复制<script>
function foo(){}
console.log(typeof 8); // number
console.log(typeof foo); // function
console.log(typeof("nfit")); // string
console.log(typeof jQuery); // undefined
console.log(typeof 1===1); // false 注意
console.log(typeof(1===1)); //boolean
console.log(typeof /w /g); // object
console.log(typeof []); // object
console.log(typeof(Null)); //undefined
</script>
typeof可以使用不带括号的写法,但还是建议带上,如"typeof 1===1"结果可能就不是你预期的了,因为没有括号所以按优先级先完成了typeof 1再与1作比较运算。
(3)、instanceof运算符用于引用类型判断,用来检测 constructor.prototype 是否存在于参数 object 的原型链上,可以简单的理解为判断对象是否属于某个引用类型。
代码语言:javascript复制 console.log([] instanceof Array); //true
function Student(){} //定义构造函数
var tom=new Student(); //实例化一个Student对象
console.log(tom instanceof Student); //true
console.log(tom instanceof Object); //true
console.log(tom instanceof Number); //false
输出结果如图1-3所示。
图1-3 instanceof运算符示例输出结果
(4)、null 被认为是对象的占位符,typeof运算符对于null值返回"object"。
(5)、原始数据类型和引用数据类型变量在内存中的存放如图1-4所示,原始数据类型与地址存放在栈中,引用类型存放在堆中。
图1-4 原始数据类型和引用数据类型存放
(6)、JavaScript中对类型的定义:一组值的集合。如Boolean类型的值有两个:true、false。Undefined和Null 类型都只有一个值,分别是undefined和null。
Null 类型只有一个值,就是 null ;Undefined 类型也只有一个值,即 undefined 。null和 undefined都可以作为字面量(literal)在JavaScript代码中直接使用。
null与对象引用有关系,表示为空或不存在的对象引用。当声明一个变量却没有给它赋值的时候,它的值就是 undefined 。
undefined 的值会出现在如下4种情况中:
1)、从一个对象中获取某个属性,如果该对象及其 prototype 链 中的对象都没有该属性的时候,该属性的值为 undefined 。
2)、一个 function 如果没有显式的通过 return 来返回值给其调用者的话,其返回值就是 undefined 。有一个特例就是在使用new的时候。
3)、当定义的变量未赋值时将返回undefined,这种情况比较常见。
4)、JavaScript 中的 function 可以声明任意个形式参数,当该 function 实际被调用的时候,传入的参数的个数如果小于声明的形式参数,那么多余的形式参数的值为 undefined。
代码语言:javascript复制<script>
var user = {name: "candy"};
console.log(user.age); //1、访问对象中不存在的属性,未定义
var i;
console.log(i); //2、变量未赋值
function f(n1) {
console.log(n1); //3、参数未赋值
}
var result = f();
console.log(result); //4、当函数没有返回值时为undefined
</script>
(7)、特殊的Boolean类型
JavaScript中会隐式的将一些非Boolean类型的值转换成布尔类型,转换时需要注意一些规则,如表1-2所示。可以使用逻辑运算符"!"将这些值还原。
序号 | 类型 | 布尔类型值 |
---|---|---|
1 | Null | 总是为false |
2 | Underfined | 总是为false |
3 | Boolean | 保持原值 |
4 | Number | 0、-0、NaN与0.0为false,其它为true |
5 | String | 空字符时为false,其它为true |
6 | Object | 都为真 |
表1-2 Boolean转换
代码语言:javascript复制 var i;
console.log(!!""); //连续两次否定获得空字符的boolean类型值
console.log(!!0); //false
console.log(!! 0); //false
console.log(!!-0); //false
console.log(!!NaN); //false
console.log(!!null); //false
console.log(!!undefined); //false
console.log(typeof (i)); //undefined
console.log(!!i); //false
console.log(false); //false
console.log(!![]); //true
(8)、== 与 ===比较的区别
JavaScript中判断值是否相等的操作符有两个 == 与 === 。两者相比,== 会做一定的类型转换;而 === 不做类型转换,所接受的相等条件更加严格。也可以简单的理解为===比较时会比较类型;当然与之对应的就是!=与!==用于比较两个值是否不相等;建议尽量使用===而不要使用==。
代码语言:javascript复制<script>
console.log("5"==5);
console.log("5"===5);
console.log("5"!=5);
console.log("5"!==5);
console.log(5==new Number(5));
console.log(5===new Number(5));
</script>
运行结果如图1-5所示。
图1-5 ==与===示例输出结果
new Number() 是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一些额外的功能并且它是一个对象。
当我们使用 == 操作符时,它只会检查两者是否拥有相同的值。因为它们的值都是 5,因此返回 true。
然后,当我们使用 === 操作符时,两者的值以及类型都应该是相同的。new Number() 是一个对象而不是 number,因此返回 false。
2.3、运算符
(1)、逻辑运算符的非布尔类型运算
JavaScript在语法上没有严格要求逻辑运算的表达式必须为布尔类型,返回结果也不是一定为布尔类型,可以根据规则转换。
||在是逻辑运算符,表示"或"的意思,当运算符同为false时结果为false否则为真,但或运算如果不是针对逻辑值运算时,如果第一个值是true,则直接返回第一个值,如果第一个值是false,则直接返回第二个值,判断时会根据表1-2进行转换。根据这一特性我们可以用于给变量设置默认值。
代码语言:javascript复制 function add(m,n) {
m=m || 0; //如果表m为false(0,undefined,空),则返回0,否则返回m
n=n || 0; //如果n为真则直接返回n,否则返回0
return m n;
}
console.log(add());
console.log(add(9));
console.log(add(8,9));
运行结果如图1-6所示。
图1-6 设置函数参数的默认值输出结果
逻辑运算符&&中,如果&&的第一个表达式是false,就不再考虑第二个表达式(短路),直接返回表达式一的值;注意返回的结果不一定是布什类型。
逻辑运行算!可以将非布尔类型的值进行转换,当然!是非的意思,一般我们会连续使用两个!!。
代码语言:javascript复制 var m=0&&5;
console.log(m); //0
var v=!!m;
console.log(v); //false
运行后输出结果为0,false,转换成布尔类型的规则可以参考表1-2,变量v的值经过非运算转换成了布尔类型。
(2)、逗号运算符
逗号运算符的特性及作用:逗号运算符的作用是将若干表达式连接起来。它的优先级是所有运算符中最低的,结合方向是自左至右。
逗号表达式:表达式1,表达式2,表达式3,......表达式n
运行过程:先计算表达式1的值,再计算表达式2的值,......一直计算到表达式n的值。最后整个表达式的值是表达式n的值。
代码语言:javascript复制 var a=(b=1,c=2,d=3); //返回表达式3(d=3)的值
console.log(a);
function add(m,n) {
return m ,n ,m n; //先计算m 与n 再返回m n;
}
console.log(add(4,5));
//交换a与b的值
a = [b][b=a,0]; //[b]是一维数组,[b=a,0]中按逗号运算处理,返回0,当做下标。
console.log(a,b);
运行结果如图1-7所示。
图1-7 逗号运算示例输出结果
通过逗号运算可以简化代码但可能会使你的代码可读性变差,但有时候我们不得不去读别人的代码。
2.4、严格模式
ECMAScript5(简称ES5)之前没有严格模式,过于宽泛的语法要求使得JavaScript代码可读性差,在ES5中增加了严格模式,这种模式使得JavaScript在更严格的条件下运行。
1、严格模式的特性
(1)、使JavaScript更加规范,减少了语法中一些不合理、不严谨、怪异的地方; 严格模式通过抛出错误来消除了一些原有静默错误。
(2)、减少了代码运行中的一些潜在不安全之处;
(3)、提高编译器效率,运行速度更快;严格模式修复了一些导致JavaScript引擎难以执行优化的缺陷。
(4)、严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
(5)、严格模式可能会增加你写代码的难度,因为你熟悉的那些语法也许不能用了。
2、严格模式的兼容性
严格模式使JavaScript更规范、更安全、更高效,浏览器对该功能的支持度相对较高,如图1-8所示包括IE 10在内的主流浏览器,都已经支持它。
图1-8 严格模式的浏览器兼容性
3、开启严格模式的方法
开启严格模式只需在代码首部加入字符串"use strict"即可。必须在首部(其前面没有任何有效代码除注释)否则无效。老版本浏览器会把它当作一行普通字符串,加以忽略。
把"use strict"放在脚本的第一行,则整个Script块或文件都将以严格模式运行。
代码语言:javascript复制<script>
"use strict";
console.log("严格模式。");
//严格模式到这里就结束了
</script>
<script>
console.log("正常模式。"); //不受上面声明的影响
</script>
如果在一个函数中声明"use strict",则该函数使用严格模式,其它函数不受影响。
<script>
function strictFunction() {
"use strict";
console.log("严格模式。");
//严格模式到这里就结束了
}
function normalFunction() {
console.log("正常模式。");
}
</script>
4、严格模式的规则
(1)、变量声明必须使用关键字
变量必须使用关键字声明,避免不小心将本地变量声明成一个全局变量(JavaScript中对于没有使用关键字声明的变量将提升为全局变量)。
代码语言:javascript复制<script>
"use strict"; //启用严格模式
var m=100; //定义变量m
n=200; //定义全局变量n,严格模式下这是错误的
</script>
运行结果如图1-9所示。
图1-9 变量声明必须使用关键字示例输出结果
(2)、with被禁止使用
JavaScript允许动态绑定(属性和方法到底属于哪一个对象),不是在编译时确定的,而是在运行时(runtime)确定的。严格模式对动态绑定做了一些限制。
with语句用于设置代码在特定对象中的作用域,with可以简化代码。
代码语言:javascript复制 var obj = {a:1,b:2,c:3}
obj.a=4;
obj.b=5;
obj.c=6;
//设置obj为特定作用域中可直接访问的成员的对象,方便访问obj
with(obj){a=4;b=5;c=6;}
严格模式下不允许使用with,因为这样可能会导致性能下降与潜在安全风险。
"use strict"; //启用严格模式
with(obj){a=4;b=5;c=6;}
运行结果如图1-10所示。
图1-10 严格模式下不允许使用with示例输出结果
(3)、强制开启eval作用域
JavaScript中作用域有两种:全局作用域和函数级作用域。严格模式带来了第三种作用域:eval作用域,严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。
代码语言:javascript复制 "use strict"; //启用严格模式
var a=100;
eval("var a=200;"); //a只在当前eval中有效
console.log(a);
输出结果为100,如果在非严格模式下将输出200,因为eval中定义的a只在eval作用域中有效;当然eval本身在就存在安全风险,不建议使用。
(4)、this不再指向全局对象
常规模式下,JavaScript太过于灵活,可能会因为错误的的调用,造成不一致的结果,严格模式下,this不被允许指向全局,如果运行时试图指向全局,this将会变为undefined。
代码语言:javascript复制 function normalFunction() {
console.log(this); //非严格模式下this指向window对象
}
normalFunction();
function strictFunction() {
"use strict"; //启用严格模式
console.log(this);
}
strictFunction();
运行结果如图1-11所示。
图1-11 this不再指向全局对象示例输出结果
(5)、越权操作直接异常,不再静默失败
在常规模式下,通过var声明的变量是不可删除的,试图删除会静默失败,在严格模式下会直接抛出异常;另外试图删除对象中不可删除的属性也会显式报错,只有configurable设置为true的对象属性,才能被删除。
代码语言:javascript复制 "use strict"; //启用严格模式
var v1=100;
delete v1; //错误Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
//创建对象obj1
var obj1 = {};
//为对象obj1定义name属性
Object.defineProperty(obj1, 'name', {
writable: false, //不可写
value: 'jack' //设置默认值
});
obj1.name = 'tom'; //错误Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
//创建对象obj2,并定义属性name
var obj2 = Object.create(null, {'name': {
value: "rose", //设置默认值
configurable: true //允许配置
}});
delete obj2.name; //删除属性name
console.log(obj2.name);
如果在非严格模式下是允许。
(6)、不允许对象属性重名
默认情况在对象中定义重复的属性,后定义的值会覆盖先定义的,严格模式规定,对象中不允许重复的属性,如图1-12所示。
图1-12 重复的对象属性将提示错误
在Chrome版本 78.0.3904.17(正式版本)64 位的浏览器中测试并未提示错误,但开发工具中提示语法错误,这可能是浏览器兼容性问题或有Bug存在。
(7)、不允许参数重名
非严格模式下函数有多个重名的参数,可以用arguments[i]读取,严格模式下不允许参数重名。
代码语言:javascript复制 "use strict"; //启用严格模式
//错误Uncaught SyntaxError: Duplicate parameter name not allowed in this context
function add(p1,p1,p2){}
因为函数add存在同名参数p1所以抛出语法错误的异常。
(8)、禁止使用八进制数字
严格模式禁止以0开头的八机制表示法,另外,ES6已经支持新的语法标准,八进制以0o来表示。
代码语言:javascript复制 "use strict"; //启用严格模式
var m=0o77; //以ES6的语法定义8进制数77
console.log(m); //输出63
//错误Uncaught SyntaxError: Octal literals are not allowed in strict mode.
var n=077;
(9)、函数须声明在顶层
非严格模式下我们可以将函数声明许多地方,而在严格模式中只允许全局作用域或函数作用域的顶层定义函数。
代码语言:javascript复制 "use strict"; //启用严格模式
while (true) {
function foo() {
console.log("1");
}
break;
}
if (true) {
function foo() {
console.log("2");
}
}
for (var i = 0; i < 10; i ) {
function foo() {
console.log("3");
}
}
foo(); //错误,foo is not defined
非严格模式下将输出3,严格模式的输出结果如图1-13所示。
图1-13 函数须声明在顶层示例输出结果
在Chrome版本 78.0.3904.17(正式版本)64 位的浏览器中测试时,不会抛出语法异常,但会忽略foo函数的声明。
(10)、caller/callee 被禁用
(11)、对禁止扩展的对象添加新属性会报错
Object.preventExtensions(obj);然后对obj增加属性则会报错。
(12)、delete不可删除属性(isSealed或isFrozen)的对象时报错
Object.isSealed() 方法判断一个对象是否被密封。Object.isFrozen()方法判断一个对象是否被冻结。
(13)、arguments严格定义为参数,不再与形参绑定,不允许对arguments赋值,arguments不再追踪参数的变化,禁止使用function直接引用caller和arguments
(14)、call/apply的第一个参数直接传入不包装为对象
(15)、call/apply/bind的第一个参数为null/undefined时,this为null/undefined
(16)、严格模式新增了一些保留字。
implements、interface、let、package、private、protected、public、static、yield、class、enum、export、 extends、import、super。
2.5、异常处理
与其它程序设计语言一样JavaScript也有异常处理机制,ECMA-262第3版引入了try-catch语句,作为JavaScript中处理异常的一种标准方式,基本的语法如下所示,这与Java与C#中的语法基本相同:
代码语言:javascript复制try{
//可能发生异常的代码
}catch(e){
//异常发生时将执行的代码
}finally{
//不管是否发生异常都将执行的代码
}
try代码块中写可能发生异常的代码;catch代码块用于捕获异常,当对应类型的异常发生时将执行相应的代码块,用于异常处理;finally是不管是否发生异常都将执行的代码块;需要注意的是catch和finally语句都是可选的,但你在使用try语句时必须至少使用一个。
1、try-catch
代码语言:javascript复制 var obj={};
try {
console.log("1");
obj.do(); //调用一个没有的方法
console.log("2");
}catch(exp){
console.log(exp.message); //输出异常信息
console.log(exp.stack); //输出堆栈信息
}
console.log("3");
输出结果如图1-14所示。
图1-14 try-catch示例输出结果
需要注意的是发生异常后直接进入了catch代码块中而异常后的语句不会再执行,所以输出结果中并没有数字2。
2、try-catch-finally与错误类型
代码语言:javascript复制 try {
console.log("1");
window.do(); //调用一个没有的方法
console.log("2");
}catch(error){
console.log(error.name); //输出错误类型
}
finally {
console.log("3");
}
console.log("4");
输出结果如图1-15所示。
图1-15 try-catch-finally示例输出结果
示例中我们通过捕获的异常对象的name属性获得了错误类型,在JavaScript执行代码期间可能会发生的错误有多种类型,每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。ECMA-262定义了下列7种错误类型,如表1-3所示:
序号 | 类型 | 意义 |
---|---|---|
1 | Error | 所有错误的基本类型,实际上不会被抛出 |
2 | EvalError | 执行eval错误时抛出 |
3 | ReferenceError | 对象不存在是抛出 |
4 | RangeError | 数字超出边界时抛出 |
5 | SyntaxError | 出现语法错误时抛出 |
6 | TypeError | 变量不是期望的类型时抛出 |
7 | URIError | 给encodeURI()等函数传递非法字符串时抛出 |
表1-3 ECMA-262定义的错误类型
3、自定义异常
throw语句用来抛出一个用户自定义的异常,throw语句后的抛出异常类型可以是自定义的任意类型。遇到throw语句后将不再执行后面的脚本,并且控制将被传递到调用堆栈中的第一个catch块。如果调用者函数中没有catch块,程序将会终止。
代码语言:javascript复制 function getAge(age) {
if(isNaN(age)){
throw {name:"TypeError",message:"类型错误,年龄必须是数字"} //抛出异常
}
console.log("年龄:" age);
return age;
}
try {
getAge("Five");
}catch(error){
console.log(error.name,error.message); //输出异常对象中的属性值
}
输出结果如图1-16所示。
图1-16 自定义异常示例输出结果
4、全局的异常事件处理
太多的try-catch在代码中会使用程序不够简洁,V8引擎不鼓励在函数中使用try-catch,使用全局的错误处理onerror会捕获这些错误。
方法一:
window.onerror = function(message, source, lineno, colno, error) {}
参数说明:
message:错误信息(字符串)
source:发生错误的脚本URL(字符串)
lineno:发生错误的行号(数字)
colno:发生错误的列号(数字)
error:Error对象(对象)
方法二:
window.addEventListener('error', function (eventTartet) {}
ErrorEvent是事件对象在脚本发生错误时产生,可以拿到target属性,部分属性说明如下:
message: 字符串,包含了所发生错误的描述信息。
filename: 字符串,包含了发生错误的脚本文件的文件名。
lineno: 数字,包含了错误发生时所在的行号。
colno: 数字,包含了错误发生时所在的列号。
error: 发生错误时所抛出的 Error 对象。
代码语言:javascript复制 onerror=function (message, source, lineno, colno, error) {
console.log(message, source, lineno, colno);
console.log(error);
};
//添加全局错误处理事件
window.addEventListener('error', function (errorEvent) {
console.log(errorEvent );
});
window.do(); //调用一个不存在的方法
输出结果如图1-17所示。
图1-17 全局错误处理示例输出结果
需要注意如下几点:
1.onerror监听器只能声明一次,后续的声明会覆盖之前的声明。而事件处理器addEventListener则可以绑定多个回调函数,后添加的不会覆盖先添加的。
2.img或script元素发生错误时会触发一个Event接口的error事件,addEventListerner有效而onerror无效。
3.onerror函数返回true,那么将会阻止执行浏览器默认的错误处理,addEventListener则可以通过事件对象的ErrorEvent.preventDefault()方法达到同样的效果。
代码语言:javascript复制 onerror=function (message, source, lineno, colno, error) {
console.log(message, source, lineno, colno);
console.log(error);
return true; //阻止异常传播
};
window.addEventListener('error', function (errorEvent) {
console.log(errorEvent );
errorEvent.preventDefault(); //阻止默认行为(阻止异常传播)
});
window.do(); //调用一个不存在的方法
输出结果如图1-18所示。
图1-18 阻止异常传播示例输出结果
三、DOM扩展
文档对象模型(Document Object Model,简称DOM)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM允许开发人员对一个树状的文档进行访问,如查询、删除、修改页面中的某个节点。
使用过jQuery我们能感受到原生的JavaScript操作DOM不如jQuery方便,所以DOM也在不断的完善与扩展,对DOM的两个主要扩展是Selectors API(选择符API)和HTML5。
3.1、选择符API
Selectors API(选择符API)是由W3C发起制定的一个标准,致力于浏览器原生支持CSS查询,SelectorsAPILevel 1的核心是两个方法:querySelector()和querySelectorAll(),可以通过Document及Element类型的实例调用他们,包含IE8在内的多数浏览器已支持这两个核心方法。
(1)、querySelector()方法
querySelector()方法通过CSS选择符查询元素,返回与该选择器匹配的第一个元素,如果没有找到匹配的元素则返回null。
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>querySelector()示例</title>
</head>
<body>
<h2 Id="topic">querySelector()示例</h2>
<p>段落1</p>
<p class="blue">段落2</p>
<p class="blue">段落3</p>
<p class="topic blue">段落4</p>
<p>段落5</p>
<div>
<p id="p6">段落6</p>
<p id="p7">段落7</p>
</div>
<script>
//获得Id为topic的元素
var topic = document.querySelector("#topic");
console.log(topic.innerHTML);
//获得第1个p标签元素
var p = document.querySelector("p");
console.log(p.innerHTML);
//获得第1个类含有blue的元素
var blue = document.querySelector(".blue");
console.log(blue.innerHTML);
//获得类含topic的第1个p元素
var ptopic = document.querySelector("p.topic");
console.log(ptopic.innerHTML);
//获得第1个标签为div的元素,在该元素中查找第1个子元素Id为p7的元素
var divp7 = document.querySelector("div").querySelector("#p7");
console.log(divp7.innerHTML);
</script>
</body>
</html>
输出结果如图1-19所示。
图1-19 querySelector()示例输出结果
使用Document类型调用querySelector方法时,将在整个文档内查找,而通过元素(Element)调用该方法时只会在元素的子元素中查找。上面仅列举了最简单的选择器,你还可以选择更多不同的CSS选择器来查找元素,但有些是不支持的。
(2)、querySelectorAll()方法
方法querySelectorAll()与jQuery的$(selector)类似,接受的参数是一个CSS选择符,返回的是所有匹配元素集合而不仅仅是一个元素。这个方法返回的是一个NodeList的实例,如果没有找到NodeList对象就是空的。
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>querySelectorAll()示例</title>
</head>
<body>
<h2 Id="topic">querySelectorAll()示例</h2>
<p>段落1</p>
<p class="blue">段落2</p>
<div id="divContent">
<p class="blue">段落3</p>
<p class="topic blue">段落4</p>
<p>段落5</p>
</div>
<script>
//获得所有的p元素
var ps = document.querySelectorAll("p");
console.log(ps.length, ps);
//获得Id为divContent元素下的所有类为blue的p元素
var divContent=document.querySelector("#divContent");
var pBlues=divContent.querySelectorAll("p.blue");
//遍历NodeList实例pBlues,将字体加下划线
for (var i=0,len=pBlues.length;i<len;i ){
pBlues[i].style.textDecoration="underline";
//pBlues.item(i).style.textDecoration="underline"; //效果与上一行相同
}
</script>
</body>
</html>
输出结果如图1-20所示。
图1-20 querySelectorAll()示例输出结果
3.2、HTML5 DOM扩展
HTML5的标准中增加了许多JavaScript API,其中有一些API与DOM重叠,定义了浏览器应该支持的DOM扩展,兼容性要逊色于选择符API。
(1)、getElementsByClassName()方法
方法接收一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的HTMLCollection。
代码语言:javascript复制//获得所有类名包含blue的元素
var blues=document.getElementsByClassName("blue");
//获得所有类名中包含"blue"与"red"的元素
var blueReds=document.getElementById("div1").getElementsByClassName("blue red");
(2)、classList属性
比起使用字符串属性className操作类名,classList要方便很多,classList是一个DOMTokenList类型的实例,包含如下属性与方法如表1-4 所示。
序号 | 类型 | 意义 |
---|---|---|
1 | DOMTokenList.length | 只读属性,一个整数,表示存储在该对象里值的个数 |
2 | DOMTokenList.item() | 根据传入的索引值返回一个值 |
3 | DOMTokenList.contains() | 如果 DOMTokenList 列表中包括相应的字符串,则返回 true,否则返回 false |
4 | DOMTokenList.add() | 添加一个标记(token)到 DOMTokenList 列表中 |
5 | DOMTokenList.remove() | 从 DOMTokenList 列表中移除一个标记(token) |
6 | DOMTokenList.toggle() | 从 DOMTokenList 字符串中移除标记字串(token),并返回 false。如果传入的符号字串(token)不存在,则将其添加进去,并返回 true |
表1-4 DOMTokenList类型对象的成员
代码语言:javascript复制<body>
<h2 class="blue red bold">classList示例</h2>
<script>
//获得标签名为h2的元素的样式列表对象
var h2ClassList=document.querySelector("h2").classList;
//删除类样式red与bold
h2ClassList.remove("red","bold");
//添加样式big与green
h2ClassList.add("hide","green");
//元素中是否包含hide样式
console.log(h2ClassList.contains("hide"));
//切换样式hide
h2ClassList.toggle("hide");
//遍历样式
for(var i=0,len=h2ClassList.length;i<len;i ){
console.log(h2ClassList.item(i)); //h2ClassList[i]也可以获取样式名称
}
</script>
</body>
输出结果如图1-21所示。
图1-21 classList示例输出结果
(3)、焦点管理
activeElement属性始终会引用 DOM 中当前获得了焦点的元素。
document.hasFocus()方法用于确定文档是否获得了焦点,可以知道用户是不是正在与页面交互。
(4)、文档加载
document.readyState属性可以获得当前文档的加载状态,值可以是uninitialized(表示还未开始加载)、loading(表示加载中)、interactive(表示已加载,文档与用户可以开始交互)、complete(表示加载完成)。
DOMContentLoaded事件表示页面中DOM加载完成,也就是页面文本内容加载完成,可能还有一些外面资源并未加载完成,如图片、音频、视频等。
load事件表示所有资源都加载完成了,他发生在DOMContentLoaded事件之后。
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>readystatechange与页面加载事件</title>
</head>
<body>
<script>
window.addEventListener("readystatechange",function (event) {
console.log("> readystatechange,页面状态发生变化,当前状态" document.readyState)
console.log(event);
},true);
window.addEventListener('DOMContentLoaded', (event) => {
console.log("> DOMContentLoaded,DOM加载完成");
},true);
window.addEventListener('load', (event) => {
console.log("> onload,所有的资源(图片,音频,视频等)被加载完成");
},true);
</script>
</body>
</html>
输出结果如图1-22所示。从输出结果中你可以看出页面加载的部分生命周期。
图1-22 文档加载示例输出结果
(5)、自定义数据属性dataset
有时候我们需要给标签中添加额外的属性,用于暂存数据,HTML5标准下可以使用data-前缀开始,名称可以自定义。
定义在标签中的属性是以data-name="value"的形式存在,访问时可以通过元素对象获得一个类型为DOMStringMap的集合dataset,通过name作为key就可以访问dataset中的数据了。
代码语言:javascript复制<body>
<div id="div1" data-size="100" data-user="{name:'tom',sex:'男'}"></div>
<script>
//获得编号为div1的元素
var div1=document.querySelector("#div1");
//从div1中获得名称为size的数据
console.log(div1.dataset.size);
//修改div1中名称为size的数据值为200
div1.dataset.size=200;
console.log(div1.dataset["size"]);
//删除div1中名称为size的数据
delete div1.dataset.size;
console.log(div1.dataset["size"]);
console.log(div1.dataset);
</script>
</body>
输出结果如图1-23所示。
图1-23 自定义数据属性dataset示例输出结果
自定义的数据可以是JSON对象,但访问时需要将字符转换成对象,直接访问不存在的对象将返回undefined。如果使用jQuery可以通过data方法方便的访问dataset。
(6)、scrollIntoView()方法
HTML5通过scrollIntoView()方法解决页面滚动的问题,该方法将调用它的元素滚动到浏览器窗口的可见区域。可以实现聊天窗口滚动显示最新的消息、回到顶部、往一个列表添加数据项后滚动显示最新的添加的数据项等应用。
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>scrollIntoView</title>
<style>
ul{
height: 70px;
width: 200px;
overflow: auto;
}
ul li{
height: 25px;
line-height: 25px;
}
</style>
</head>
<body>
<ul>
<li>对话1</li>
<li>对话2</li>
<li>对话3</li>
<li>对话4</li>
<li>对话5</li>
<li>对话6</li>
<li>对话7</li>
</ul>
<button id="btnScroll">滚动到第5条对话</button>
<script>
document.querySelector("#btnScroll").addEventListener("click",function () {
//获得第5个li元素,并滚动到可显示的位置
document.querySelectorAll("li")[4].scrollIntoView();
},true);
</script>
</body>
</html>
输出结果如图1-24所示。
图1-24 scrollIntoView()方法示例输出结果
小贴士:scrollIntoView()方法可带参数,true表示顶部对齐父元素,false表示底部对齐;另外还可以通过参数设置动画效果。
四、BOM扩展
浏览器对象模型(Browser Object Model,简称BOM),用于访问浏览器功能。如果说ECMAScript是语法的核心,DOM是文档的核心,那么浏览器的核心就是BOM对象了。这里主要介绍location、history与navigator对象中的部分属性与方法。
4.1、location对象
location对象用于控制当前位置,就是与地址栏相关内容,比如想要跳转到某个页面,或者通过URL获取一定的内容。通过window.location或document.location可以访问该对象,两种访问方式都是指向了同一个对象,表1-5列出了location对象的主要属性。
属性 | 描述 | 例子 |
---|---|---|
hash | 设置或返回从井号(#)开始的URL(锚) | #page1 |
host | 设置或返回主机名和当前URL的端口号 | www.example.com:8088 |
hostname | 设置或返回当前URL的主机名 | www.example.com |
href | 设置或返回完整的URL | http://www.example.com:8088/path/index.html?id=1&page=2#page1 |
pathname | 设置或返回当前URL的路径部分 | path/index.html |
port | 设置或返回当前URL的端口号 | 8088,如果是默认80端口,返回空字符 |
protocol | 设置或返回当前URL的协议 | http |
search | 设置或返回从问号(?)开始的URL(查询部分) | ?id=1&page=2 |
表1-5 location对象的主要属性
在开发中经常需要通过路由为用户设置导航,特别是在一些SPA(单页应用)中,这里侧重介绍一下hash与hashchange事件。
(1)、URL构成
格式:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
举例:
http://tom:123456@www.example.com:80/path/to/index.html?id=1&type=2#page3
http表示协议类型,如http,https,ftp等;tom:123456@表示用户名与密码,一般会省去;www.example.com为域名,www是主机名,example是单位名称,.com是机构类型,这是一个2级域名;80表示端口号;/path/to/index.html表示要访问的文件路径;id=1&type=2表示查询参数;#page3表示页面内部标签(锚记)。
(2)、hash
hash即URL中"#"字符后面的部分,通过window.location.hash属性获取和设置hash值,它主要有如下特点:
1.hash 属性是一个可读可写的字符串,该字符串是URL从#号开始的部分;
2.使用浏览器访问网页时,如果网页URL中带有hash,页面就会定位到id(或name)与hash值一样的元素的位置;
3.hash改变不会引起页面重新加载;
4.hash值不会发送到服务器端;
5.hash值变化会显示到浏览器地址栏,地址栏hash值的变化会触发onhashchange事件;
6.改变#会改变浏览器的访问历史
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>hash</title>
</head>
<body>
<p style="height: 800px;" id="p1">第一段</p>
<p style="height: 800px;" id="p2">第二段</p>
<form method="post">
<button type="submit">提交</button>
</form>
<script>
//获得当前页的hash值并追加到p2中
document.querySelector("#p2").innerHTML =window.location.hash;
</script>
</body>
</html>
运行结果如图1-25所示。
图1-25 hash示例运行结果
从HTML脚本中可以看出页面中共有两个段落p1与p2,直接访问页面时url中带有#p2所以第二段显示在最顶端。点击提交按钮后发送到服务器的参数中并没有带上#p2这一段。此时后退按钮是可以使用的,说明hash影响访问的历史记录。
(3)、hashchange事件
这是一个HTML 5新增的事件,当hash值发生变化时,该事件会触发。IE8 、Firefox 3.6 、Chrome 5 、Safari 4.0 支持该事件。
代码语言:javascript复制<body>
<a href="#p1">到第一段</a> |
<a href="#p2">到第二段</a> |
<a href="#p3">到第三段</a>
<script>
addEventListener("hashchange", function (e) {
console.log(e);
}, false);
</script>
</body>
运行结果如图1-26所示。
图1-26 hashchange事件示例运行结果
从运行输出的结果可以看出当点击第二段时触发了了hashchange事件,事件对象中有许多属性可以访问,newURL表示当前URL,oldURL表示原URL,timeStamp表示访问的时间戳。
小贴士:使用hash与hashchage事件就可以完成一个简单的单页程序了,当然你可以先去了解一下Hjax技术。
4.2、history对象
history对象用于管理用户访问网页的历史记录。使用window.history就可以访问到该对象了,history.length属性表示历史列表中的网址数;history.back()方法表示加载history列表中的前一个URL;history.forward()方法表示加载history列表中的下一个 URL;history.go(n)方法表示加载history列表中的某个具体页面,可以是负值,类似点击了浏览器的前进与后退按钮。
前面我们可以通过hash与hashstate来管理页面内的当前状态,但这并不是一个SEO友好的方式,HTML5为history对象添加了两个新方法,history.pushState()和history.replaceState(),用来在浏览历史中添加和修改记录。state属性用来保存记录对象,而popstate事件用来监听history对象的变化。
(1)、history.pushState()方法
history.pushState()方法向浏览器历史中添加了一个状态,可以通俗的理解为在浏览器的访问历史记录中添加了一条记录。语法如下:
history.pushState(state, title, url);
state:是一个自定义的状态对象,触发popstate事件后可用,使用该对象可以实现传递状态数据。
Title:新页面的标题,几乎所有浏览器目前都忽略该值,可以指定null值。
URL:表示URL地址,必须在同一个域,否则异常。该参数可选,默认为当前URL。
添加后并不会跳转到指定的URL,但地址栏会被立即修改。pushState方法不会触发页面刷新,不会触发hashchange事件。
代码语言:javascript复制<body>
<button id="btnAdd" type="button">添加一个访问的历史状态</button>
<script>
//输出当前历史状态个数
console.log(history.length);
//为按钮btnAdd绑定点击事件
document.querySelector("#btnAdd").addEventListener("click",function () {
//添加一个访问的历史状态,第1个参数为自定义数据对象,第2个参数表示标题,第3个参数表示URL
history.pushState({data:'foo'},'新页面1','c0135.html');
console.log(history.length);
},false);
</script>
</body>
运行时的初始状态如图1-27所示,URL为c036.html,当前的访问历史记录为1条。
图1-27 执行pushState前的页面状态
当点击页面中的按钮后向历史记录中添加了一个新的访问状态,URL立即变成了c035.html,页面并没有刷新,也没有跳转到c035.html页面,标题也没有变化,历史记录总数变成了2,如图1-28所示。
图1-28 执行pushState后的页面状态
(2)、history. replaceState()方法
history.replaceState()方法的参数与pushState()方法一样,但pushState()方法是实现添加功能,而replaceState()方法是实现修改当前历史状态功能,页面同样不会立即跳转到修改后的URL,但再次刷新页面时将会跳转到修改后的URL。
代码语言:javascript复制<button id="btnModify" type="button">修改当前访问的历史状态</button>
<script>
//输出当前页面的状态对象
console.log(history.state);
document.querySelector("#btnModify").addEventListener("click",function () {
//修改当前访问的历史状态,第1个参数为自定义数据对象,第2个参数表示标题,第3个参数表示URL
history.replaceState({data:'foo'},'新页面1','c0135.html');
console.log(history.length);
console.log(history.state);
},false);
</script>
当前页面为c0137.html,点击修改历史状态后URL立即被修改,但总数量仍然为1。
图1-29 执行replaceState后的页面状态
如果再次刷新页面将跳转到c0135.html这个页面,如图1-30所示。
图1-30 执行replaceState并刷新后的页面状态
(3)、popstate事件
页面的历史状态发生变化时将触发popstate事件,使用pushState方法或replaceState方法,并不会触发该事件,只有点击浏览器前进和后退按钮,或使用back()、forward()、go()方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
代码语言:javascript复制<a href="#page1.html" id="page1">页面一</a> |
<a href="#page2.html" id="page2">页面二</a>
<script>
//添加浏览历史状态变化事件
addEventListener("popstate", function (e) {
console.log("popstate事件被引发,%o", e.state);
}, false);
</script>
运行时的状态如图1-31所示。
图1-31 popstate事件示例运行结果
上图中的输出结果1是点击"页面一"超链接输出的结果,2与3是直接在控制台执行脚本响应的结果。
现在主流的页面路由(Router)基层实现一是采用hash的方案,第二就是采用H5的history API。
小贴士:使用pushState、replaceState方法与popstate事件就可以完成一个简单的单页程序了,当然你可以先去了解一下Pjax技术。
4.3、navigator对象
navigator对象记录了与客户端浏览器相关的基本信息,如名称、版本和系统等,HTML5增强了该对象。通过 window.navigator可以引用该对象,并利用它的属性来读取客户端基本信息。
属性 | 描述 |
---|---|
appCodeName | 当前浏览器的内部"开发代号"名称。 |
appMinorVersion | 返回浏览器的次级版本。 |
appName | 以DOMString 的形式返回浏览器官方名称。 |
appVersion | 以DOMString 的形式返回浏览器版本。 |
browserLanguage | 返回当前浏览器的语言。 |
cookieEnabled | 返回指明浏览器中是否启用 cookie 的布尔值。 |
cpuClass | 返回浏览器系统的 CPU 等级。 |
onLine | 返回Boolean来表明浏览器是否联网。 |
platform | 返回运行浏览器的操作系统平台。 |
systemLanguage | 返回 OS 使用的默认语言。 |
userAgent | 返回由客户机发送服务器的 user-agent 头部的值。 |
userLanguage | 返回 OS 的自然语言设置。 |
battery | 返回一个BatteryManager对象,获取一些电池充电状态的信息 |
connection | 提供一个NetworkInformation对象来获取设备的网络连接信息 |
plugins | 返回PluginArray数组用于列举出浏览器安装的插件。 |
geolocation | 返回一个 Geolocation 对象,据之可访问设备的地理位位置信息。 |
oscpu | 返回当前操作系统名。 |
vibrate(n) | 震动方法,n是指定震动的时长(毫秒)。 |
表1-6 navigator对象的部分成员
(1)、userAgent属性
navigator.userAgent属性是一个只读的字符串,声明了浏览器用于 HTTP 请求的用户代理头的值。该属性的使用频度非常高。
console.log(navigator.userAgent);
在chrome浏览器上运行后输出结果如下:
Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.17 Safari/537.36
在Firefox浏览器上运行后输出结果如下:
Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
在IE8浏览器上运行后输出的结果如下:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)
Android手机上运行后输出的结果如图1-32所示:
图1-32 Android手机端获得的userAgent信息
五、课后作业
5.1、上机任务一(50分钟内完成)
上机目的
掌握JavaScript基础知识的运用。
上机要求
使用JavaScript完成一个积分等级换算功能,运行时的效果如图1-33所示。
图1-33 积分等级换算
1、文档框的积分默认值为0,只允许输入数字。
2、输入完成后立即计算等级,并显示与等级对应的星号个数,规则如下:
>20分 5级
>15分 4级
>10分 3级
>5分 2级
>0分 1级
<=0分 0级
3、亮的星与灰色的星号总数始终为5个。
推荐实现步骤
步骤1:完成页面布局,星形素材可以使用中文字符★、图片或字体,图片可以在(https://www.iconfont.cn/)下载到。
步骤2: 编写JavaScript脚本,分阶段完成功能。
步骤3: 测试运行效果、优化代码。
5.2、上机任务二(30分钟内完成)
上机目的
掌握扩展内容的应用。
上机要求
完成一个积分等级换算功能,在阶段一的基础上要求满足以下要求:
- 使用严格模式。
- 脚本中不允许使用switch与if,可以考虑使用逻辑运算符的非布尔类型运算。
- 增加异常处理部分。
- 操作DOM时优先选择DOM扩展提供的方法与属性。
- 封装代码,所有脚本对外只暴露一个对象。(选作)
5.3、上机任务三(50分钟内完成)
上机目的
应用BOM与AJAX技术、了解SPA。
上机要求
使用JavaScript完成一个简单的SPA(单页应用程序),运行时的效果如图1-34所示。
1、点击页面一时动态加载page1.html中的内容,点击页面二时动态加载page2.html中的内容,页面不刷新。
2、page1.html与page2.html需要预先定义好,加载内容可使用AJAX技术。
3、必须使用严格模式。
图1-34 简单的SPA
推荐实现步骤
步骤1:完成布局。
步骤2:编写JavaScript脚本逐步实现功能。
步骤3:测试运行效果,优化代码,关键位置书写注释,必要位置进行异常处理。
5.4、上机任务四(50分钟内完成)
上机目的
掌握navigator对象与正则的应用。
上机要求
完成一个JavaScript函数queryString(name),实现获取URL中参数的功能:
假定当前的URL: http://127.0.0.1?id=1&name=tom
1、指定name则返回name对应的参数,如queryString("id"),则返回1。
2、果不指定参数则返回一个数组,数据中包含所有参数,如queryString(),则返回的数据是:[{key:"id",value:"1"},{key:"name",value:"tom"}]。
3、如果有重名参数则以数组的形式返回,如?id=1&id=3,则返回[1,3],不指定参数时返回:[{key:"id",value:[1,3]}]。
5.5、上机任务五(20分钟内完成)
上机目的
掌握navigator对象与正则的应用。
上机要求
完成一个JavaScript函数IsMobile(),实现判断用户是否是在移动端打开当前页面的功能:
1、如果用户在手机上打开该页面时返回true。
2、如果用户在电脑上打开该页面时返回false。
提示:navigator.userAgent属性是一个只读的字符串,声明了浏览器用于 HTTP 请求的用户代理头的值。
5.6、任务管理(MyTask)
一、使用JavaScript完成一个任务管理(MyTask)功能,如图1-35所示,要求如下:
(1)、使用DOM与数组完成,不需要数据库。
(2)、实现列表、添加、修改、删除、多删除功能。
(3)、完成状态切换功能,在完成与未完成间切换。
(4)、不能使用jQuery,用原生的JavaScript完成。
(5)、不需要后台,全选与反选要求子项联动父项。
(6)、任务名称只能是2-20位的合法字符(中文、英文与数字)。
(7)、使用严格模式。
图1-35 我的任务