花了半个多月的时间,终于又把“JS红宝书”又撸了一遍。
第一次读“JS红宝书”还是2015年初学JS的时候,那时候只是把语法部分读了一遍,还有一些浏览器相关知识做了下了解,大概也就读了半本的样子,
就开始了用JS进行开发了,在成长的道路上遇见了JQuery,当时真的是感觉到JQuery太友好了,慢慢放下了原生开发。
现在呢,更多的时候是在用框架进行开发,越来越觉得自己的JS基础很缺乏,然后就开启了“JS红宝书”二刷之路。
下面就把书中自己觉得重要的、没有掌握的知识整理出来。因为我觉得还是会三刷“JS红宝书”,希望把这本700多页的书越读越薄,勉励。
在HTML中使用JavaScript
async
加载外部脚本文件,通知浏览器立即下载,异步执行。
noscript
元素
noscript
标签显示条件:
- 浏览器不支持脚本
- 浏览器支持脚本,但是脚本被禁用
基本概念
语法
标识符
- 第一个字符必须是一个字母、下划线或者一个美元符号
- 其他字符可以是字母、下划线、美元或者数字
严格模式
支持严格模式的浏览器包括:IE10+、Firefox4+、Safari 5.1+、Opera 12+和Chrome。
数据类型
undefined
对未初始化的变量执行typeof
操作会返回undefined
值,而对于未声明的变量执行typeof
操作同样会返回undefined
值。
null
1 | typeof null // -> object |
undefined值派生自null
值。1
console.log(null == undefind) // -> true
isFinite()
测试一个数值是不是无穷值。
Number.NEGATIVE_INFINITY
:负无穷Number.POSITION_INFINITY
:正无穷
NaN
在ECMAScript中,任何数值除以0会返回NaN
。
isNaN()
接受一个参数,确定这个参数是否”不是数值”。
数值转换
Number()
- 如果是
null
,返回0
- 如果是
undefined
,返回NaN
parseInt()
在ES5 中不支持解析八进制的能力。1
parseInt('070'); // -> 70 not 56
通过第二个参数,指定转换基数(进制)默认十进制。
字符串
- ECMAScript中的字符串是不可变的
toString()
在调用数值的toString
方法,可以传递一个参数:输出数值的基数。没有toString
方法的则返回该值的字面量1
2var num = 10;
console.log(num.toString(2)); // -> '1010'
object类型
Object类型的属性方法:
- constructor
- hasOwnProperty(propertyName)
- isPrototypeOf(obj)
- propertyIsEnumerable(propertyName)
- toLocalString()
- toString()
- valueOf()
操作符
++
or--
前置与后置的区别1
2
3
4
5
6
7
8
9var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2; // 21
var num4 = num1 + num2; // 21
var num5 = 2;
var num6 = 20;
var num7 = num5-- + num6; // 22
var num8 = num5 + num6; // 21
- 一元加操作符用于强制类型转换,隐式
Number()
效果
for-in 语句
for-in
语句是一种精确的迭代语句,可以用来枚举对象的属性。
通过for-in
循环输出的属性名的顺序是不可预测的。
如果要迭代的对象的变量值为null
或undefined
,for-in
语句会抛出错误。ES5更正了这一行为,不再抛出错误,只是不再执行循环体。
建议:在是使用for-in
循环之前,先检查对象值是不是null
或者undefined
。
变量、作用域和内存问题
基本类型和引用类型
复制变量值
- 复制基本类型值,这两个变量相互独立,互不影响。
- 复制引用类型(对象),值引用是一个指针,改变其中一个对象,会影响另一个对象。
传递参数
1 | function setName(obj) { |
以上代码中创建一个对象,并将其保存在了变量person
中。然后,这个变量被传递到setName()
函数中之后就被复制给了obj
。在这个函数部,obj
和person
引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj
也会按引用来访问同一个对象。于是,当在函数内部为obj
添加name
属性后,函数外部的person
也将有所反映;因为person
指向的对象在堆内存中只有一个,而且是全局对象。有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:1
2
3
4
5
6
7
8function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
这个例子与前一个例子的唯一区别,就是在setName()
函数中添加了两行代码:一行代码为obj
重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name
属性。在把person
传递给setName()
后,其name
属性被设置为”Nicholas”。然后,又将一个新对象赋给变量obj
,同时将其name
属性设置为”Greg”。如果person
是按引用传递的,那么person
就会自动被修改为指向其name
属性值为”Greg”的新对象。但是,当接下来再访问person.name
时,显示的值仍然是”Nicholas”。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj
时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
检测类型
虽然在检测基本数据类型时typeof
是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供了instanceof
操作符。
延长作用域
try-catch
语句中的catch
块with
语句
小结
- 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
- 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
- 引用类型的值是对象,保存在堆内存中;
- 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
引用类型
Array类型
检测数组
1 | if (value instanceof Array) { |
ECMAScript5新增了 Array.isArray()
方法1
2
3if (Array.isArray(value)) {
}
sort方法
该方法有缺陷,sort()
方法会调用每个数组项的toString()
转型方法,然后比较字符串进行排序。1
2
3var values = [0, 1, 5, 10, 15];
values.sort();
alert(values); //0,1,10,15,5
因此sort()
方法接受一个比较函数作为参数。1
2
3
4
5
6
7
8
9
10
11
12
13function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15
splice方法
splice
方法始终返回一个数组,该数组包含了从原始数组中删除的项。
1 | var colors = ["red", "green", "blue"]; |
迭代方法
ECMAScript5为数组定义了5个迭代方法。
every()
: 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。filter()
: 对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。forEach()
: 对数组中的每一项运行给定函数。这个方法没有返回值。map()
: 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。some()
: 对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
归并方法
ECMAScript 5 还新增了两个归并数组的方法。
reduce()
reduceRight()
reduce()
和reduceRight()
的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。1
2
3
4
5var values = [1, 2, 3, 4, 5];
var sum = values.reduce((prev, cur, index, array) => {
return prev + cur;
});
console.log(sum);
RegExp类型
正则表达式中的元字符
( [ { \ ^ $ | ) ? * + . ] }
注:匹配元字符必须转义
RegExp
构造函数
接受两个参数: 一个是要匹配的字符串模式,另一个是可选的标志字符串。1
2
3var pattern1 = /[bc]at/i;
// 等价于
var pattern2 = new RegExp('[bc]at', 'i');
注:由于RegExp构造函数的模式是字符串,所以在某些情况下要对字符串进行双重转义,所有元字符都必须双重转义。例如\n
在字符串中被转义为\\n
,而在正则表达式字符串中就会变成\\\\n
。
RegExp
实例方法
exec()
方法
该方法是专门为捕获组而设计的。exec()
接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null
。返回的数组虽然是Array 的实例,但包含两个额外的属性:index
和input
。其中,index
表示匹配项在字符串中的位置,而input
表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。1
2
3
4
5
6
7
8
9var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
alert(matches.index); // 0
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
aler t(matches[2]); // " and baby"
对于exec()
方法而言,即使在模式中设置了全局标志(g
),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()
将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()
则都会在字符串中继续查找新匹配项。
test()
方法
接受一个字符串参数。在模式与该参数匹配的情况下返回true
;否则,返回false
。1
2
3
4
5
6var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
alert("The pattern was matched.");
}
RegExp实例继承的toLocaleString()
和toString()
方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。1
2
3var pattern = new RegExp("\\[bc\\]at", "gi");
alert(pattern.toString()); // /\[bc\]at/gi
alert(pattern.toLocaleString()); // /\[bc\]at/gi
Function类型
函数声明与函数表达式
解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。1
2
3
4
5
6
7
8
9
10
11// ok
alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}
// unexpected identifier(意外标识符)
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};
注:要访问函数的指针而不执行函数的话,必须去掉函数名后的那对圆括号。
函数内部属性
arguments
this
arguments
具有一个callee
属性,该属性是一个指针,指向拥有这个arguments
对象的函数。1
2
3
4
5
6
7function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}
等价于1
2
3
4
5
6
7function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
达到一种解耦的效果。
ECMAScript 5也规范了一个函数对象属性:caller
(看着很像callee
),这个属性中保存着调用当前函数的函数的引用,如果实在全局作用域中调用当前函数,它的值为null
。1
2
3
4
5
6
7function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
inner.caller
指向outer()
。为了实现更松散的耦合,也可以通过argument.callee.caller
来访问相同的信息。1
2
3
4
5
6
7function outer() {
inner();
}
function inner() {
alert(arguments.callee.caller);
}
outer();
注:当函数在严格模式下运行时,访问arguments.callee
会导致错误。ECMAScript 5 还定义了arguments.caller
属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined
。定义这个属性是为了分清arguments.caller
和函数的caller
属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
严格模式还有一个限制:不能为函数的caller
属性赋值,否则会导致错误。
函数属性和方法
每个函数都包含两个属性:
length
: 表示函数希望接收的命名参数的个数prototype
: 保存实例方法
每个函数都包含两个非继承而来的方法:
apply()
call()
这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this
对象的值。首先,apply()
方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array
的实例,也可以是arguments
对象。call()
方法与apply()
方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()
方法而言,第一个参数是this
值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()
方法时,传递给函数的参数必须逐个列举出来。
注:在严格模式下,未指定环境对象而调用函数,则this
值不会转型为window
。除非明确把函数添加到某个对象或者调用apply()
或call()
,否则this 值将是undefined
。
在非严格模式下,call
、apply
的第一个参数传递为null
或undefined
时,函数体内的this
会指向默认的宿主对象,在浏览器中则是window
。
ECMAScript 5定义了一个方法bind()
,这个方法会创建一个函数的实例,其this
值会被绑定到传给bind()
函数的值。
基本包装类型
使用new
调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。1
2
3
4
5
6var value = '25';
var number = Number(value); // 转型函数
console.log(typeof number); // 'number'
var obj = new Number(value); // 构造函数
console.log(typeof obj); // 'object'
Number类型
Number类型的toString()
方法很特别,可以传递一个表示基数的参数。1
2
3
4
5
6var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"
String类型
字符方法
charAt()
charCodeAt()
1 | var stringValue = "hello world"; |
字符串操作方法
concat()
slice()
substr()
substring()
这些方法对原字符均没有任何影响。
1 | var stringValue = "hello "; |
字符串位置方法
indexOf()
lastIndexOf()
两个方法的第二个参数,表示从字符串中哪个位置开始搜索。
trim()方法
ECMAScript 5方法
字符串转换大小写方法
toLowerCase()
toLocaleLowerCase()
toUpperCase()
toLocaleUpperCase()
字符串的模式匹配方法
match()
search()
replace()
split()
match()
方法,在字符串上调用这个方法,本质上和调用RegExp
的exec()
方法相同。match()
方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。1
2
3
4
5
6
7
8var text = 'cat, bat, sat, fat';
var pattern = /.at/;
// 等价于 pattern.exec(text)
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //"cat"
alert(pattern.lastIndex); //0
search()
方法的参数与match()
方法相同,该方法返回字符串中第一个匹配项的索引,没有匹配项返回-1
;个人认为serch()
就是正则版的indexOf()
。1
2
3var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
aler t(pos); //1
ECMAScript提供了replace()
方法,该方法接受两个参数,第一个参数可以是RegExp对象或者是一个字符串,第二个参数可以是一个字符串或者一个函数。1
2
3
4
5
6var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
aler t(result); //"cond, bond, sond, fond"
字符序列 | 替换文本 |
---|---|
$$ |
$ |
$& |
匹配整个模式的子字符串。RegExp.lastMatch |
$' |
匹配子字符串之前的字符串。RegExp.leftContext |
$` | 匹配的子字符串之后的字符串。 RegExp.rightContext |
$n |
匹配第n个捕获组的子字符串 n: 0~9 |
$nn |
匹配第nn个捕获组的子字符串 nn: 01~99 |
1 | var text = "cat, bat, sat, fat"; |
split()
方法可以基于指定的分隔符(字符串 or RegExp对象)将一个字符串分割成多个子字符串,并将结构放在一个数组中。可以接受可选的第二个参数,用于指定数组的大小。1
2
3
4var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"]
var colors2 = colorText.split(",", 2); //["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
localeCompare()方法
比较两个字符串,并返回下列值中的 一个:
- 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数
- 如果字符串等于字符串参数,则返回0;
- 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数
1 | var stringValue = "yellow"; |
fromCharCode()方法
这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。相当于charCodeAt()
反操作。1
alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"
Math 对象
min()
max()
ceil()
floor()
round()
random()
面向对象的程序设计
理解对象
属性类型
数据类型
[[Configurable]]
: 表示能否通过delete
删除属性从而重新定义属性,能够修改属性的特性,或者能否把属性修改为访问器属性[[Enumerable]]
: 表示能否通过for-in
循环返回属性[[Writable]]
: 表示能否修改属性的值[[Value]]
: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值undefined
要修改属性默认的特性,必须使用ECMAScript 5的Object.defineProperty()
方法。这个方法接受三个参数:属性所在对象,属性名和一个描述符对象。其中描述符对象的属性值必须是:configurable
、enumerable
、writable
和value
。设置其中一个或多个。1
2
3
4
5var person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: 'Yeaseon'
});
Object.defineProperty()
方法不能对configurable: false
的对象进行修改。
访问器属性
[[Configurable]]
: 表示能否通过delete
删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。[[Enumerable]]
: 表示能否通过for-in
循环返回属性。[[Get]]
: 在读取属性时调用的函数,默认undefined
[[Set]]
: 在写入属性时调用的函数,默认undefined
1 | var book = { |
读取属性的特性
ECMAScript 5的Object.getOwnPropertyDescriptor()
方法,可以取得给定属性的描述符。该方法接收两个参数:属性所在的对象和要读取器描述符的属性名称,返回值是对象。
创建对象
工厂模式
1 | function createPerson(name, age, job){ |
构造函数模式
1 | function Person(name, age, job){ |
原型模式
1 | function Person(){ |
Person.prototype.constructor
会指向Person
,person1
并没有直接连接到构造函数Person
。
- 可以通过
isPrototypeOf()
方法来确定对象之间是否存在原型关系。从本质上讲,[[Prototype]]
指向调用isPrototypeOf()
方法的对象Person.prototype
,则会返回true
。
1 | alert(Person.prototype.isPrototypeOf(person1)); //true |
在ECMAScript 5增加了Object.getPrototypeOf()
方法,该方法返回[[Prototype]]
的值。1
2alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
注: 虽然可以通过对象实例person1
访问保存在原型中的值,但却不能重写原型中的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg"; //实质是在实例上增加一个name属性
alert(person1.name); //"Greg"——来自实例
alert(person2.name); //"Nicholas"——来自原型
可以通过delete
删除实例属性,从而继续访问原型中的属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
hasOwnProperty()
方法可以检测一个属性是不是存在于实例,是则返回true
。
in
操作符
(prop in obj)通过in
操作符可以判定对象是否有该属性,不论是本身含有还是原型含有,都返回true
。
可以通过in
配合hasOwnProperty()
确定该属性是存在于对象中还是原型中:1
2
3
4
5
6
7function detectProperty(obj, name) {
if (name in obj) {
obj.hasOwnProperty(name) ? '在对象中' : '在原型中';
} else {
console.log('不含有该属性');
}
}ECMAScript 5
Object.keys()
方法可以取得对象上所有可枚举的实例属性。
1 | function Person(){ |
Object.getOwnPropertyNames
会得到所有实例属性,不论是否可枚举。
1 | var keys = Object.getOwnPropertyNames(Person.prototype); |
简化Person.prototype
写法:1
2
3
4
5
6
7
8
9
10function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
这样写有一个缺陷,constructor
属性则会等于Object
,我们需要手动设置constructor
。1
2
3
4
5
6
7
8
9
10
11function Person(){
}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
但这同时也会导致constructor
的[[Enumerable]]
特性变成了true
,默认情况下是false
。再修改下写法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
原型重写会导致构造函数与最初原型之间的联系切断。1
2
3
4
5
6
7
8
9
10
11
12
13function Person(){
}
var friend = new Person();
Person.prototype = { //重写
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
结合使用构造函数和原型模式
用构造函数模式定义实例属性,用原型模式定义方法和共享属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
继承
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型的内部指针。
原型链
1 | function SuperType () { |
instanceof
操作符
用来确定原型和实例之间的关系。1
2
3alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
第二种方式就是isPrototypeOf()
方法,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。1
2
3alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
函数表达式
由于有声明提升的存在,定义函数不要放在条件表达式中1
2
3
4
5
6
7
8
9if (condition) {
function sayHi () {
console.log('Hi');
}
} else {
function sayHi () {
console.log('Yo');
}
}
在ECMAScript中属于无效语法,在不同浏览器中修正的做法并不一致。推荐的写法,如下:1
2
3
4
5
6
7
8
9
10
11var sayHi;
if (condition) {
sayHi = function () {
console.log('Hi');
}
} else {
sayHi = function () {
console.log('Yo');
}
}
这种函数表达式不存在声明提升,所以OK。
递归
函数作用域链
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments
和其他命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位…,直到作用域终点的全局执行环境。
闭包
1 | function createFunctions () { |
1 | function createFunction () { |
注:在闭包中使用this
对象可能会导致一些问题。匿名函数的执行环境具有全局性,因此其this
对象通常指向window
。1
2
3
4
5
6
7
8
9
10
11
12var name = 'The window';
var obj = {
name: 'my object',
getNameFunc: function () {
return function () {
return this.nam;
}
}
}
console.log(obj.getNameFunc()()); // The Window (非严格模式)
模仿块级作用域
块级作用域
1 | (function () { |
BOM
window 对象
全局作用域
抛开全局变量会成为window
对象的属性不谈,定义全局变量与在window
对象上直接定义属性还是有一点差别:全局变量不能通过delete
操作符删除,而直接定义在window
对象上的定义的属性可以。
窗口位置
获得窗口左边和上边的位置。1
2var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
Firefox支持screenX
和screenY
,其他浏览器均支持screenLeft
、screenTop
。
但是还是需要注意一个问题:在IE
Opera
中,screenLeft screenTop
保存的的是可见区域的距离,也就是我们浏览器中不包含工具栏的区域与屏幕的距离;在Chrome
、Firefox
和Safari
中screenY
或screenTop
返回的是整个浏览器窗口相对于屏幕坐标的值。
窗口大小
IE9+、Firefox、Safari、Opera和Chrome均提供了4个属性innerWidth
、innerHeight
、outerWidth
和outerHeight
。
- IE9+、Safari和Firefox中,
outerWidth
和outerHeight
返回浏览器窗口本身的尺寸,而innerWidth
和innerHeight
则表示该容器中页面视图区的大小(减去边框宽度) - Chrome中,
inner*
和outer*
返回相同的值,即视口大小而非浏览器窗口的大小。 - 在IE、Firefox、Safari、Opera和Chrome中,都能通过
document.documentElement.clientWidth
和document.documentElement.clientHeight
中保存了页面视口信息。
获取页面视口大小1
2
3
4
5
6
7
8
9
10
11
12var pageWidth = window.innerWidth,
pageHeight = window.innerHeight;
if (typeof pageWidth != 'number') {
if (document.compatMode == 'CSS1Compat') { // 浏览器标准模式
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else { // IE6 混杂模式
pageWidth = document.body.clientWidth;
pageHeight = document.doby.clientHeight;
}
}
resizeTo()
接受浏览器窗口的新宽度和新高度resizeBy()
接受新窗口与原窗口的宽度和高度差。
这两个方法可能被浏览器禁用。
导航
如果是浏览器内置的屏蔽程序组织的弹出窗口,那么window.open()
很可能会返回null
。1
2
3
4var newWindow = window.open('https://www.google.com.hk', '_blank');
if (newWindow == null) {
console.log('The popup was blocked!');
}
如果是浏览器扩展或其他程序组织的弹出窗口,那么window.open()
通常会抛出一个错误。1
2
3
4
5
6
7
8
9
10
11
12
13
14var blocked = false;
try {
var newWindow = window.open('https://www.google.com.hk', '_blank');
if (newWindow == null) {
blocked = true;
}
} catch (ex) {
blocked = true;
}
if (blocked) {
console.log('The popup was blocked');
}
location 对象
location
对象的属性
hash
host
hostname
:与host
不同的是,不带端口号href
pathname
: 返回URL中的目录和(或)文件名port
protocol
search
:返回URL的查询字符串,这个字符串?
开头
navigator 对象
location
对象的属性
appCodeName
: 浏览器的名称,通常都是MozillaappMinorVersion
:此版本信息appName
: 完整的浏览器名称appVersion
:浏览器的版本buildID
:浏览器编译版本cookieEnabled
:表示cookie
是否可用cpuClass
:客户端计算机中使用的CPU类型javaEnabled()
:表示当前浏览器中是否启用了javalanguage
: 浏览器的主语言mimeTypes
:浏览器中注册的MIME类型数组onLine
:表示浏览器是都连接到因特网oscpu
:客户端计算机的操作系统或使用的CPUplatform
:浏览器所在的系统平台plugins
:浏览器中安装的插件信息的数组preference()
:设置用户的首选项systemLanguage
:操作系统的语言userAgent
:浏览器的用户代理字符串
DOM
节点层次
Node类型
每个节点都有一个nodeType
属性,用于表明节点的类型。
Node.ELEMENT_NODE(1)
Node.ATTRIBUTE_NODE(2)
Node.TEXT_NODE(3)
Node.CDATA_SECTION_NODE(4)
Node.ENTITY_REFERENCE_NODE(5)
Node.ENTITY_NODE(6)
Node.PROCESSING_INSTRUCTION_NODE(7)
Node.COMMENT_NODE(8)
Node.DOCUMENT_NODE(9)
Node.DOCUMENT_TYPE_NODE(10)
Node.DOCUMENT_FRAGMENT_NODE(11)
Node.NOTATION_NODE(12)
为了确保跨浏览器兼容,将nodeType
属性与数字值进行比较:1
2
3if (someNode.nodeType == 1) {
console.log('Node is an element');
}
nodeName
属性1
2
3if (someNode.nodeType == 1) {
var value = someNode.nodeName; // nodeName的值是元素的标签名
}
节点关系
childNodes
属性
每个节点都有一个childNodes
属性,其中保存着一个NodeList
对象,该对象是一种类数组对象。
parentNode
属性
每个节点都有一个parentNode
属性,该属性指向文档树中的父节点。包含在childNodes
列表中的每个节点相互都是兄弟节点。使用previousSibling
和nextSibling
属性,可以访问其他兄弟节点。
注:列表中第一个节点的previousSibling
属性值为null
,同理列表中最后一个节点的nextSibling
属性也是null
。父节点的firstChild
和lastChild
属性分别指向其childNodes
列表中的第一个和最后一个节点。如果不存在则为null
。
hasChildNodes()
方法在节点包含一个或多个子节点的情况下返回true
,比查询childNodes.length
更简便。
最后一个属性ownerDocument
,该属性指向表示整个文档的文档节点(root),直接返回根节点不需要一层层向上回溯。
操作节点
appendChild()
用于向childNodes
列表的末尾添加一个节点。1
2
3var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
任何DOM节点不可能同时出现在多个位置。1
2
3
4//someNode 有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
insertBefore()
把节点放在指定位置,该方法接受两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点。参照节点是null
的话,insertBefore
与appendChild
执行相同的操作,都插入列表末尾。1
2
3
4
5
6
7
8
9
10
11
12//插入后成为最后一个子节点
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true
//插入后成为第一个子节点
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
//插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true
replaceChild()
替换节点,接受两个参数:要插入的节点和要替换的节点。1
2
3
4
5//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
removeChild()
移除节点,接受一个参数:要被移除的节点。1
2
3
4
5//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);
cloneNode()
复制节点,接受一个布尔值,表示是否深复制。复制后返回的节点没有父节点,可以通过插入等操作手动指定。1
2
3
4
5var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)
var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0
注:cloneNode
方法不会复制DOM节点的js
属性。IE存在一个bug,它会复制事件处理程序。
normalize()
稍后讨论
以上方法的返回值,都是被操作的节点。
Document类型
Document
节点具有下列特征:
nodeType
的值为9nodeName
的值为#document
nodeValue
的值为null
parentNode
的值为null
ownerDocument
的值为null
- 其子节点可能是一个
DocumentType
(最多一个)、Element
(最多一个)、ProcessingInstruction
或Comment
1 | // 通过`documentElement`属性访问<html>元素 |
查找元素的方法:
document.getElementById()
document.getElementsByTagName()
document.getElementsByName()
文档写入:
document.write()
document.writeln()
在字符串尾加换行符(\n)
1 | <script type="text/javascript"> |
Element类型
Element
类型提供了对元素标签名、子节点及特性的访问。
nodeType
的值为1nodeName
的值为元素的标签名nodeValue
的值为null
parentNode
可能是Document
或Element
- 其子节点可能是
Element
、Text
、Comment
、ProcessingInstruction
、CDATASection
或EntityReference
访问元素的标签名,可以使用nodeName
属性,也可以使用tagName
属性,后者更直观。1
2
3
4
5<div id="myDiv"></div>
var div = document.getElementById("myDiv");
alert(div.tagName); //"DIV"
alert(div.tagName == div.nodeName); //true
操作特性的方法:
getAttribute()
setAttribute()
removeAttribute()
attributes
属性
Element
类型是使用attributes
属性的唯一一个DOM节点属性。attributes
属性包含一个NamedNodeMap
。元素的每一个特性都由一个Attr
节点表示,每个节点都保存在NamedNodeMap
对象中。
NamedNodeMap
对象的方法:
getNamedItem(name)
:返回nodeName
属性等于name
的节点removeNamedItem(name)
:从列表中移除nodeName
属性等于name
的节点setNamedItem(node)
:向列表中添加节点,以节点的nodeName
属性为索引item(pos)
:返回位于数字pos
位置处的节点
attributes
属性中包含一系列节点,每个节点的nodeName
就是特性的名称,而节点nodeValue
就是特性的值。1
2
3var id = element.attributes.getNamedItem('id').nodeValue;
// 简写
var id = element.attributes['id'].nodeValue;
创建元素document.createElement()
方法可以创建新元素,这个方法接受一个参数(标签名)1
var div = document.createElement('div');
Text类型
文本节点由Text
类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的HTML字符,但不能包含HTML代码。
nodeType
的值为3nodeName
的值为#text
nodeValue
的值为节点所包含的文本parentNode
是一个Element
操作节点中的文本:
appendData(text)
:将text
添加到节点的末尾deleteData(offset, count)
:从offset
指定的位置开始删除count
个字符insertData(offset, text)
:在offset
指定的位置插入text
replaceData(offset, count, text)
:用text
替换从offset
指定的位置开始到offset+count
为止的文本splitText(offset)
:从offset
指定的位置将当前文本分成两个文本节点substringData(offset, count)
:提取从offset
指定的位置开始到offset+count
为止处的字符串。
在向DOM文档中插入文本之前,应该先对其进行HTML
编码
创建文本节点
document.createTextNode()
1 | var textNode = document.createTextNode("<strong>Hello</strong> world!"); |
DOM 操作技术
使用函数实现加载外部JS
文件1
2
3
4
5
6
7function loadScript(url) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.body.appendChild(script);
}
loadScirpt('xx.js');
IE将<script>
视为一个特殊的元素,不允许DOM访问其子节点。不过可以使用<script>
元素的text
属性指定JS
代码。
操作表格
1 | // create table |
DOM 扩展
选择符 API
querySelector()
方法
querySelector()
方法接受一个CSS选择符,返回与该模式匹配的第一个元素,若没有,返回null
。
可以通过Document
类型调用,也可以通过Element
类型调用,后者只会在该元素后代元素的范围内查找匹配的元素。
querySelectorAll()
方法
querySelectorAll()
方法返回的是所有匹配的元素,是一个NodeList
实例。
matchesSelector()
方法
为Element
类型新增的一个方法,接受一个参数CSS
选择符,如果调用元素与该选择符匹配,返回true
,否则返回false
。
元素遍历
childElementCount
:返回子元素(不包含文本节点和注释)的个数firstElementChild
:指向第一个子元素lastElementChild
:指向最后一个子元素previousElementSibling
:指向前一个兄弟元素nextElementSibling
:指向后一个兄弟元素
不同于前面的返回节点的方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 节点版本
var i,
len,
child = element.firstChild;
while(child != element.lastChild){
if (child.nodeType == 1){ //检查是不是元素
processChild(child);
}
child = child.nextSibling;
}
// 元素版本
var i,
len,
child = element.firstElementChild;
while(child != element.lastElementChild){
processChild(child); //已知其是元素
child = child.nextElementSibling;
}
HTML5
getElementsByClassName()
方法classList
属性,这个属性是新集合类型DOMTokenList
的实例。add(value)
contains(value)
remove(value)
toggle(value)
1 | div.classList.remove("user"); |
焦点管理
document.activeElement
属性,始终会引用DOM中前端获得了焦点的元素。1
2
3var button = document.getElementById("myButton");
button.focus();
alert(document.activeElement === button); //true
document.hasFocus()
方法,可以确定文档是否获得了焦点。1
2
3var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true
HTMLDocument
的变化
+ `readyState`属性有两个值,`loading`和`complete`
1 | if (document.readyState == 'complete') { |
document.charset
字符集属性
data-
自定义数据属性
1 | <div id="myDiv" data-appId="12345" data-myname="Nicholas"></div> |
innerHTML
属性
在读模式下,innerHTML
属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应
的HTML 标记。在写模式下,innerHTML
会根据指定的值创建新的DOM树,然后用这个DOM树完全
替换调用元素原先的所有子节点
outerHTML
属性
在读模式下,outerHTML
返回调用它的元素及所有子节点的HTML 标签。在写模式下,outerHTML
会根据指定的HTML 字符串创建新的DOM 子树,然后用这个DOM子树完全替换调用元素。
insertAdjacentHTML()
方法
插入元素的新增方法,接受两个参数,插入的位置和要插入的HTML文本,第一个参数的值:
'beforebegin'
'afterbegin'
'beforeend'
'afterend'
1 | //作为前一个同辈元素插入 |
scrollIntoView()
方法
scrollIntoView
方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果这个方法传入true
作为参数,或者不传参数,那么窗口滚动之后就会让调用元素的顶部与视口顶部 尽可能平齐,如果传入false
,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐。1
2// 让元素可见
document.form[0].scrollIntoView();
专有扩展
插入文本
innerText
属性outerText
属性
滚动
scrollIntoViewIfNeeded(alignCenter)
:只有在当前元素不可见的情况下,才滚动浏览器或窗口或容器元素最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。scrollByLines(lineCount)
:将元素的内容滚动指定的行高,lineCount
值可以是正值,也可以是负值。scrollByPages(pageCount)
:将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。
scrollIntoView()
和scrollIntoViewIfNeeded()
的作用对象是元素的容器,而scrollByLines()
和scrollByPages()
影响的则是元素自身。
1 | //在当前元素不可见的时候,让它进入浏览器的视口 |
DOM2 和 DOM3
样式
元素大小
偏移量
offsetHeight
:元素在垂直方向上占用的空间大小。包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度offsetWidth
:元素在水平方向上占用的空间大小。包括元素的宽度,(可见的)垂直滚动条的宽度,左边框宽度和右边框宽度offsetLeft
:元素的左外边框至包含元素的左内边框之间的像素距离。offsetTop
:元素的上外边框至包含元素的上内边框之间的像素距离。
其中,offsetLeft
和offsetTop
属性与包含元素有关,包含元素的引用保存在offsetParent
属性中。offsetParent
属性不一定与parentNode
的值相等。1
2
3
4
5
6
7
8
9
10
11
12// 元素上偏移
function getElementLeft (ele) {
var actualLeft = ele.offsetLeft;
var current = ele.offsetParent;
while (current !== null) {
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
// 元素左偏移同理
一般来说,页面中所有的元素都被包含在几个<div>
元素中,而这些<div>
元素的offsetParent
又是<body>
元素,所以getElementLeft()
与getElementTop()
会返回与offsetLeft
和offsetTop
相同的值。
客户区大小
clientWidth
:元素内容区宽度加上左右内边距宽度clientHeight
: 元素内容区高度加上上下内边距高度
1 | function getViewport(){ |
滚动大小
滚动大小,指的是包含滚动内容的元素的大小。有些元素(<html>
),即使没有执行任何代码也能自动添加滚动条;但另外一些元素,则需要通过CSS的overflow
属性设置才能滚动。
scrollHeight
:在没有滚动条的情况下,元素内容的总高度scrollWidth
:在没有滚动条的情况下,元素内容的总宽度scrollLeft
:被隐藏在内容区域左侧的像素数,通过设置这个属性可以改变元素的滚动位置scrollTop
:被隐藏在内容区域上方的像素数,通过设置这个属性可以改变元素的滚动位置
scrollWidth
和scrollHeight
主要用于确定元素内容的实际大小。
scrollWidth
和scrollHeight
与clientWidth
和clientHeight
之间的关系?
- Firefox中这两组属性始终相等,但大小代表的是文档内容区域的实际尺寸,非视口尺寸
- Opera Safari Chrome中这两组属性有区别,其中
scrollWidth
和scrollHeight
等于视口大小,而clientWidth
和clientHeight
等于文档内容区域的大小。 - IE(在标准模式)中的这两组属性不相等,其中
scrollWidth
和scrollHeight
等于文档内容区域的大小,而clientWidth
和clientHeight
等于视口大小。
通过scrollLeft
和scrollTop
属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置。在元素尚未被滚动时,这两个属性的值都等于0。如果元素被垂直滚动了,那么scrollTop
的值会大于0,且表示元素上方不可见内容的像素高度。如果元素被水平滚动了,那么scrollLeft
的值会大于0,且表示元素左侧不可见内容的像素宽度。这两个属性都是可以设置的,因此将元素的scrollLeft
和scrollTop
设置为0,就可以重置元素的滚动位置。下面这个函数会检测元素是否位于顶部,如果不是就将其回滚到顶部。1
2
3
4
5function scrollToTop(element){
if (element.scrollTop != 0){
element.scrollTop = 0;
}
}
确定元素大小
getBoundingClientRect()
方法,会返回一个矩形对象,包含left top right bottom
四个属性。这些属性给出了元素在页面中相对于视口的位置。
遍历
NodeIterator
可以使用document.createNodeIterator()
方法创建它的新实例,接受4个参数。
root
:想要作为搜索起点的树中的节点whatToShow
:表示要访问哪些节点的数字代码filter
:是一个NodeFilter
对象,或者一个表示应该接受还是拒绝某种特定节点的函数entityReferenceExpansion
:布尔值,表示是否要扩展实体引用。
whatToShow
这个参数的值以常量形式在NodeFilter
类型中定义:
NodeFilter.SHOW_ALL
NodeFilter.SHOW_ELEMENT
NodeFilter.SHOW_ATTRIBUTE
NodeFilter.SHOW_TEXT
NodeFilter.SHOW_CDATA_SECTION
NodeFilter.SHOW_ENTITY_REFERENCE
NodeFilter.SHOW_ENTITYE
NodeFilter.SHOW_PROCESSING_INSTRUCTION
NodeFilter.SHOW_COMMENT
NodeFilter.SHOW_DOCUMENT
NodeFilter.SHOW_DOCUMENT_TYPE
NodeFilter.SHOW_DOCUMENT_FRAGMENT
NodeFilter.SHOW_NOTATION
事件
事件流
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。
事件捕获
Netscape 团队提出的事件流叫做事件捕获,事件捕获的用意在于在事件到达预定目标之前捕获它。
DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段
、处于目标阶段
和事件冒泡阶段
。
事件处理程序
DOM0 级事件处理程序
每个元素(包括window
和document
)都有自己的事件处理程序,这些属性通常全部小写。1
2
3
4var btn = document.getElementById('myBtn');
btn.onclick = function () {
console.log('clicked');
}
DOM 0级方法指定的事件处理程序被认为是元素的方法,因此,这个时候的事件处理程序是在元素的作用域中运行,也就是说程序中的this
可以引用当前元素。1
2
3
4var btn = document.getElementById('myBtn');
btn.onclick = function () {
console.log(this.id); // 'myBtn'
}
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
DOM2 级事件处理程序
addEventListener()
removeEventListener()
定义了两个方法用于处理指定和删除事件处理程序的操作。所有的DOM节点中都包含这两个方法,接受三个参数:事件名、事件处理程序和布尔值。最后这个布尔值如果是true
,表示在捕获阶段调用事件处理程序;false
表示在冒泡阶段调用事件处理程序,默认是false
。
通过addEventListener()
添加的事件处理程序只能使用removeEventListener()
来移除。如果通过addEventListener()
添加的匿名函数将无法移除。1
2
3btn.addEventListener('click', function () { //匿名函数
...
})
注:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false
),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
IE事件处理程序
attachEvent()
detachEvent()
这两个方法接受两个参数:事件名(带on
)和事件处理函数。1
2
3
4var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
注:在IE 中使用attachEvent()
与使用DOM0 级方法的主要区别在于事件处理程序的作用域。
- DOM0 级作用域是其所属元素
attachEvent()
方法的作用域是全局(this === window
)
1 | var btn = document.getElementById("myBtn"); |
注:与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是”Hello world!”,然后才是”Clicked”。
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event
。
DOM中的事件对象
event
对象成员
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
bubbles |
Boolean |
只读 | 表明事件是否冒泡 |
cancelable |
Boolean |
只读 | 表明是否可以取消事件的默认行为 |
currentTarget |
Element |
只读 | 其事件处理程序当前正在处理事件的那个元素 |
defaultPrevented |
Boolean |
只读 | 为true 表示已经调用preventDefault() |
detail |
Integer |
只读 | 与事件相关的细节信息 |
eventPhase |
Integer |
只读 | 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡 |
preventDefault() |
Function |
只读 | 取消事件的默认行为。如果cancelable 是true ,则可以使用这个方法 |
stopImmediatePropagation() |
Function |
只读 | 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用 |
stopPropagation() |
Function |
只读 | 取消事件的进一步捕获或冒泡。如果bubbles 为true ,则可以使用这个方法 |
target |
Element |
只读 | 事件的目标 |
trusted |
Boolean |
只读 | 为true 表示事件是浏览器生成,false 是开发人员创建 |
type |
String |
只读 | 被触发的事件类型 |
view |
AbstractView |
只读 | 与事件关联的抽象视图。等同于发生事件的window 对象 |
在事件处理程序内部,对象this
始终等于currentTarget
的值,而target
则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this
、currentTarget
和target
包含相同的值。1
2
3
4
5document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
调用event
方法1
2
3
4var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
跨浏览器的事件对象
1 | var EventUtil = { |
事件类型
UI 事件
load
事件unload
事件resize
事件scroll
事件
焦点事件
blur
事件:失去焦点focus
事件:获得焦点
鼠标与滚动事件
click
事件dbclick
事件mousedown
事件:按下鼠标mouseenter
事件:光标移入mouseleave
事件:光标移出mousemove
事件:鼠标在元素内部移动重复触发mouseout
事件:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素mouseover
事件:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发mouseup
事件:释放鼠标按钮时触发
页面上的所有元素都支持鼠标事件。除了mouseenter
和mouseleave
,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。
只有在同一个元素上相继触发mousedown
和mouseup
事件,才会触发click
事件;如果mousedown
或mouseup
中的一个被取消,就不会触发click
事件。
触摸设备
iOS和Android设备的相关事件:
- 不支持
dbclick
事件。双击浏览器窗口会放大画面 - 轻击可单击元素会触发
mousemove
事件。。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生mousedown
、mouseup
和click
事件。轻击不可单击的元素不会触发任何事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了onclick
事件处理程序的元素。 mousemove
事件也会触发mouseover
和mouseout
事件- 两个手指放在屏幕上且页面随手指移动而滚动时会触发
mousewheel
和scroll
事件。
HTML5事件
contextmenu
事件beforeunload
事件DOMContentLoaded
事件readystatechange
事件uninitialized
未初始化loading
loaded
interactive
:可以操作对象,但还没有完全加载complete
hashchange
事件
设备事件
orientationchange
事件:横竖屏,有三个值: -90 ,0, 90
触摸与手势事件
- 触摸事件
touchstart
touchmove
touchend
touchcancel
- 手势事件
gesturestart
gesturechange
gestureend
内存和性能
事件委托
例如在<ul>
为添加一个click
事件,所有<li>
子元素点击事件都会冒泡到<ul>
上。
表单脚本
表单基础知识
提交表单
1 | <input type="submit" value="Submit Form"> |
重置表单
1 | <input type="reset" value="Reset Form"> |
表单字段
每个表单都有elements
属性,该属性是表单中所有表单元素的集合。1
2
3
4
5
6
7var form = document.getElementById("form1");
//取得表单中的第一个字段
var field1 = form.elements[0];
//取得名为"textbox1"的字段
var field2 = form.elements["textbox1"];
//取得表单中包含的字段的数量
var fieldCount = form.elements.length;
文本框脚本
过滤输入
屏蔽特定的字符,需要检测keypress
事件对应的字符编码。1
2
3
4
5
6
7
8
9EventUtil.addHandler(textbox, 'keypress', function (event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var charCode = EventUtil.getCharCode(event);
if (!/\d/.test(String.fromCharCode(charCode))) {
EventUtil.preventDefault(event);
}
})
HTML5约束验证API
输入模式
HTML5为文本字段新增了pattern
属性。1
<input type="text" pattern="\d+" name="count">
检测有效性
使用checkValidity()
方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>
的约束条件。1
2
3
4
5if (document.forms[0].elements[0].checkValidity()){
//字段有效,继续
} else {
//字段无效
}
也可以检测整个表单是否有效1
2
3
4
5if(document.forms[0].checkValidity()){
//表单有效,继续
} else {
//表单无效
}
禁用验证
1 | <form method="post" action="signup.php" novalidate> |
HTML5 脚本编程
跨文档消息传递
跨文档消息传送(cross-document messaging)简称XDM。其核心方法是postMessage()
方法。
postMessage()
方法接受两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。1
2
3// 注意:所有支持XDM的浏览器也支持iframe的`contentWindow`属性
var iframeWindow = document.getElementById('myframe').contentWindow;
iframeWindow.postMessage('A secret', 'https://yeasoenzhang.github.io');
尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于https://yeasonzhang.github.io
域。
接收到XDM消息时,会触发window
对象的message
事件,这个事件是以异步形式触发。
传递的onmessage
处理程序的事件对象包含三个重要信息:
data
:作为postMessage()
第一个参数传入的字符串数据origin
:发送消息的文档所在的域。source
:发送消息的文档的window
对象的代理。
1 | EventUtil.addHandler(window, "message", function(event){ |
XDM 还有一些怪异之处。首先,postMessage()
的第一个参数最早是作为“永远都是字符串”来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用postMessage()
时,最好还是只传字符串。如果你想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify()
,通过postMessage()
传入得到的字符串,然后再在onmessage
事件处理程序中调用JSON.parse()
。
原生拖放
拖放事件
拖动某个元素时,将依次触发的事件:
dragstart
drag
dragend
当某个元素被拖动到一个有效的放置目标时,会依次触发下列事件:
dragenter
dragover
dragleave
(离开)或drag
(放进去了)
dataTransfer对象
dataTransfer
对象,它是事件对象的一个属性,用于被拖动元素向放置目标传递字符串格式的数据。该对象有两个主要方法:
getData()
setData()
1
2
3
4
5
6
7//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//设置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");
不过,保存在dataTransfer
对象中的数据只能在drap
事件处理程序中读取。如果在ondrop
处理程序中没有读到数据,那就是dataTransfer
对象已经被销毁,数据也丢失了。
drapEffect 与 effectAllowed
dateTransfer
对象有两个属性:
dropEffect
effectAllowed
dropEffect
,属性可以知道被拖动的元素能够执行那种放置行为。
none
:不能放在这里move
:应该把拖放的元素移动到放置目标copy
:应该把拖动的元素复制到放置目标link
:表示放置目标会打开拖动的元素
要使用dropEffect
属性,必须在ondragenter
事件处理程序中针对放置目标来设置。
effectAllowed
属性表示允许拖动元素的哪种dropEffect
。
uninitialized
:没有给被拖动的元素放置任何放置行为none
:被拖动的元素不能有任何行为copy
:只允许值为copy
的dropEffect
link
:只允许值为link
的dropEffect
move
:只允许值为move
的dropEffect
copyLink
:允许值为copy
和link
的dropEffect
copyMove
:允许值为copy
和move
的dropEffect
linkMove
:允许值为link
和move
的dropEffect
all
: 允许任意dropEffect
必须在ondragstart
事件处理程序中设置effectAllowed
属性。
可拖动
HTML5为所有元素规定了draggable
属性,表示元素是否可以拖动。只有图像和链接的draggable
默认是true
1
2
3
4<!-- 让这个图像不可以拖动 -->
<img src="smile.gif" draggable="false" alt="Smiley face">
<!-- 让这个元素可以拖动 -->
<div draggable="true">...</div>
其他成员
HTML5规定了dateTransfer
对象还应该包含下列方法和属性。
addElement(element)
clearData(format)
setDragImage(element, x, y)
type
错误处理与调试
错误处理
try-catch 语句
1 | try { |
finally
子句
只要代码中包含finially
子句,无论try
还是catch
语句中的return
语句都将被忽略。
错误类型
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
1 | try { |
抛出错误
与try-catch
语句相配的还有一个throw
操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw
操作符指定一个值,这个值是什么类型,没有要求。1
2
3
4throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};
遇到throw
操作符时,代码会立即停止执行。只有当try-catch
语句捕获到被抛出值,代码才会继续执行
自定义错误类型
可以利用原型链通过继承Error
创建自定义错误类型。需要为新创建的错误类型指定name
和message
属性1
2
3
4
5
6
7
8function CustomError (message) {
this.name = 'CustomError';
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError('Error msg');
Error事件
任何没有通过try-catch
处理的错误都会触发window
对象的error
事件。
在任何Web浏览器中,onerror
事件处理程序都不会创建event
对象,但它可以接受三个参数:错误消息、错误所在的URL和行号。
要指定onerror 事件处理程序,必须使用如下所示的DOM0 级技术,它没有遵循“DOM2 级事件”的标准格式(addEventListener
)。1
2
3window.onerror = function(message, url, line){
alert(message);
};
只要发生错误,无论是不是浏览器生成的,都会触发error
事件,然后让浏览器的默认机制发挥作用,这时候我们需要阻止浏览器的默认行为(return false
)。1
2
3
4window.onerror = function (message, url, line) {
console.log(message);
retrun false;
}
常见的错误类型
- 类型转换错误
- 数据类型错误
- 通信错误
在数据检测的时候,基本类型的值应该使用typeof
来检测,对象的值应该使用instanceof
。
JSON
解析与序列化
JSON对象
JSON
对象有两个方法:stringify
和parse()
。1
2
3
4
5
6
7
8
9var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book);
以上就把Javascript
对象序列化为一个JSON
字符串(没有空格和缩进)1
{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011}
如果传给JSON.parse()
的字符串不是有效的JSON
,会抛出错误。
序列化选项
JSON.stringify()
除了要序列化的JS
对象外,还可以接受两个参数,一个是过滤器(数组或函数),第二个参数是一个选项,表示是都在JSON
字符串中保留缩进。
过滤结果
1 | var book = { |
第二个参数中包含两个字符串"title", "edition"
,所以只会返回对应的属性1
{"title":"Professional JavaScript","edition":3}
过滤器为函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, function(key, value){
switch(key){
case "authors":
return value.join(",")
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
注:返回undefined
删除该属性,上例的edition
属性就会被删除。
字符串缩进
JSON.stringify()
方法的第三个参数用于控制结果中的缩进和空白符。可以是数字,表示缩进的空格数;也可以是字符串,将该字符串作为缩进的表示。
toJSON()
方法
解析选项
JSON.parse()
方法也可以接受第二参数,该参数是一个函数(被称为还原函数),传入函数的参数均为key, value
。
如果还原函数返回undefined
,则表示要从结果中删除响应的键。
Ajax与Comet
XMLHttpRequest 对象
XHR的用法
open('method', 'url', bool)
:第三个参数表示是否异步发送send()
:接受一个参数作为请求主体发送的数据,如果不需要则传入null
XHR
对象的属性
responseText
:作为相应主体被返回的文本responseXML
:如果相应的内容类型是"text/xml"
或"application/xml"
,这个属性中将包含这响应数据的XML DOM文档status
:响应的HTTP状态statusText
:HTTP状态的说明
同步请求1
2
3
4
5
6
7xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
readyState
:表示请求/响应过程的阶段- 0:未初始化,尚未调用
open()
方法 - 1:启动,调用了
open()
方法,尚未调用send()
方法 - 2:发送,调用了
send()
方法,尚未接收到响应。 - 3:接收,接收到部分响应数据
- 4:完成,已经接收到全部响应数据
- 0:未初始化,尚未调用
1 | var xhr = createXHR(); |
abort()
:在接收到响应之前通过该方法取消异步请求。
建议调用这个方法之后,对XHR
对象进行解引用操作。
HTTP 头部信息
默认情况下,在发送XHR
请求的同时,还会发送下列头部信息:
Accept
:浏览器能够处理的内容类型Accept-Charset
:浏览器能够显示的字符集Accept-Encoding
:浏览器能够处理的压缩编码Accept-Language
:浏览器当前设置的语言Connection
:浏览器与服务器之间连接的类型Cookie
: 当前页面的 CookieHost
:发出请求的页面所在的域Referer
:发出请求的页面的URIUser-Agent
:浏览器的用户代理
自定义请求头部信息,使用setRequestHeader()
方法,该方法接受两个参数:头部字段的名称和头部字段的值。
要成功发送请求头部信息,必须在调用open()
方法之后且调用send()
方法之前调用serRequestHeader()
。1
2
3
4
5
6
7var xhr = createXHR();
xhr.onreadystatechange = function(){
// ...
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
注建议使用自定义的头部字段名称,不要使用浏览器正常发送的字段名称,否则有可能会影响服务器的响应。有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。
调用XHR
对象的getResponseHeader()
方法,接受一个参数:头部字段名称。就能取得相应的响应头部信息。
调用getAllResponseHeaders()
方法可以取得包含所有头部信息的字符串。
GET请求
使用GET
请求经常会发生一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()
进行编码,然后才能放到URL的末尾。1
2
3
4
5
6
7
8
9
10
11
12function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);
POST请求
如果我们希望用XHR模仿表单提交,需要将Content-Type
头部信息设置为application/x-www-form-urlencoded
(表单提交的内容类型)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function submitData(){
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form));
}
XMLHttpRequest 2 级
FormData
FormData
为序列化表单以及创建于表单格式相同的数据提供了便利。1
2var data = new FormData();
data.append('name', 'Yeaseon');
append
方法可以将表单的字段和值,传入FormData
对象中。也可以预先填入表单中的字段:1
var data = new FormData(document.form[0]);
FormData
的方便就在于不必手动修改XHR对象的请求头部。
超时设定
XHR
对象添加了一个timeout
属性,表示请求在等待多少毫秒之后终止。如果规定时间内浏览器没有收到响应,就会触发timeout
事件,进而调用ontimeout
事件处理程序。1
2
3
4
5
6
7
8
9
10var xhr = createXHR();
xhr.onreadystatechange = function(){
// ...
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //将超时设置为1 秒钟
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
超时之后请求终止,但是此时的readyState
可能已经变为了4
,就意味着会调用onreadystatechange
事件。
可是,如果在超时终止请求之后再访问status
属性,就会导致错误。为避免浏览器报告错误,可以将检查status
属性的语句封装在一个try-catch
语句当中。
overrideMimeType()方法
用于重写XHR
响应的MIME
类型。因为返回响应的MIME
类型决定了XHR
对象如何处理它,所以提供一种方法能够重写服务器返回的MIME
类型是很有用的。1
2
3
4var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
进度事件
有以下6个进度事件:
loadstart
:在接收到响应数据的第一个字节触发progress
:在接收响应期间持续不断地触发error
:在请求发生错误时触发abort
:在因为调用abort()
方法而终止连接时触发load
:在接收到完整的响应数据时触发loadend
:在通信完成或者触发error
、abort
,或load
事件后触发
progress事件onprogress
事件处理程序会接收到一个event
对象,target
属性指向XHR
对象,包含着三个额外的属性:
lengthComputable
:表示进度信息是否可用的布尔值position
:表示已经接受的字节数totalSize
:表示根据Content-Length
响应头部确定的预期字节数。
跨资源共享
IE对CORS的实现
微软在IE8中引入了XDR
类型,类似与XHR
对象,两者的不同之处:
- cookie不会随请求发送,也不会随响应返回
- 只能设置请求头部信息中的
Content-Type
字段 - 不能访问响应头部信息
- 只支持
GET
和POST
请求
请求返回之后,就会触发load
事件,响应数据也会保存在responseText
属性中:1
2
3
4
5
6
7
8
9var xdr = new XDomainRequest();
xdr.onload = function () {
console.log(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.open('get', 'http://..../xxx/');
xdr.send(null);
在请求返回之前可以调用abort()
方法终止请求。1
xdr.abort();
XDR
对象也支持timeout
属性以及ontimeout
事件处理程序1
2
3
4
5
6
7
8
9
10
11
12
13var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
为了支持POST
请求,XDR
对象提供了contentType
属性,用来表示发送数据的格式。1
2
3
4
5
6
7
8
9
10var xdr = new XDomainRequest();
xdr.onload = function () {
//
}
xdr.onerror = function () {
//
}
xdr.open('post', 'http://www.somewhere-else.com/page/');
xdr.contentType = 'application/x-www-form-urlencoded';
xdr.send('name1=value1&name2=value2');
其他浏览器对CORS的实现
与IE中的XDR
对象不同,通过跨域XHR
对象可以访问status
和statusText
属性,并且支持同步请求。同时也有一些限制:
- 不能使用
setRequestHeader()
设置自定义头部 - 不能发送和接收
cookie
- 调用
getAllResponseHeaders()
方法总会返回空字符串
1 | var xhr = createXHR(); |
其他跨域技术
图像Ping
1 | var img = new Image(); |
JSONP
JSONP
是JSON with padding的简写。JSONP
只不过时被包含在函数调用中的JSON
:1
callback({"name": "Yeaseon"});
JSONP
由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是请求中指定的。下面是一个经典的JSONP
请求1
http://freegeoip.net/json/?callback=handleResponse
这里指定的回调函数的名字叫做handleResponse
。
JSONP
是通过动态<script>
元素来使用的,使用时可以为src
属性指定一个跨域URL
。
1 | function handleResponse(response){ |
服务器发送事件
SSE支持短轮询、长轮训和HTTP流,而且能在断开连接时自动确定何时重新连接。
SSE API
要预订新的事件流,首先要创建一个新的EventSource
对象,并传入一个入口点:1
var source = new EventSource('myevents.php');
传入的URL必须与创建对象的页面同源。EventSource
的实例有一个readyState
属性:0
表示正连接到服务器,1
表示打开了连接,2
表示关闭了连接。EventSource
实例还有三个事件:
open
:在建立连接时触发message
:在从服务器接收到新事件时触发error
:在无法建立连接时触发
服务器发回的数据以字符串形式保存在event.data
中。
默认情况下,EventSource
对象会保持与服务器的活动连接。如果想强制立即断开连接并且不在重新连接,可以调用close()
方法。
Web Sockets
由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同。未加密的连接不再是 http:// ,而是 ws:// ;加密的连接也不是 https:// ,而是 wss:// 。
Web Sockets API
创建一个WebSockets实例对象:1
var socket = new WebSocket("ws://www.example.com/server.php");
WebSocket也有一个表示当前状态的readyState
属性:
WebSocket.OPENING (0)
:正在建立连接WebSocket.OPEN (1)
:已经建立连接WebSocket.CLOSING (2)
:正在关闭连接WebSocket.CLOSE (3)
:已经关闭连接
发送和接收数据
向服务器发送数据,使用send()
方法并传入任意字符串:1
2var socket = new WebSocket('ws:// www.example.com/server.php');
socket.send('Hello World');
Web Sockets只能发送纯文本数据,对于复杂的数据结构,在发送之前,必须进行序列化。1
2
3
4
5
6
7var message = {
time: new Date(),
text: 'Hello world',
clientId: 'adfalsf39'
};
socket.send(JSON.stringify(message));
当服务器向客户端发来消息时,WebSocket对象就会触发message
事件。这个message
事件与其他传递消息的协议类似,也是把返回的数据保存在event.data
属性中。1
2
3
4socket.onmessage = function (event) {
var data = event.data;
// ....
}
与send()
类似,event.data
中返回的数据也是字符串,需要手工解析这些数据。
其他事件
WebSocket对象还有其他三个事件,在连接生命周期的不同阶段触发:
open
:在成功建立连接时触发error
:在发生错误时触发,连接不能持续close
:在连接关闭时触发
WebSocked对象不支持DOM 2级事件监听:1
2
3
4
5
6
7
8
9
10var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
alert("Connection established.");
};
socket.onerror = function(){
alert("Connection error.");
};
socket.onclose = function(){
alert("Connection closed.");
};
高级技巧
高级函数
安全的类型检测
用于区分原生和非原生JavaScript对象,通过Object.prototype.toString()
。1
2
3
4
5
6
7
8
9
10
11function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
function isFunction(value){
return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value){
return Object.prototype.toString.call(value) == "[object RegExp]";
}
作用域安全的构造函数
防止构造函数内this
指针的指向被改变(指向window
)1
2
3
4
5
6
7
8
9function Person (name, age, job) {
if (this instanceof Person) {
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
惰性载入函数
1 | function createXHR(){ |
第一种改法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function createXHR () {
if (typeof XMLHttpRequest != 'undefined') {
createXHR = function () {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObjext != 'undefined') {
createXHR = function () {
if (typeof arguments.callee.activeXString != 'string') {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i,len;
for (i = 0; len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (e) {
// skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function () {
throw new Error('No XHR object available.');
}
}
return createXHR();
}
第二种改法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28var createXHR = (function () {
if (typeof XMLHttpRequest != 'undefined') {
return function () {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObjext != 'undefined') {
return function () {
if (typeof arguments.callee.activeXString != 'string') {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i,len;
for (i = 0; len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (e) {
// skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
return function () {
throw new Error('No XHR object available.');
}
}
})();
函数绑定
bind()
函数,语法如下:1
2
3
4
5function bind (fn, context) {
return function () {
return fn.apply(context, arguments);
}
}
离线应用与客户端存储
离线检测
navigator.onLine
属性可以判断设备否能访问网络。
HTML5定义两个事件:online
和offline
,当网络状态变化时,分别触发这两个事件:1
2
3
4
5
6EventUtil.addHandler(window, 'online', function () {
console.log('online');
});
EventUtil.addHandler(window, 'offline', function () {
console.log('offline');
});
数据存储
Web存储机制
Web Storage规范包含两种对象的定义:sessionStorage
和globalStorage
。这两个对象在支持的浏览器中都是以windows
对象属性的形式存在。
Storage
类型
Storage
类型提供最大的存储空间来存储名值对。
clear()
:删除所有值getItem(name)
:根据指定的名字name
获取对应的值key(index)
:获得index
位置处的值的名字removeItem(name)
:删除由name
指定的名值对setItem(name, value)
:为指定的name
设置一个对应的值
sessionStorage
对象
sessionStorage
对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage
中的数据可以跨越页面刷新而存在。1
2
3
4
5
6
7
8
9
10
11
12
13
14//使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
//使用属性存储数据
sessionStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;
//使用delete 删除一个值——在WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");
可以通过结合length
属性和key()
方法来迭代sessionStorage
中的值:1
2
3
4
5for (var i = 0, len = sessionStorage.length; i < len; i++) {
var key = sessionStorage.key(i);
var value = sessionStorage.getItem(key);
console.log(key + ' = ' + value);
}
还可以使用for-in
循环来迭代sessionStorage
中的值:1
2
3
4for (var key in sessionStorage) {
var value = sessionStorage.getItem(key);
console.log(key + ' = ' + value);
}
globalStorage
对象
这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage
,首先要指定哪些域可以访问该数据。1
2
3
4
5// 保存数据
globalStorage['wrox.com'].name = 'Yeaseon';
// 获取数据
var name = globalStorage['wrox.com'].name;
上例,访问的是针对域名wrox.com
的存储空间。globalStorage
对象不是Storage
的实例,
而具体的globalStorage['wrox.com']
才是。这个存储空间对于wrox.com
及其所有子域都是可以访问的。1
2
3
4globalStorage["www.wrox.com"].name = "Yeaseon";
globalStorage["www.wrox.com"].book = "Professional JavaScript";
globalStorage["www.wrox.com"].removeItem("name");
var book = globalStorage["www.wrox.com"].getItem("book");
localStorage
对象
localStorage
对象是HTML5规范中作为持久保存客户端数据的方案,并且取代globalStorage
。要访问同一个localStorage
对象,页面必须来自同一个域名(子域名无效),必须同源。1
2
3
4
5
6
7
8
9//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;
storage
事件
对Storage
对象进行任何修改,都会在文档上触发storage
事件。这个事件的event
对象有以下属性。
domain
:发生变化的存储空间的域名key
:设置或删除的键名newValue
:如果是设置值,则是新值;如果是删除键,则是null
oldValue
:键被更改之前的值
在这四个属性中,IE8 和Firefox 只实现了domain
属性。在撰写本书的时候,WebKit 尚不支持storage
事件
IndexedDB
Indexed Database API,简称IndexedDB,是在浏览器中保存结构化数据的一种数据库。IndexedDB设计的操作完全是异步进行。1
var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
数据库
IndexedDB就是一个数据库,它最大的特色就是使用对象保存数据,而不是使用表来保存数据。
indexDB.open()
,传入一个数据库参数。如果该数据库存在就会发送一个打开它的请求;如果该数据库不存在,就会发送一个创建并打开它的请求。请求会返回一个IDBRequest
对象,这个对象上可以添加onerror
和onsuccess
事件处理程序。1
2
3
4
5
6
7
8
9var request, database;
request = indexedDB.open('admin');
request.onerror = function (event) {
console.log(event.target.errorCode);
};
request.onsuccess = function (event) {
database = event.target.result;
};
event.target
都指向request
对象,因此他们可以互换使用。
发生错误了,event.target.errorCode
中将会保存一个错误码:
IDBDatebaseException.UNKNOWN_ERR(1)
:意外错误IDBDatebaseException.NON_TRANSIENT_ERR(2)
:操作不合法IDBDatebaseException.NOT_FOUND_ERR(3)
:未发现要操作的数据库IDBDatebaseException.CONSTRAINT_ERR(4)
:违反了数据库约束IDBDatebaseException.DATA_ERR(5)
:提供给事务的数据不能满足要求IDBDatebaseException.NOT_ALLOWED_ERR(6)
:操作不合法IDBDatebaseException.TRANSACTION_INACTIVE_ERR(7)
:试图重用已完成的事务IDBDatebaseException.ABORT_ERR(8)
:请求中断IDBDatebaseException.READ_ONLY_ERR(9)
:试图在只读模式下写入或修改数据IDBDatebaseException.TIMEOUT_ERR(10)
:在有效时间内未完成操作IDBDatebaseException.QUOTA_ERR(11)
:磁盘空间不足
指定数据库版本号,通过setVersion()
方法:1
2
3
4
5
6
7
8
9
10
11if (database.version != '1.0') {
request = database.setVersion('1.0');
request.onerror = function (event) {
console.log(event.target.errorCode);
};
request.onsuccess = function (event) {
console.log(''Database name: ' + database.name + ', Version: ' + database.version);
}
} else {
console.log(''Database name: ' + database.name + ', Version: ' + database.version);
}
对象存储空间
假设要保存的用户记录由用户名、密码等组成,那么保存一条记录的对象应该类似:1
2
3
4
5
6var user = {
username: '007',
firstname: 'James',
lastname: 'Bond',
password: 'foo'
}
如果使用username
属性作为这个对象存储空间的键,这个username
必须全局唯一,而且大部分时候都要通过这个键来访问数据。1
var store = db.createObjectStore('users', { keyPath: 'username' });
其中第二个参数中的keyPath
属性,就是空间中将要保存的对象的一个属性,而这个属性将作为存储空间的键来使用。
通过add()
或put()
方法来向存储空间添加数据。着两个方法都接收一个参数,就是要保存的对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//users 中保存着一批用户对象
var i=0,
request,
requests = [],
len = users.length;
while(i < len){
request = store.add(users[i++]);
request.onerror = function(){
//处理错误
};
request.onsuccess = function(){
//处理成功
};
requests.push(request);
}
事务
在数据库对象上调用transaction()
可以创建事务,任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。1
2// 创建事务
var transaction = db.transaction();
可以传入要访问的一或多个对象存储空间。1
2
3var transaction = db.transaction('users');
var transaction = db.transaction(['users', 'anotherStore']);
前面这些事务都是以只读方式访问数据。要修改访问方式,必须在创建事务时传入第二个参数,这个参数表示访问模式:
IDBTransaction.READ_ONLY(0)
:只读IDBTransaction.READ_WRITE(1)
:读写IDBTransaction.VERSION_CHANGE(2)
:改变
在Chrome中叫做webkitIDBTransaction
,可以使用一下代码兼容:1
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
这样就能方便的指定transaction()
第二个参数:1
var transaction = db.transaction('users', IDBTransaction.READ_WRITE);
取得事务的索引后,使用objectStore()
方法并传入存储空间的名称,就可以访问指定的存储空间。然后通过如下方法操作对象:
add()
put()
get()
delete()
clear()
1 | var request = db.transaction('users').objectStore('users').get('007'); |
也可以针对事务对象本身进行事件处理,存在两个事件onerror
,oncomplete
:1
2
3
4
5
6transaction.onerror = function (event) {
// 整个事务都被取消了
}
transaction.oncomplete = function (event) {
// 整个事务都成功完成了
}
注:在oncomplete
事件的事件对象中访问不到get()
请求返回的数据,必须在onsuccess
事件中处理。
键范围
1 | var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; |
有四种定义键范围的方法:
only()
:取得指定对象的键lowerBound()
:指定结果集的下界upperBound()
:指定结果集的上界bound()
:同时指定上、下界
1 | var onlyRange = IDBKeyRange.only("007"); |
新型的API
Page Visibility API
Page Visibility API 是为了让开发人员知道页面是否对用户可见推出的。
document.hidden
:表示页面是否隐藏的布尔值。document.visibilityState
- 页面在后台标签页中或浏览器最小化
- 页面在前台标签页中
- 实际的页面已经隐藏,但用户可以看到页面的预览
- 页面在屏幕外执行预渲染处理
visibilitychange
事件:当文档可见性发生改变时,触发该事件。
Geolocation API
Geolocation API 在浏览器中的实现是navigator.geolocation
对象。
getCurrentPosition()
调用这个方法就会触发请求用户共享地理定位信息的对话框。这个方法接收三个参数:成功回调,可选的失败回调和可选的选项对象。
成功回调
会接收到一个Position
对象参数,该对象有两个属性:coords
和timestamp
。
coords
对象中包含于位置相关的信息:
latitude
:十进制度数表示的纬度longitude
:十进制度数表示的经度accuracy
:经纬度坐标的精度,以米为单位
1 | navigator.geolocation.getCurrentPosition(function (position) { |
失败回调
在被调用的时候也会接受到一个参数,这个参数是一个对象,包含连个属性:message
和code
。code
保存着一个数值,表示错误的类型:用户拒绝共享(1)、位置无效(2)或者超时(3)。1
2
3
4
5
6navigator.geolocation.getCurrentPosition(function (position) {
drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
}, function (error) {
console.log('Error code:' + error.code);
console.log('Error message:' + error.message);
});
第三个参数是一个可选对象
,用于设定信息的类型。可以设置的选项有三个:
enableHightAccuracy
:布尔值,表示必须尽可能使用最准确定的位置信息timeout
:以毫秒数表示的等待位置信息的最长时间maximumAge
:表示上一次取得的坐标信息的有效时间,以毫秒表示,如果时间到则重新取得新坐标信息
1 | navigator.geolocation.getCurrentPosition(function (position) { |
File API
File API 在表单中的文件输入字段的基础上,又添加了一些直接访问文件信息的接口。HTML5在DOM中为文件输入元素添加了一个files
集合。每个File对象都有下列只读属性。
name
:本地文件系统的文件名size
:文件的字节大小type
:字符串,文件的MIME类型lastModifiedDate
:字符串,文件上一次修改的时间
FileReader 类型
FileReader 类型实现的是一种异步文件读取机制。可以把FileReader
想象成XMLHttpRequest
。
readAsText(file, encoding)
:以纯文本形式读取文件,将读取到的文本保存在result
属性中,第二个参数用于指定编码类型(可选)readAsDataURL(file)
:读取文件并将文件以数据URI形式保存在result
属性中readAsBinaryString(file)
:读取文件并将一个字符串保存在result
属性中,字符串中的每个字符表示一字节readAsArrayBuffer(file)
:读取文件并将一个包含文件内容的ArrayBuffer
保存在result
属性中
由于读取过程是异步的,所以FileReader
提供了三个事件:
progress
error
load
progress
事件,每50ms就会触发一次,通过事件对象可以获得与XHR
的progress
事件相同的信息:
lengthComputable
loaded
total
由于种种原因无法读取文件,都会触发error
事件,相关信息都会保存到FileReader
的error
属性中。error.code
即错误码:
1
:为找到文件2
:安全性错误3
:读取中断4
:文件不可读5
:编码错误
文件加载成功后会触发load
事件。
1 | var filesList = document.getElementById('files-list'); |
读取拖放的文件
从桌面上把文件拖放到浏览器中会触发drop
事件。而且可以在event.dataTransfer. files
中读取到被放置的文件,当然此时它是一个File
对象,与通过文件输入字段取得的File
对象一样。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var droptarget = document.getElementById('droptarget');
function handleEvent(event) {
var info = '',
output = document.getElementById('output');
files, i, len;
EventUtil.preventDefault(event);
if (event.type == 'drop') {
files = event.dataTransfer.files; //转换成File对象
i = 0;
len = files.length;
while (i < len) {
info += files[i].name + ' (' + files[i].type + ', ' + files[i].size + ' bytes)<br>';
i++;
}
output.innerHTML = info;
}
}
// 阻止默认事件,只有 drop 事件会被处理
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
使用XHR上传文件
创建一个FormDate
对象,通过它调用append()
方法并传入相应的File
对象作为参数,再把FormData
对象传递给XHR
的send()
方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34var droptarget = document.getElementById('droptarget');
function handleEvent(event) {
var info = '',
output = document.getElementById('output'),
data, xhr,
files, i, len;
EventUtil.preventDefault(event);
if (event.type == 'drop') {
data = new FormData();
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len) {
data.append('file' + i, files[i]);
i++;
}
xhr = new XMLHttpRequest();
xhr.open('post', 'FileAPIExapleUpload.php', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
console.log(xhr.responseText);
}
};
xhr.send(data);
}
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);