JavaScript高级程序设计_注释笔记

基本概念

解释型和编译型语言的区别

  • 编译型
    需要编译器。把高级语言编译为机器码文件,编译期代码检查,执行速度更快,一次编译处处执行,可移植性更好。如:Java,C#等。
  • 解释性
    需要解释器。解释器直接读入源代码,边解释(编译)边执行。错误只能等到解释器执行的有关代码时才能被发现,再次运行,需要重新解释,速度较慢。如:JS,Python等。

完整的JavaScript的三个部分

  1. 核心(ECMAScript)
    与Web浏览器没有依赖关系,是基础。定义了:
    语法,类型,语句,关键字,保留字,操作符,对象

  2. 文档对象模型(DOM)
    针对XML但经过扩展用于HTML的API。
    DOM1级,包括DOM核心(DOM Core)和DOM HTML。

  3. 浏览器对象模型(BOM)
    处理浏览器和框架

严格模式

"use strict"

编译指示,为了不破坏ECMAScript 3语法,一些不确定的行为将得到处理,对于某些不安全的操作也会抛出错误。

局部域定义全局变量

1
2
3
function test(){
msg = "hi"; // 全局变量
}

并不推荐,很难维护。相应变量不会马上就有定义导致混乱。

数据类型

undefined,null,boolean,number,string

typeof 操作符

未定义 - undefined
布尔值 - boolean
字符串 - string
数值 - number
对象或null - object
函数 - function

从技术上来讲,函数在ECMAScript中是对象,不是一种数据类型。然后函数有一些特殊的属性,typeof得到的值也不同于对象

undefined类型

1
2
3
4
5
var v1;      //声明之后默认取得undefined值
alert(v1); //"undefined"
alert(v2); //引用错误
alert(typeof v1) //"undefined"
alert(typeof v2) //"undefined"

null类型

typeof null  //返回 object

适用于,准备将变量用于保存对象,那么最好将变量初始化为null,而不是其他值。使用 if(obj != null)来判断是否已经保存对象的引用。

undefined值派生自null值,所以null == undefined 返回ture。

boolean类型

1
2
3
4
5
6
if (true) {     //通过,区分大小写
alert(1)
}
if (True) { //不通过,不是Boolean值,只是标识符
alert(1)
}

Boolean()函数,传入任何参数总会返回Boolean值。规则如下:

类型 返回true 返回false
Boolean true false
String 任何非空字符串 “”(空字符串)
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined N/A(不适用) undefined

不传参数,返回false。

if()语句,自动执行Boolean()函数。

Number类型

  1. 整数值:
    整数值可以以十进制,八进制,十六进制来表示。

    1
    2
    3
    4
    var num=070;        //前导0,十进制的56
    var num=079; //无效,前导0被忽略,解析为79
    var num=0xA; //前导0x,十进制的10
    var num=0x1z //无效,报错
  2. 浮点数值:

    1
    2
    3
    4
    5
    var num=1.;           //解析为整数,1
    var num=10.0; //解析为整数,10
    简写:
    var num=3.12e7; //3.12乘以10的7次方
    var num=3.12e-7; //3.12乘以0.1的7次方

    PS:0.1+0.2 == 0.3
    返回false,因为ECMAScript的浮点运算基于IEEE754,存在二进制和十进制的小数点转换问题,并非独此一家。

  3. 数值范围

    1
    2
    3
    4
    5
    alert(Number.MAX_VALUE + Number.MAX_VALUE)              //返回Infinity
    alert(-1 * (Number.MAX_VALUE + Number.MAX_VALUE)) //返回-Infinity
    Number.POSITIVE_INFINITY //保存着Infinity
    Number.NEGATIVE_INFINITY //保存着-Infinity
    isFinite() //判断是否是有穷的,Infinity返回false,MAX_VALUE返回ture,MAX_VALUE+1返回ture
  4. NaN
    任何涉及NaN的计算,返回都是NaN。
    NaN与任何值都不相等,包括NaN本身。NaN == NaN ,返回false。

    1
    2
    3
    4
    5
    isNaN(NaN)              //true
    isNaN(10) //false
    isNaN("10"/"") //false ,可被转换成数值10/0
    isNaN("blue") //true,不能转换
    isNaN(ture/false) //false,可别转换成数值1
  5. 数值转换
    Number(),用于任何数据类型
    规则:
    Boolean,ture和false分别转换为1和0
    数字,直接返回
    null,返回0
    undefined,返回NaN
    字符串:整数,浮点数,对应的十进制。有效的十六进制,等值的十进制整数。空字符串,0;包含其他字符,NaN
    对象,先调用valueOf()方法,然后按照前面的规则转换返回的值,如果结果为NaN。再调用toString()方法。然后转换返回的字符串

    parseInt(),parseFloat(),转换字符串。

String类型

1
2
3
var num=10;
num.toString() //'10'
num.toString(2) //'1010' 2/8/10/16,转换成相应的进制

Object类型

常用属性/方法:

  • Constructor:保存着用于创建当前对象的构造函数。
  • hasOwnProperty(name):检查给定的属性在当前对象实例中是否存在。
  • toString()
  • valueOf(),返回对象的字符串,数值或布尔值表示

BOM和DOM中的对象,属于宿主对象。可能不会集成Object。

操作符

乘法

有一个数是NaN,结果是NaN。
Infinity与0相乘,结果是NaN。
Infinity与非0相乘,结果是Infinity或-Infinity,取决于操作数的符号。
如果是Infinity与Infinity相乘,结果是Infinity。
如果一个不是数值,则调用Number()转换为数值后再相乘。

除法

一个数是NaN,结果NaN。
Infinity被Infinity除,结果是NaN。
0被0除,结果NaN。
非零有限数被0除,结果是Infinity或-Infinity,取决于符号。(-1/0)
Infinity被非零数除,结果是Infinity或-Infinity,取决于符号。
一个操作数不是数值,同乘法。

加法

1
2
3
Infinity+Infinity=Infinity
-Infinity-Infinity=-Infinity
Infinity-Infinity=NaN

如果一个操作数是字符串,调用他们的toString()得到字符串(undefined和null调用String()返回字面量),然后拼接。两个都是字符串,则直接拼接。
没有字符串,则先调用Number()转换为数值,再进行加法。

减法

1
2
3
4
Infinity-Infinity=NaN
-Infinity+Infinity=NaN
Infinity+Infinity=Infinity
-Infinity-Infinity=-Infinity

如果一个操作数是字符串,布尔值,null或undefined,则先调用Number()函数转换为数值,再根据规则进行减法。如果转换为NaN,则结果是NaN。

+/- 操作符

对非数值应用一元操作符时,会先执行Number()函数转换,然后在转为正/负数。

1
2
3
4
5
6
+"01"           //1
-"01" //-1
+"z" //NaN
-"z" //NaN
+false //0
-false //0

比较操作符

两个字符串,比较两个字符串对应的字符编码值。
一个数值,一个非数值,先进行数值转换。
一个对象,则调用valueOf()方法,没有则调用toString()方法。再比较。

1
2
3
4
5
"a"     >/<     3       //false,先进行转换"a"->NaN
"a" > "3" //true
"23" < "3" //true
"23" > 3 //true
任何数与NaN比较,结果都是false

相等和不相等

默认先进行强制转型,再比较。
字符串/布尔,先转换,再比较。
一个对象,一个不是,则调用对象的valueOf方法。
两个都是对象,则比较是不是同一个对象。
null和undefined是相等的,且比较时都不会执行转换。

特殊情况:

1
2
3
4
5
6
7
8
9
10
11
null == undefined   //true
undefined == 0 //false
null == 0 //false
"NaN" == NaN //false
NaN == 5 //false
NaN == NaN //false
NaN != NaN //true
false == 0 //true
true == 1 //true
true == 2 //false
"5" == 5 //true

语句

for-in语句

可以用来枚举对象的属性

1
2
3
4
for (var prop in window) {
var element = window[prop];
console.log(prop + ' ' + element)
}

switch语句

可以使用任何数据类型,case的值可以是常量,变量,或者表达式

1
2
3
4
5
6
7
8
9
10
11
12
var num = 12;
switch (true) {
case num > 10:
alert(">")
break;
case num < 10:
alert("<")
break;
default:
alert("=")
break;
}

函数

定义时不必制定是否返回值。

严格模式下,不能把函数/参数命名为eval或arguments,不能有连个同名参数。

参数

ECMAScript函数不介意传递进多少个参数,不也在乎参数类型。定义和实际传递的参数没有个数与对应关系的限制。
因为,参数在内部是用一个类似数组的对象(Arguments对象,并不是真的数组)来表示的。而不关心数组中包含哪些参数。

1
2
3
4
5
name(1)                  //length 为1
name(1,2) //length 为2
function name() {
alert(arguments[0])
}

命名参数只提供便利,但不是必须的。解析器不会验证命名参数。

1
2
3
4
function add(num1,num2){
arguments[1]=10;
alert(arguments[0]+num2);
}

  • 修改了arguments[1]的值,也就修改了num2,他们的值都会改变。不过它们的内存空间是独立的,但它们的值会同步。
  • arguments.callee返回的是当前执行的函数。
  • js的函数没有重载,重复定义的函数不论参数列表,后定义的函数会覆盖前面的函数。但可以使用arguments对象来模拟实现。

变量、作用域和内存

基本类型和引用类型

传递参数

ECMAScript所有函数的参数都是按值传递。对于引用类型(对象)来说,不会改变原始引用,只会根据引用改变对象的成员。

1
2
3
4
5
6
7
8
9
10
var p = { name: 'bily' };
set(p);
alert(p.name)
function set(p) {
p = {};
p.name = 'alan';
}
function set(p) {
p.name = 'alan';
}

①,弹出bily。②,弹出alan。

检测类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typeof
var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();

alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //Boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object

instanceof: 检测对象的具体类型
alert({} instanceof Object); //所有对象,都返回ture
alert([] instanceof Array);
alert(/^.$/ instanceof RegExp);

执行环境与作用域

执行环境与作用域

每个函数都有自己的执行环境和作用域,但js没有块级作用域。例如if和for等语句。
但是也有例外,例如catch和with语句。

查询标识符

在某个环境中读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。从作用域链的前段开始,向上逐级查询。
局部找到,则搜索停止。否则一直向上追溯到全局环境的变量对象,如果都没有找到,则意味着该变量未声明。

垃圾回收

标记清除

引用类型

Object类型

Array类型

创建

1
2
3
4
5
var colors = ["red", "blue", "green"];
colors[colors.length] = "black"; //结尾添加
colors[99] = "black";
alert(colors.length); //100
alert(colors[50]); //undefined

检测

arr instanceof Array                        //true/false

假定单一的全局执行环境,如果网页包含多个框架,则可能出现问题。
为了解决这个问题,ECMAScrit新增了能检测iframes的方法

Array.isArray(arr)                          //true/false   IE9+

转换方法

1
2
3
4
5
var colors = ["red", "blue", "green"];    
alert(colors.toString()); //red,blue,green
alert(colors.valueOf()); //red,blue,green
alert(colors); //red,blue,green。自动执行toString()方法
alert(colors.join("||")); //red||green||blue。改变分隔符。

栈方法:LIFO

1
2
3
4
5
6
var colors = [];                     
var count = colors.push("red", "green");
count = colors.push("black");
var item = colors.pop(); //弹出末端最后的元素
alert(item); //"black"
alert(colors.length); //2

队列方法:FIFO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var colors = [];                     
var count = colors.push("red", "green");
count = colors.push("black");
alert(count); //3
var item = colors.shift(); //移除头部第一个元素
alert(item); //"red"
alert(colors.length); //2

//反向的队列:
var colors = [];
var count = colors.unshift("red", "green"); // 0,1
count = colors.unshift("black"); //从头部插入,成为第一个元素,索引为0
var item = colors.pop();
alert(item); //"green"

排序方法

1
2
3
4
var values = [0, 1, 5, 10, 15];
values.sort(); //正向 0,1,10,15,5
//sort方法默认会调用每项的toString()转型方法,然后比较得到的字符串。
values.reverse(); //反向

自定义排序

1
2
3
4
5
6
7
function compare(value1, value2) {
return value2-value1;
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);
values.reverse(compare);

操作方法:增,减,替换

  • concat

    返回新构建的数组副本

    1
    2
    3
    4
    var colors = ["red", "green", "blue"];
    var colors2 = colors.concat("yellow", ["black", "brown"]);
    alert(colors); //red,green,blue
    alert(colors2); //red,green,blue,yellow,black,brown
  • slice

    返回当前数组截取的副本

    1
    2
    3
    4
    5
    var colors = ["red", "green", "blue", "yellow", "purple"];
    var colors2 = colors.slice(1);
    var colors3 = colors.slice(1,4);
    alert(colors2); //green,blue,yellow,purple
    alert(colors3); //green,blue,yellow
  • splice

    删除,插入,替换数组元素。返回删除项组成的数组,直接影响原数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var colors = ["red", "green", "blue"];
    var removed = colors.splice(0,1);
    alert(colors); //green,blue
    alert(removed); //red (数组)

    removed = colors.splice(1, 0, "yellow", "orange");
    alert(colors); //green,yellow,orange,blue
    alert(removed); //空数组

    removed = colors.splice(1, 1, "red", "purple");
    alert(colors); //green,red,purple,orange,blue
    alert(removed); //yellow(数组)

数组的迭代方法

  • every(),对每一项运行给定函数,如果该函数对每一项都返回ture,则返回ture
  • some(),对每一项运行给定函数,如果该函数对任一项返回ture,则返回ture
  • filter(),对每一项运行给定函数,返回该函数会返回ture的项组成的数组
  • map(),对每一项运行给定函数,返回每次函数调用的结果组成的数组
  • forEach(),对每一项运行给定函数。无返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var everyResult = numbers.every(function(item, index, array) {
return (item > 2);
});
alert(everyResult); //false

var someResult = numbers.some(function(item, index, array) {
return (item > 2);
});
alert(someResult); //true

var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]

var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]

缩小方法

接受4个参数,前一个值,当前值,当前索引,数组对象。函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项。

1
2
3
4
5
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum);

reduceRight从反方向执行。

Date类型

创建

1
2
3
4
5
6
7
var now = new Date()                      //新创的对象自动获得当前日期时间。
var someDate = new Date(Date.parse("May 25, 2004"));
var someDate = new Date("May 25,2004"); //默认调用Date.parse()

var y2k = new Date(Date.UTC(2000, 0));
var y2k = new Date(2000, 0);
var allFives = new Date(2005, 4, 5, 17, 55, 55);

格式化

格式化

RegExp类型

正则表达式模式

正则表达式

1
2
var pattern=/[bc]at/i;
var pattern=new RegExp("[bc]at","i");

实例属性

实例属性

exec()

1
2
3
4
5
6
7
8
9
10
var 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"
alert(matches[2]); //" and baby"

[0],与整个模式匹配的字符串
[1][2],与模式中的捕获组(?)匹配的字符串。没有捕获组则不存在。
模式g,不设置则多次调用exec()将始终返回第一个匹配项的信息。设置则每次调用exec()都会在字符串中继续查找剩余的新匹配项。

构造函数属性

1
2
3
4
5
6
7
8
9
10
11
12
var text = "this has been a short summer";
var pattern = /(.)hort/g;

if (pattern.test(text)){ //可换成exec(),不反回ture/false。返回一个数组
alert(RegExp.input); //this has been a short summer
alert(RegExp.leftContext); //this has been a
alert(RegExp.rightContext); // summer
alert(RegExp.lastMatch); //short
alert(RegExp.lastParen); //s
alert(RegExp.multiline); //false 。IE,Chrome等不支持
alert(RegExp.$1); //$1~$9,自动存储匹配的捕获组,exec()和test()方法时自动填充。
}

Function类型

定义

“函数是对象,函数名是指针”
每个函数都是Function类型的实例,具有属性和方法。函数名不与某个函数绑定,只是一个指向函数对象的指针。

function sum(){…} 等于 var sum=function(){…}
所以JS没有重载,声明同一个函数两次,函数名指向最后赋值的函数对象。

函数声明与函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13

alert(sum(10,10)); //20

function sum(num1, num2){
return num1 + num2;
} //不会报错。解析器会进行函数声明提升的过程,率先读取函数声明,并使其在执行任何代码之前可访问。


alert(sum(10,10));

var sum = function(num1, num2){
return num1 + num2;
}; //报错,解析器进行到表达式时,sum才被保存对函数的引用。

作为值的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];

if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}

var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];

data.sort(createComparisonFunction("name"));
alert(data[0].name); //Nicholas

data.sort(createComparisonFunction("age"));
alert(data[0].name); //Zachary

函数内部属性

arguments.callee,指向拥有arguments对象的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function factorial(num){
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1) ;
//如果return num*factorial(num-1);,则代码和函数名紧耦合
}
}
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120。代码执行不因函数名的指向而改变。

this,引用的是函数所在的执行环境。全局作用域中调用函数时,this引用的是window

1
2
3
4
5
6
7
8
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
o.sayColor = sayColor;
o.sayColor(); //blue

caller,保存着调用当前函数的函数的引用,全局作用域中调用当前函数,它为null

1
2
3
4
5
6
7
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller); //或inner.caller
}
outer();

函数属性和方法

length,函数定义的命名参数的个数。
function sum(num1,mum2){…} //length为2

apply()和call(),每个函数都包含两个非继承而来的方法,用途都是在特性的作用域中调用函数,可以设置函数体内this对象的值。apply(obj,[ … ])接收数组参数,call(obj,arg1,arg2…)接收连续参数

bind()创建一个函数的实例,this值绑定到传递给bind()函数的参数值。

1
2
3
4
5
6
7
8
9
10
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments);
//this为window对象。arguments对象
//或者 sum.apply(this, [num1, num2])
//或者 sum.call(this,num1,num2)
}
alert(callSum1(10,10)); //20

扩充函数赖以运行的作用域

1
2
3
4
5
6
7
8
9
10
11
12
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

基本包装类型

1
2
var s1 = "text";            //string 是基本类型
var s2 = s1.substring(2); //基本类型并不具有此方法

每当读取一个基本类型值得时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。相当于下面的过程

1
2
3
4
5
6
7
8
s1 = new String('text');
var s2 = s1.substring(2);
s1 = null; //即刻销毁

var o = new Object('text');
alert(o instanceof Object); //true
alert(o instanceof String); //ture
alert(typeof o); //object

Boolean类型

1
2
3
4
5
6
7
8
9
var falseObject = new Boolean(false);
var falseValue = false;

var result = falseObject && true;
alert(result); //true
alert(typeof falseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false

Number类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var numberObject = new Number(10);
var numberValue = 99;

alert(numberObject.toString(2)); //"1010"
alert(numberObject.toString(16)); //"a"
alert(numberObject.toFixed(2)); //outputs "10.00"
alert(numberObject.toExponential(2)); // 1.00e+1

numberObject = new Number(99);
alert(numberObject.toPrecision(1)); //"1e+2"
alert(numberObject.toPrecision(2)); //"99"
alert(numberObject.toPrecision(3)); //"99.0"

alert(typeof numberObject); //object
alert(typeof numberValue); //number
alert(numberObject instanceof Number); //true
alert(numberValue instanceof Number); //false

String类型

1
2
3
4
5
6
7
var stringObject = new String("hello world");
var stringValue = "hello world";

alert(typeof stringObject); //"object"
alert(typeof stringValue); //"string"
alert(stringObject instanceof String); //true
alert(stringValue instanceof String); //false
字符方法

x.charAt(),x.charCodeAt() //字符编码,x[n]。

字符串操作方法
concat()

接受任意个参数,拼接返回新字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world" 总长度作为结束位置
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.substr(3, 7)); //"lo worl" 第二个参数指定长度

alert(stringValue.slice(-3)); //"rld" 将负的参数与总长度相加
alert(stringValue.slice(3, -4)); //"lo w"
alert(stringValue.substring(-3)); //"hello world" 将所有负参数转为0
alert(stringValue.substring(3, -4)); //"hel" (3,0)=>(0,3 )
alert(stringValue.substr(-3)); //"rld" 将第一个负的参数与总长度相加
alert(stringValue.substr(3, -4)); //"" 将第二个负的参数转为0

字符串位置方法
1
2
3
4
5
var stringValue = "hello world";
alert(stringValue.indexOf("o")); //4
alert(stringValue.lastIndexOf("o")); //7
alert(stringValue.indexOf("o", 6)); //7。从第二个参数往后搜索
alert(stringValue.lastIndexOf("o", 6)); //4。从第二个参数往前搜索
trim()

返回去除字符串两端空格的副本。

大小写转换
1
2
alert(stringValue.toUpperCase());        //"HELLO WORLD"
alert(stringValue.toLowerCase()); //"hello world"
字符串的模式匹配方法
  • match(),本质上与调用RegExp的exec()方法相同。唯一参数,正则表达式。

    1
    2
    3
    4
    5
    6
    7
    var text = "cat, bat, sat, fat"; 
    var pattern = /.at/;

    var matches = text.match(pattern);
    alert(matches.index); //0
    alert(matches[0]); //"cat"
    alert(pattern.lastIndex); //0
  • search(),由开头向后查找,返回字符串中第一个匹配项的索引,如果没找到,返回-1。唯一参数,正则表达式。

    var pos = text.search(/at/); //1

  • replace(),第一个参数,正则表达式或字符串,第二个参数字符串或者一个函数。

    第一个参数为字符串,只替换第一个匹配项

    1
    2
    var result = text.replace("at", "ond");
    alert(result); //"cond, bat, sat, fat"

    全局替换,使用正则表达式

    1
    2
    result = text.replace(/at/g, "ond");
    alert(result); //"cond, bond, sond, fond"

    第二个参数为字符串,还可以使用一些特殊的字符序列

    1
    2
    result = text.replace(/(.at)/g, "word ($1)");
    alert(result); //word (cat), word (bat), word (sat), word (fat)

    $n,匹配的第n个捕获组

    第二个参数,为函数。
    replace

split()

第一个参数,分隔符字符串或RegExp。第二个参数,结果数组的大小。

1
2
3
4
var 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()

比较两个字符串在字母表中的位置。

1
2
3
4
var stringValue = "yellow";       
alert(stringValue.localeCompare("brick")); //1
alert(stringValue.localeCompare("yellow")); //0
alert(stringValue.localeCompare("zoo")); //-1

fromCharCode()

接受一或多个字符编码。转换成一个字符串。

alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"

单体内置类型

Global对象

URI编码

encodeURI(),不会对本属于URI的特殊字符进行编码,如冒号,正斜杠问号和井号。
encodeURIComponet(),对任何非标准字符进行编码。

1
2
3
4
5
6
7
var uri = "http://www.wrox.com/illegal value.htm#start";

//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURI(uri));

//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
alert(encodeURIComponent(uri));

URI解码

decodeURI(),只对非URI特殊字符解码。
decodeURIComponet(),解码所以非标准字符。

1
2
3
4
5
6
7
var uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";

//http%3A%2F%2Fwww.wrox.com%2Fillegal value.htm%23start
alert(decodeURI(uri));

//http://www.wrox.com/illegal value.htm#start
alert(decodeURIComponent(uri));

eval()

像是一个完整的ECMAScript解析器,只接受一个参数。传入的参数当做实际的ECMAScript语句来解析,然后把执行结果插入到原位置。通过eval()执行的代码,被认为是包含调用的执行环境的一部分,因此被执行代码具有该执行环境相同的作用域链。但在eval()中创建的任何变量或函数都不会被提升。

eval("var msg='hello world';");
alert(msg);     //严格模式中会导致错误
Global对象的属性

Global对象的属性

window对象

Web浏览器都是将Global对象作为window对象的一部分加以实现的。因此全局作用域中声明的所有变量和函数都成为了window对象的属性。

Math对象

Math对象的属性:

min()和max()
1
2
3
4
5
6
7
var max = Math.max(3, 54, 32, 16);
alert(max); //54
var min = Math.min(3, 54, 32, 16);
alert(min); //3
//想要直接传递数组,可以使用下面的技巧
var arr=[3, 54, 32, 16];
var max = Math.max.apply(Math,arr);
舍入方法
1
2
3
4
5
alert(Math.ceil(25.1));     //26
alert(Math.floor(25.9)); //25

alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
random()
1
2
3
4
5
6
7
8
9
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}

var num = selectFrom(2, 10); //2-10的整数

var colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
var color = colors[selectFrom(0, colors.length-1)];

对象

理解对象

属性类型

ECMAScript中属性分两种:数据属性和访问器属性。
“特性”描述了属性的各种特征,只为了实现JS引擎,不能直接访问。

数据属性

数据属性

1
2
3
4
5
6
7
8
9
var person = {};
Object.defineProperty(person, "name", {
writable: false, //只读属性
value: "Nicholas"
});

alert(person.name);
person.name = "Michael"; //严格模式下,抛出错误
alert(person.name); //Nicholas

一旦把属性定义为不可配置的,就不能再把它便回可配置了。再调用Object.defineProperty()修改除writable之外的特性,都将导致错误。

访问器属性

不包含数据值,包含一对getter和setter函数。都不必需,可创建只读,只写属性。

访问器属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var book = {
_year: 2004,
edition: 1
};

Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){

if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;

}
}
});

book.year = 2005;
alert(book.edition); //2

定义/修改多个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var book = {};
Object.defineProperties(book, {
_year: { value: 2004 }, //其他特性默认为false,undefined
edition: { value: 1 },
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});

读取属性的特性

1
2
3
4
5
6
7
8
9
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"

var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"

创建对象

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){ //弊端,每个对象的此属性指向不同方法对象
alert(this.name);
};
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
person1.sayName(); //"Nicholas"
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person1.constructor == Person); //true

new 操作符,执行步骤:
1,创建1个新对象。2,将构造函数的作用域赋给新对象,this就指向了这个新对象
3,执行构造函数中的代码。4,返回新对象

如果直接调用构造函数,则和普通函数一样。

Person("Greg", 27, "Doctor");  //adds to window
window.sayName();   //"Greg"。通过this,赋值给了window对象。

原型模式

理解原型对象

理解原型

每一个函数都有一个prototype属性,是一个指针,指向通过构造函数而创建的那个对象实例的原型对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。通过原型,可以把需要定义在构造函数中的实例信息,转移到原型对象中。

1
2
3
4
5
6
7
function Person(){        }        
Person.prototype.name = "Nicholas";
Person.prototype.sayName = function(){ //每个实例的此属性指向同一方法
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"。通过查找对象属性得过程来实现查找调用
  • 确定对象间是否存在原型关系

    alert(Person.prototype.isPrototypeOf(person1));  //true
    
  • 取得[[Prototype]]的值

    alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
    alert(person1.constructor.prototype == Person.prototype);  //true
    
  • 屏蔽原型属性

    person1.name = "Greg";
    alert(person1.name);   //"Greg"
    

    读取实例的属性时,先搜索实例本身,然后搜索原型对象。

  • 删除实例属性,解除屏蔽。

    delete person1.name;
    
  • 检测属性是否存在于实例中

    alert(person1.hasOwnProperty("name")); 
    
原型与in操作符

in操作符会在通过对象能够访问给定属性时返回ture,无论属性存在于实例或原型中

1
2
3
4
5
6
7
var person1 = new Person();        
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true

function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
} //判断属性是否存在于原型中
  • 使用for-in循环时,返回的是所有能通过对象访问的,可枚举([[Enumerable]])的属性。其中既包括实例中的属性和原型中的属性。屏蔽原型中不可枚举属性得实例属性也会在for-in循环中返回。
  • Object.keys()。接收一个对象,返回一个只包含在此实例中的所有可枚举属性的字符串数组。

    1
    2
    3
    4
    5
    var keys = Object.keys(Person.prototype);
    alert(keys); //"name,age,job,sayName"
    var p=new Person();
    var keys = Object.keys(p);
    alert(keys); //""
  • Object.getOwnPropertyNames()。接收一个对象,返回一个只包含在此实例中的无论是否可枚举的属性的字符串数组。

    1
    2
    3
    4
    var keys = Object.getOwnPropertyNames(Person.prototype);
    alert(keys); //"constructor,name,age,job,sayName"
    var keys = Object.getOwnPropertyNames(p);
    alert(keys); //""
更简单的原型语法
1
2
3
4
5
6
7
function Person(){ }        
Person.prototype = {
name : "Nicholas",
sayName : function () {
alert(this.name);
}
};

重写了prototype对象。因此constructor属性变成了Object构造函数,不在指向Person函数

1
2
3
4
5
var friend = new Person();        
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false。实际访问的是原型对象的constructor属性
alert(friend.constructor == Object); //true

重设constructor属性

Person.prototype = {
    constructor: Person,
    ...
};

缺点:导致constructor属性的枚举特性为ture。改良如下:

Object.defineProperty(Person.prototype,"constructor",{ 
    enumerable:false,
    value:Person
});
原型的动态性

因为实例和原型之间的连接是一个指针,而非一个副本。创建实例后,对原型对象做的任何修改都能立即从实例上反应出来。
如果先创建实例,又重写了其原型对象,则切断了新原型对象与实例的关系。实例指向的仍是原来的原型对象。

原生对象的原型
1
2
3
4
5
6
alert(typeof Array.prototype.sort);         //"function"
alert(typeof String.prototype.substring); //"function"

String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
}; //给原生引用类型增加扩展方法(不推荐,容易引起冲突)

组合构造函数模式和原型模式

组合两种模式,创建自定义类型。构造函数用于定义实例不同的属性,原型模式用于定义方法和共享的属性。

继承

原型链

原型链

确定原型和实例之间的关系
1
2
3
4
5
6
instance instanceof Object;      //true
instance instanceof SuperType; //true
instance instanceof SubType; //true
Object.prototype.isPrototypeOf(instance); //true
SuperType.prototype.isPrototypeOf(instance); //true
SubType.prototype.isPrototypeOf(instance); //true
原型链的问题
1
2
3
4
5
6
7
8
9
10
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){ }
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"。所有子类型实例共享一个原型对象
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"。所有子类型实例都受影响

组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function SuperType(name){                       //父类型 私有属性
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){ //父类型 私有方法
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //子类型 借用父类型构造函数,生成自己的私有属性(与父类型一致)
this.age = age; //子类型 私有属性
}

SubType.prototype = new SuperType(); //父类型实例作为子类型的原型,继承父实例原型的方法和实例属性。父实例的私有属性将被子实例相同的私有属性覆盖,而不再被子实例共享,避免篡改。
SubType.prototype.sayAge = function(){ //子类型 私有方法
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29); //拥有:父类型原型方法,子类型原型方法,子实例私有属性

优点:重用了父类型的方法,避免二次定义与父类型相同的属性
缺点:实例了两次相同的属性,子实例覆盖了父实例创建的私有属性

原型式继承

1
2
3
4
5
6
7
8
9
10
11
12
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = { //原型(基础)对象
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person); //传递父类型(非实例)进行浅复制,创建了2个副本,拥有相同的原型对象
var yetAnotherPerson = object(person);
anotherPerson.prototype.sayHello=function(){ ... } //对子类型的扩展,并不影响父类型

ECMAScript 5 规范了原型式继承

1
2
3
4
5
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);

寄生组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); //创建一个中间层,继承了父类型的原型方法。
prototype.constructor = subType; //增强
subType.prototype = prototype; //子类型的原型引用此中间层,对子类型的扩展应用在此层上,并不影响父类型。
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //借用,避免二次定义相同的属性
this.age = age;
}
inheritPrototype(SubType, SuperType); //应用中间层,继承父类型原型方法,且并不实例化父类型,避免实例后覆盖带来的浪费
SubType.prototype.sayAge = function(){ //扩展子类型
alert(this.age);
};

函数表达式

闭包

一个函数,有权访问其他函数作用域变量对象的函数。

作用域链初始化过程

  1. 创建时,创建一个预先包含全局变量对象及外层变量对象的作用域链(根据函数是否引用外层函数的变量来决定是否加入外部函数的变量对象,未引用则外部函数的变量对象销毁),保存在内部的[[ Scope ]]属性中。
  2. 调用时,为此函数创建一个执行环境。
  3. 复制函数的[[ Scope ]]属性中的对象(指针)构建执行环境的作用域链。
  4. 创建次函数的活动对象(在此作为变量对象)并被推入执行环境作用域链的前端。

作用域链本质上是一个指向变量对象的指针列表。

闭包与变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){ //创建,作用域链引用createFunctions的变量对象
return i;
};
} //不存在块级作用域,变量i仍然存在,最终的值为10
return result;
} //被引用createFunctions的变量对象未销毁

var funcs = createFunctions();

for (var i=0; i < funcs.length; i++){
document.write(funcs[i]() + "<br />");
//调用函数,全部都返回createFunctions变量对象中的i变量,10
}

关于this对象

this对象在运行时基于函数的执行环境绑定的。当函数作为某个对象的方法被调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return this.name;
var that=this;
return function() {
return this.name;
};

}
};

alert(object.getNameFunc()());

  1. 作为对象的方法调用,this等于此对象。”My Obeject”
  2. 匿名函数,此时this表示window。”The Window” 。(内部函数搜索this和arguments变量时,只会搜索到其活动对象位置,不可能直接访问到外部函数中的这连个变量。)
  3. 把外部作用域中的this对象保存在一个闭包能够访问 到的变量,就可以让闭包访问此对象了。

特殊情况,

1
2
3
alert(object.getName());     //"My Object"
alert((object.getName)()); //"My Object"
alert((object.getName = object.getName)()); //"The Window" 非严格模式

第三行的赋值表达式等于函数本身,this没有得到保持,等于window对象。

闭包导致的内存泄漏

IE9之前的版本对JS对象和COM对象使用不同的垃圾收集例程。BOM和DOM中的对象就是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数。

1
2
3
4
5
6
function assign() {
var ele=document.getElementById('id');
ele.onclick=function(){
alert(ele.id);
}
}

循环引用,导致内存泄漏
解决方案

1
2
3
4
5
6
7
8
function assign(){
var ele=document.getElementById('Id');
var id=ele.id;
ele.onclick=function(){
alert(id);
}
ele=null;
}

模仿块级作用域

JS没有块级作用域的概念,在for或if中定义的变量,实际上是在包含函数中而非语句中创建的。

1
2
3
4
5
6
7
8
function outputNumbers(count){            
(function () { //内部为块级作用域
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //出错
}

私有变量

1
2
3
4
5
6
7
8
9
10
11
function Person(){
//私有变量
var name;
//特权方法
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}

静态私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function(){            
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();

var person1 = new Person("Nicholas");
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var application = function(){            
var components = new Array();
components.push(new BaseComponent()); //初始化
return { //匿名对象
getComponentCount : function(){
return components.length;
},
registerComponent : function(component){
if (typeof component == "object"){
components.push(component);
}
}
};
}();

增强模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var application = function(){
var components = new Array();
components.push(new BaseComponent());
var app = new BaseComponent(); //属于某种类型
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if (typeof component == "object"){
components.push(component);
}
};
return app;
}();

BOM

window对象

全局作用域

window对象扮演着Global对象的角色。定义在全局作用域中的变量都会成为window对象的属性
区别:

1
2
3
4
5
6
var age = 29;               //[[Configurable]]特性为false
window.color = "red";
delete window.age; //false <IE 9报错
delete window.color; //true
alert(window.age); //29
alert(window.color); //undefined

窗口关系及框架

如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frames集合中(通过从0开始的数值索引或者框架名称来访问对应的window对象)。

top.frames[0]           //top始终指向最高层的框架,也就是浏览器窗口
parent对象                //当前对象直接上层框架,如果没有框架的话。parent=top=window

窗口位置

浏览器在屏幕左边和上边的位置。

1
2
var leftPos = (typeof window.screenLeft == "number") ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ? window.screenTop : window.screenY;

窗口大小

页面大小,不包括工具栏等浏览器本身。

1
2
3
4
5
6
7
8
9
10
11
12
var pageWidth = window.innerWidth,
pageHeight = window.innerHeight;

if (typeof pageWidth != "number"){
if (document.compatMode == "CSS1Compat"){ //是否是标准模式
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}

间歇调用和超时调用

JS是单线程语言,一定时间内只能执行一段代码。为了控制要执行的代码,就有一个JS任务队列,这些任务会按照它们添加到队列的顺序执行。setTimeout()的第二个参数告诉JS再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行。否则它就要等前面的代码执行完了之后再执行。

系统对话框

window.print()      //调用打印窗口
window.find('xx')         //查找页面是否包含'字符串',返回ture/false

location对象

位置操作

1
2
3
4
5
6
7
location.hash = "sec1";                 //  http://..../#sec1   不刷新页面,跳转到页内锚记
location.search = "q=22"; // http://..../?q=22#sec1
location.hostname = "www.yahoo.com"; // http://www.yahoo.com/?q=22#sec1
location.pathname = "mydir"; // http://..../mydir/t1/dd/?q=22#sec1
location.port = 8080; // http://...:8080/mydir/t1/dd/?q=22#sec1
location.replace("http://www.wrox.com/"); //不会产生历史记录,不能回到前一个页面
location.reload([true]); //true,忽略浏览器缓存,强制从服务器重新加载

浏览器及客户端系统相关信息

navigator对象
navigator对象

screen对象

表示客户端的能力,包括浏览器窗口外部的显示器信息,如像素宽度和高度等。

screen对象

history对象

因history也是window对象的属性,因此每个浏览器窗口,每个标签页乃至每个框架,都有自己的history对象与特定的window对象关联。

1
2
3
4
5
history.go(-1)
history.go(1)
history.go("www.baidu.com") //跳转到最近的baidu页面,可能前进,也可能倒退
history.back()
history.forward()

DOM

节点层次

Node类型

每个节点都有一个childNodes属性,保存着NodeList对象。NodeList是一种类数组对象,动态实时更新,使用时最好先缓存起来,可以通过索引[i]或者item(i)访问,也有length属性,但并不是Array的实例。
可以通过

var arr=Array.protptype.slice.call(some.childNodes,0);

来进行转换成数组。

Node之间的关系

Node之间的关系

操作节点
1
2
3
4
5
6
7
var retNode=someNode.appendChild(someNode.firstChild);  //成为第二个节点
var retNode=someNode.insertBefore(newNode,null); //插入为最后一个节点
var retNode=someNode.insertBefore(newNode,someNode.firstChild); //插入成为第一个节点
var retNode=someNode.replaceChild(newNode,someNode,firstChild); //替换第一个节点。该节点的所有关系指针都会被替换。
var retNode=someNode.removeChild(someNode.firstChild); //移除第一个子节点
var deepList=myList.cloneNode(true); //复制节点及整个子节点数
var shallowList=myList.cloneNode(false); //只复制节点本身

Document类型

包括HTML元素,HTML声明等。

1
2
3
4
5
var html=document.documentElement;  //HTML元素
html==document.childNodes[0]; //true
html==document.firstChild; //true
var body=document.body; //body的引用
var doctype=document.doctype; //<!DOCTYPE>的引用

在jQuery的$(function(){ … });页面加载事件里,this对象引用的即为Document对象。且直接通过var 声明的变量不会成为Document对象的属性(与全局作用域不同,如全局声明var变量,此变量即成为window对象的属性)。

文档信息
1
2
3
4
5
6
7
document.title='new title';
var url=document.url;
var domain=document.domain;
var referrer=document.referrer;

images.nemedItem('name'); 或
iamges["name"] //取得HTMLCollection对象中name为'name'的元素
特殊集合
1
2
3
4
document.anchores,带name的a元素
document.links,带href的a元素
document.forms,所有form元素
document.images,所有img元素
一致性检测

document.implementation对象,DOM1级只为此对象规定了一个方法,hasFeature()。传入两个参数,要检测的DOM功能名称和版本号。

var hasXmlDom=document.implementation.hasFeature('XML','1.0');

一致性检测

Element类型

特征

1
2
3
4
nodeType           //1
nodeName/tagName //标签名
nodeValue //null
parentNode

HTML元素

HTMLElement,所有HTML元素都是HTMLELment活它的子类型表示。
继承了Element的属性,新增了部分属性:
id,title,lang,dir(语言方向,类似text-align),className(所有class)。

操作特性
1
2
3
4
var div=document.getElementById('my');
div.getAttribute('id');
div.setAttribute('class','123');
div.removeAttribute('name');

直接通过属性名访问和getAttribute()方法得到的结果不一样的两种特性:
style,get得到的CSS文本,属性访问得到对象,用于访问元素样式。
事件,onclick等,get访问代码文本,属性访问得到function对象。

attributes属性

包含一个NamedNodeMap“动态”集合,拥有下列方法:

1
2
3
4
getNamedItem(name)          //返回指定nodeName(tagName)的节点
removedNamedItem(name) //从列表移除指定nodeName
setNamedItem(node) //向列表添加节点
item(id) //返回位于数字id处的节点

取值

ele.attributes['age'].nodeValue
创建元素
1
2
3
4
var div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);

动态创建的元素特点:不能通过表单reset,与已有同name单选框无关系。

元素的子节点
ele.childNodes

除了IE,其他浏览器会把各子元素之间的回车等空白也计算为一个节点。所以需要遍历子元素时,需要判断元素类型。

1
2
3
if(chiEle.nodeType==1){
//do some
}

Text类型

特征

1
2
3
4
5
6
nodeType    //3
nodeName //#text
nodeValue //节点文本
parentNode //Element
没有子节点
length属性

方法

1
2
3
appendData(txt),deleteData(offset,count),
insertData(offset,count,text),replaceData(offset,count,text),
splitText(offset),substringData(offset,count)

创建
document.createTextNode('<strong>Hello</strong>');

Comment类型

特征

1
2
3
4
5
nodeType    //8
nodeName //'#comment'
nodeValue //注释的内容
parentNode //Document或Element
无子节点

CDATASection类型

特征

1
2
3
4
5
6
只针对XML文档,CDATA区域。与Comment类似,继承自Text类型
nodeType //4
nodeName //#cdata-section
nodeValue //CDATA区域的内容
parentNode //Document或Element
无子节点

DocumentType类型

很少有浏览器支持。表示文档类型,对应<!DOCTYPE>等。

Attr类型

元素的特性

DOM操作技术

动态脚本

页面加载时不存在,将来通过修改DOM动态添加的脚本。

  • 外部引用

    1
    2
    3
    4
    5
    6
    7
    function addScript(){
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.text = "function sayHi(){alert('hi');}";
    document.body.appendChild(script);
    sayHi();
    }
  • 行内方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function loadScriptString(code){
    var script = document.createElement("script");
    script.type = "text/javascript";
    try {
    script.appendChild(document.createTextNode(code));
    } catch (ex){
    script.text = code; //IE特定,不支持对Script添加子节点
    }
    document.body.appendChild(script);
    }

动态样式

  • 外部引用

    1
    2
    3
    4
    5
    6
    7
    function addStyle(){
    var style = document.createElement("style");
    style.type = "text/css";
    style.appendChild(document.createTextNode("body{background-color:red}")); //error in IE
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(style);
    }
  • 嵌入方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function loadStyleString(css){
    var style = document.createElement("style");
    style.type = "text/css";
    try{
    style.appendChild(document.createTextNode(css));
    } catch (ex){
    style.styleSheet.cssText = css; //IE
    }
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(style);
    }

选择符API

使用CSS选择符查询匹配的DOM元素。

querySelector()

接受一个CSS选择符,返回与该模式匹配的第一个元素,没找到则返回null。

1
2
3
4
var body = document.querySelector("body");
var myDiv = document.querySelector("#myDiv");
var selected = document.querySelector(".selected");
var img = document.body.querySelector("img.button");

querySelectorAll()

返回所有匹配的元素,一个NodeList实例(此方法返回的实际上是一组元素的快照,避免不断动态查询带来的性能影响)。没有匹配则返回一个空的NodeList。

1
2
3
4
var ems = document.getElementById("myDiv").querySelectorAll("em");
var selecteds = document.querySelectorAll(".selected");
var strongs = document.querySelectorAll("p strong");
var s1=strongs[0];

matchesSelector()

如果调用元素与该选择符匹配,返回ture,否则返回false。

1
2
3
if (matchesSelector(document.body, "body.page1")){
alert("It's page 1!");
}

支持不完善,需要做跨浏览器兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
function matchesSelector(element, selector){
if (element.matchesSelector){
return element.matchesSelector(selector);
} else if (element.msMatchesSelector){
return element.msMatchesSelector(selector);
} else if (element.mozMatchesSelector){
return element.mozMatchesSelector(selector);
} else if (element.webkitMatchesSelector){
return element.webkitMatchesSelector(selector);
} else {
throw new Error("Not supported.");
}
}

元素遍历

对于元素见得空格,IE9及之前版本不会返回文本节点,其他浏览器都会返回文本节点。
子节点查询的元素版:
childElementCount属性,子元素(不含文本和注释节点啊)个数。
firstElementChild,lastElementChild,previousElementSibling,nextElementSibling
children属性,HTMLCollection实例,只包含元素子节点。childNodes的元素版。

跨浏览器兼容

1
2
3
4
5
6
7
8
9
10
11
12
if (document.body.firstElementChild){
var i,
len,
child = document.body.firstElementChild;

while(child != document.body.lastElementChild){
document.write("<p>" + child.tagName + "</p>");
child = child.nextElementSibling;
}
} else {
document.write("<p>Element Traversal API not supported.</p>");
}

HTML5

document.head,head部分引用
document.charset,字符集

类的扩充

getElementsByClassName()

所有元素都可调用

1
2
3
4
5
classList       //DOMTokenList
add(value) //添加或忽略
contains(value) //是否包含
remove(value) //删除
toggle(value)

滚动

scrollIntoView()

任何元素可调用,出现在视口中。
传入true或不传,顶部与视口对齐。
传入false,底部对齐。

DOM2和DOM3变化

Node类型

localName,不带命名空间前缀的节点名称
namespaceURI,不带命名空间前缀的节点名称
prefix,命名空间或者null

Document类型

新增了带NS后缀的方法,表示特定命名空间下的操作,比如:
createElementNS(ns,tagName):创建属于ns命名空间的元素
createAttributeNS(ns,attr):使用给定attr创建一个ns命名空间的新特性
getElementsByTagNameNS(ns,tagName):返回特定命名空间的元素NodeList

元素大小

偏移量

偏移量

某个元素在页面上的偏移量计算:

1
2
3
4
5
6
7
8
9
10
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}

return actualLeft;
}

客户区大小

客户区

元素内部空间大小,因此滚动条占用的空间不计算。
确定浏览器视口大小:

document.documentElement.clientWidth             //document.body代表body,document.documentElement                         //代表html标签
滚动大小

scrollHeight,没有滚动条的情况下,元素总高度
scrollWidth,同上,元素内容总宽度
scrollLeft,被隐藏在内容区域左侧的像素数。设置可以改变元素滚动位置
scrollTop,上方的元素,设置可以改变元素滚动位置
(需要设置overflow属性才能滚动)

确定文档的总高度时,必须获得scrollHeight/clientHeight中的最大值,才能跨浏览器得到准确的结果。

Math.max(ducment.documentElement.scrollHeight,document,documentElement.clientHeight)

遍历

TreeWalker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function makeList() {
var div = document.getElementById("div1");
var filter = function(node){
return (node.tagName.toLowerCase() == "li") ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);

var output = document.getElementById("text1");
var node = walker.nextNode();
while (node !== null) {
output.value += node.tagName + "\n";
node = walker.nextNode();
}
}

NodeFilter.FILTER_SKIP,跳过节点前进道子树种的下一个节点
NodeFilter.FILTER_REJECT,跳过节点及该节点整个子树。
walker可以眼任何方向移动

walker.firstChild();   
walker.nextSibling();

walker.currentNode,当前节点。

范围

范围

事件

事件流

事件流

事件处理程序需

DOM0级事件处理程序

1
2
3
4
bar btn=document.getElementById('my');
btn.onclick=fuction(){
alert(this.id);
};

DOM0级方法指定的事件处理程序被认为是元素的方法,在元素的作用域中运行,this引用当前元素。

DOM2级事件处理程序

1
2
3
4
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);

false,冒泡阶段调用,true,捕获阶段调用。
作用域同DOM0级方法一样。好处是,可以添加多个事件处理程序,且按添加顺序执行。

通过此方法添加的事件只能使用removeEventListener()移除,传入的参数与添加时使用的参数相同(同一个函数对象),所以通过addEventListener()添加的匿名函数将无法移除。

事件对象

所有事件都会有的成员

事件成员
事件成员

1
2
3
4
5
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};

事件类型

事件类型

UI事件

UI事件

在window上发生的任何事件都可以在body元素上通过相应的特性来指定,因为HTML无法访问window元素,为了向后兼容
例:window的load事件,

unload事件

兼容性很不好,chrome不会显示文本,IE显示乱码

1
2
3
4
5
EventUtil.addHandler(window, "beforeunload", function(event) {
var msg = "will you leave?";
event.returnValue = msg;
return msg;
});

scroll事件

document.body.scrollTop获取滚动高度。
火狐使用document.documentElement.scrollTop。

焦点事件

焦点事件

鼠标与滚轮事件

鼠标事件
鼠标事件

  • 客户区(可视)坐标

    event.clientX和event.clientY

  • 页面坐标

    event.pageX,event.pageY

  • 屏幕坐标

    event.screenX,event.screenY

  • 修改键

    e.shiftKey,e.ctrlKey,e.altKey,e.metaKey。布尔值,代表是否按下了相应按键,支持同时按。

  • 关联元素

    e.relatedTarget,只对mouseover和mouseout事件才包含值,代表与target发生关系的元素。

  • 鼠标按钮

    e.button,按下或释放的按钮。0,主按钮,1,滚轮,2,次按钮。

  • 滚轮事件

    mousewheel,任何元素上出发,最终会冒泡到document或window对象。event对象包含一个特殊的wheelDelta属性,向前滚动wheelDelta是120的倍数,向后滚动wheelDelta是-120的倍数。

  • 触摸设备

    触摸设备

键盘与文本事件

keydown,按下键盘上任意键时触发,按住不放会重复触发。
keypress,按下奸商上字符键时触发,按住不放会重复触发。
keyup,释放键盘上的键时触发keydown->keypress->keyup。
支持4个修改键。
键码,event.keyCode,表示触发keydown或keyup事件特定的键,如回车-13,空格-32。
字符编码,event.key,事件产生的相应文本字符或键名。

文本事件

textInput。按下能够输入实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发。
event.data,实际输入的字符文本。
event.inputMethod,代表文本是如何输入到控件中的,从而验证有效性(只有IE支持)。

HTML5事件

contextmenu事件

windows中,右键单击。Mac,Ctrl+单击。
冒泡,可以为document指定一个事件处理程序自定义右键菜单。

1
2
3
4
5
6
7
8
9
10
document.oncontextmenu=function(event){
event.preventDefault()
var menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
};
document.addEventListener('click',function(){
document.getElementById("myMenu").style.visibility = "hidden"; //隐藏右键菜单
});

beforeUnload事件

必须添加给window对象。firefox和opera不支持。chrome不显示message。

1
2
3
4
5
6
EventUtil.addHandler(window, "beforeunload", function(event){
event = EventUtil.getEvent(event);
var message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message;
});

DOMContentLoaded事件

DOM树加载完毕之后触发,不理会图片,js,css等。

pageShow,pageHide事件

必须添加给window对象。pageShow在load后触发,pageHide在unload之前触发。兼容性不好。

hashchange事件

必须添加给window对象。url发生变化时触发,包含oldURL和newURL两个属性。

设备事件

只在移动设备上实现。

orientationchange事件

横向纵向查看模式。只有Safari支持

deviceorientation事件
1
2
3
4
5
6
EventUtil.addHandler(window, "deviceorientation", function(event){
var output = document.getElementById("output");
var arrow = document.getElementById("arrow");
arrow.style.webkitTransform = "rotate(" + Math.round(event.alpha) + "deg)";
output.innerHTML = "Alpha=" + event.alpha + ", Beta=" + event.beta + ", Gamma=" + event.gamma + "<br>";
});
devicemotion事件
1
2
3
4
5
6
7
8
EventUtil.addHandler(window, "devicemotion", function(event){
var output = document.getElementById("output");
if (event.rotationRate !== null){
output.innerHTML += "Alpha=" + event.rotationRate.alpha + ", Beta=" +
event.rotationRate.beta + ", Gamma=" +
event.rotationRate.gamma;
}
});

触摸和手势事件

触摸事件

都会冒泡。

touch事件

Touch对象包含:
identifier,唯一ID,clientX/Y,pageX/Y,screenX/Y,target,触摸的DOM节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function handleTouchEvent(event){
//只触发一次
if (event.touches.length == 1){
var output = document.getElementById("output");
switch(event.type){
case "touchstart":
output.innerHTML = "Touch started (" + event.touches[0].clientX + "," + event.touches[0].clientY + ")";
break;
case "touchend":
output.innerHTML += "<br>Touch ended (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
case "touchmove":
event.preventDefault(); //阻止滚动
output.innerHTML += "<br>Touch moved (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
}
}
}

document.addEventListener("touchstart", handleTouchEvent, false);
document.addEventListener("touchend", handleTouchEvent, false);
document.addEventListener("touchmove", handleTouchEvent, false);

模拟事件

模拟鼠标事件

1
2
3
4
var event = document.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, document.defaultView, 0, 100, 0, 0, 0, false,
false, false, false, 0, btn2);
btn.dispatchEvent(event);

模拟键盘事件

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
EventUtil.addHandler(btn, "click", function(event){
var event;
//DOM Level 3
if (document.implementation.hasFeature("KeyboardEvent", "3.0")){
event = document.createEvent("KeyboardEvent");
event.initKeyboardEvent("keydown", true, true, "a", 0, "Shift");
} else {
try {
//Firefox
event = document.createEvent("KeyEvents");
event.initKeyEvent("keydown", true, true, document.defaultView, false, false,
true, false, 65, 65);
} catch (ex){ //others
event = document.createEvent("Events");
event.initEvent("keydown", true, true);
event.view = document.defaultView;
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.metaKey = false;
event.keyCode = 65;
event.charCode = 65;
}
}
textbox.dispatchEvent(event);
});

表单脚本

选择框脚本:
HTMLOptionElement对象,属性有:index,label,selected,text,value。

取得选择的option,select.options[select.selectedIndex];
选择,select.option[0].selected = true; 对其他option无影响。
select.selectedIndex = 0,影响多选框其他option。

JSON

JSON序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var bookCopy = JSON.parse(jsonText, function(key, value){
if (key == "releaseDate"){
return undefined;
} else {
return value;
}
});
var jsonText = JSON.stringify(book, ["title", "edition"], 4); //只序列化字段,格式化缩进个数
var jsonText = JSON.stringify(book, null, "--"); //缩进占位符
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011,
toJSON: function(){
return this.title;
}
};

Ajax

XHR默认头部信息

默认头部

发送请求

通过setRequestHeader()设置自定义头部,jQuery方式:

1
2
3
4
5
6
7
8
9
10
$.ajax({
type: "GET",
url: "...",
beforeSend: function(request) {
request.setRequestHeader("Test", "content");
},
success: function(result) {
alert(result);
}
});

XHR2级

FormData

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
var data = new FormData();
data.append("file" , files[i]);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(event){
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", "FileAPIExample06Upload.php", true);
xhr.send(data);

var formData = new FormData();
var upload = document.getElementById('file');
if (upload.files.length > 0) {
formData.append('upload', upload.files[0]);
$.ajax({
url: '/Customers/Import',
method: 'POST',
data: formData,
contentType: false,
processData: false,
cache: false,
success: function (data) {}
});
}

进度事件

1
2
3
4
5
6
7
8
9
var xhr = createXHR();
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);

跨域

XHR对象,默认只能访问与它同一个域中的资源。
CORS:发送请求时,需要添加一个额外的Origin头部,Origin:http://www.xxx.com。服务器如果任何请求,就在Access-Control-Allow-Origin头部中挥发相同的源(如果不需要跨域保护,可会回发 *),例如Access-Control-Allow-Origin:http://www.xxx.com
大部分浏览器默认实现CORS的原声支持,访问时,使用绝对URL即可。但跨域XHR对象也有一些限制:不能自定义头部,不能发送和接受cookie,获取自定义头部总会返回空

其他跨域技术

图像Ping,JSONP,Comet,Web sockets。

WebSockets

在一个单独的持久链接上提供全双工、双向通信。第一次取得服务器响应后,建立的链接会从HTTP协议升级成Web Socket协议(需要服务器支持)。

Web Sockets协议:’ws://‘和’wss://‘(加密)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var socket = new WebSocket('ws://www.xxx.com/handler'); // 不受同源策略影响,可以跨域
// readState 属性:WebSocket.CONNECTING(0) / OPEN(1) / CLOSING(3) / CLOSED(2)

socket.send('xxx'); //只能发送纯文本,发送复杂数据结构,需要先序列化
socket.onmessage = function(event){ // 获取服务器消息
var data=event.data;
}
socket.close() //关闭链接 ,状态由2到3
WebSocket事件:
不支持DOM2级事件侦听器,必须使用DOM0级语法定义事件。
socket.onopen = function(){};
socket.onerror = function(){};
socket.onclose = function(e){
// e.wasClean 是否明确关闭
// e.code 服务器返回码
// e.reason 服务器发回的消息
};

高级技巧

高级函数

安全的类型检测

检测是否为数组/函数/正则表达式:

Object.prototype.toString.call(obj) == '[object Array]'/'[object Function]'/'[object RegExp]';

作用域安全的构造函数

假设一个构造函数没有搭配new 关键词而调用,那么this默认绑定到window对象,则可能导致意外的错误。

1
2
3
4
5
6
7
8
9
function Person(name, age, job){
if (this instanceof Person){ //判断是否为指定类型的对象,再进行操作
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}

函数绑定

绑定后返回一个函数,无论再对此函数调用什么apply或call改变this的引用,都不会改变执行结果
handleClick.bind(obj)。

1
2
3
4
5
6
function bind(context){
var fn=this;
return function(){
return fn.apply(context, arguments);
};
}

柯里化

使用闭包创建一个已经设置了部分参数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments),
finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}

function add(num1, num2){
return num1 + num2;
}

var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8

bind的柯里化实现

1
2
3
4
5
6
7
8
9
var handler = {
message: "Event handled",
handleClick: function(name, event){
alert(this.message + ":" + name + ":" + event.type);
}
};

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn")); //第一个参数是绑定的this,第二个参数开始是预设置的参数。

防篡改对象

不可扩展
1
2
3
4
var person = { name: "Nicholas" };
Object.preventExtensions(person);
alert(Object.isExtensible(person)); //false
person.age = 29;

静默失败undefined,严格模式抛出异常

密封

不可扩展,[[Configurable]]特性被设置为false,不能删除属性和方法。但是属性值是可以修改的。

1
2
3
4
5
6
7
var person = { name: "Nicholas" };
Object.seal(person);
alert(Object.isSealed(person)); //true
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"

冻结

最严格。及不可扩展,又是密封的,对象的[[Writable]]特性被设置为false,如果定义了[[Set]],则访问器属性仍是可写的。

1
2
3
4
5
6
7
8
9
10
11
var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true
alert(Object.isFrozen(person)); //true

分隔执行过程

当执行数据量过大,为避免造成JS线程一直在进行中,导致UI线程被卡住。推荐对数据分割处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];

function chunk(array, process, context){ // context可选的
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100); // 100毫秒 给线程一个协调的时间
}

function printValue(item){
var div = document.getElementById("myDiv");
div.innerHTML += item + "<br>";
}

chunk(data.concat(), printValue); // 不想改变原数组的数据,可以使用concat或slice创建一个副本

函数节流

一些会被反复执行的事件,如果频率太高容易导致浏览器挂起或崩溃。需要对该函数进行节流
适用于resize事件或over事件等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function throttle(method, scope) {       // 可选的scope
return function() {
clearTimeout(method.tId);
method.tId = setTimeout(function() {
method.call(scope);
}, 100);
}
}

function resizeDiv() {
var div = document.getElementById("myDiv");
div.style.height = div.offsetWidth + "px";
}

window.onresize = throttle(resizeDiv);

自定义拖动功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function DrapDrop() {
var drag
var dragging = null;
var diffLeft, diffTop, diffRight, diffBottom;
var count = 0;

function handlerEvent(event) {
var target = event.target;
switch (event.type) {
case 'mousedown':
if ($(target).hasClass('draggable')) {
dragging = target;
diffLeft = event.clientX - target.offsetLeft;
diffTop = event.clientY - target.offsetTop;
diffRight = target.clientWidth - diffLeft;
diffBottom = target.clientHeight - diffTop;
console.log(event.clientX, target.offsetLeft)
}
break;
case 'mousemove':
if (dragging != null) {
var parentWidth = target.offsetParent.clientWidth;
var parentHeight = target.offsetParent.clientHeight;
var left, top;
if (event.clientX - diffLeft <= 0 || event.clientX + diffRight >= parentWidth) {
if (event.clientX - diffLeft <= 0) left = 0;
if (event.clientX + diffRight >= parentWidth) left = parentWidth - target.clientWidth;
} else {
left = event.clientX - diffLeft;
}
if (event.clientY - diffTop <= 0 || event.clientY + diffBottom >= parentHeight) {
if (event.clientY - diffTop <= 0) top = 0;
if (event.clientY + diffBottom >= parentHeight) top = parentHeight - target.clientHeight;
} else {
top = event.clientY - diffTop;
}
dragging.style.left = left + 'px';
dragging.style.top = top + 'px';
}
break;
case 'mouseup':
dragging = null;
break;
}
}
return {
enable: function() {
document.addEventListener('mousedown', handlerEvent)
document.addEventListener('mousemove', handlerEvent)
document.addEventListener('mouseup', handlerEvent)
},
disable: function() {
document.removeEventListener('mousedown', handlerEvent)
document.removeEventListener('mousemove', handlerEvent)
document.removeEventListener('mouseup', handlerEvent)
}
}
}

离线应用和客户端存储

离线应用

离线检测

navigator.onLine

离线事件

1
2
3
4
5
6
EventUtil.addHandler(window, "online", function(){
document.getElementById("status").innerHTML = "Online";
});
EventUtil.addHandler(window, "offline", function(){
document.getElementById("status").innerHTML = "Offline";
});

数据存储

限制:存在客户端计算机上,每个域最多几十个。只能保存单一文本,总共最大尺寸4k。

属性:
key,名称,必须经过URL编码
value:值,必须经过URL编码
域:在某些域下才有效并携带发送到服务器。(.baidu.com对baidu.com的所有子域都有效)
路径:指定路径有效并携带。(http://www.baidu.com/news/,跟路径则不会发送cookie)
失效时间:时间戳,什么时候删除cookie。
安全标志:secure(非名值对儿),只有在SSL链接下才发送。

Set-Cookie: name=value; domain= .wrox.com; path=/; secure

Http专有cookie

可以从浏览器和服务器设置,但是只能从服务器端读取,JS无法获取。

Web Storage

Storage类型

clear():清空所有值
getItem(name):获取name的值
key(index):获取index处值的名称
removeItem(name):删除
setItem(name,value):设置
PS:只能存储字符串

sessionStorage对象

会话存储,存储某个特定会话的数据,保持到浏览器关闭。sessionStorage中的数据只能由最初设置的页面访问,不能跨页面

localStorage对象

限制:同一来源有效,即同一个域(子域名无效),同一种协议,同一个端口。每个来源2~5MB大小限制。
有效期:一直存在,直到JS删除或用户清楚缓存

IndexedDB

替代了Web SQL Database API。保存结构化数据的一种数据库。操作完全是异步进行,因此,大多数操作会以请求的方式进行,异步返回成功结果或失败错误。
限制:不能跨域,必须同源(域,协议,端口),大小5MB左右。

获取:(因为浏览器厂商提供的API都有前缀)

var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(function() {
var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB, // 消除厂商差异
IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction, // 定义事务的访问模式
IDBCursor = window.IDBCursor || window.webkitIDBCursor,
IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange,
request,
store,
database,
users = [{
username: "007",
firstName: "James",
lastName: "Bond",
password: "foo"
}, {
username: "ace",
firstName: "John",
lastName: "Smith",
password: "bar"
}, {
username: "foobar",
firstName: "Michael",
lastName: "Johnson",
password: "secret"
}];

request = indexedDB.open("example");
request.onerror = function(event) {
alert("Something bad happened while trying to open: " + event.target.errorCode);
};
request.onsuccess = function(event) {
database = event.target.result;
initializeDatabase();
};

function initializeDatabase() {
if (database.version != "1.0") { // 默认无版本,最好先定义版本,避免产生不同的版本
request = database.setVersion("1.0");
request.onerror = function(event) {
alert("Something bad happened while trying to set version: " + event.target.errorCode);
};
request.onsuccess = function(event) {
store = database.createObjectStore("users", {keyPath: "username"}); // 定义对象存储空间和主键
users.forEach(function(user) {
store.add(user); // 如果主键重复了,add返回错误,put直接覆盖。
// get取得值,delete删除对象,clear删除所有对象
});
outputValues();
};
} else {
outputValues();
}
}

function outputValues() {
// 事务组织所有的操作
var store = database.transaction("users", IDBTransaction.READ_WRITE).objectStore("users"),
index = store.createIndex("username","username",{unique:false}); // 取得索引 sotre.index("username")
range = IDBKeyRange.bound("007", "ace", false, false), // 指定游标范围。下界,上界,是否跳过下,上
request = store.openCursor(range, IDBCursor.PREV); // 指定游标的方向。NEXT/NEXT_NO_DUPLICATE

request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) { // 必须要检查
console.log("Key: " + cursor.key + ", Value: " + JSON.stringify(cursor.value));
cursor.value.password = 'xxx';
var updateRequest = cursor.update(cursor.value); // 请求更新
// cursor.delete() 删除当前项
cursor.continue(); // 移动到下一项,可以指定key
// cursor.advance(count) 移动指定位数
} else {
console.log("Done!");
}
};
request.onfailure = function(event) {
console.error("Iteration did not succeed.");
};
}
})();

最佳实践

性能优化

避免不必要的属性查找

访问数组元素是一个O(1)的操作,访问对象的属性则是O(n)的操作。避免多次查找统一属性,可以先存储起来。

优化循环

  • 减值迭代,从最大值到0,更高效。
  • 简化终止条件,避免属性查找或O(n)的操作。比如:i<arr.length。
  • 简化循环体。
  • 使用do-while,避免最初的终止条件计算。

Duff装置

仅在处理大数据集时使用,更加高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = []
var iterations = Math.floor(arr.length / 8);
var leftover = arr.length % 8;
var i = 0;
if (leftover > 0) {
do {
process(arr[i++]);
} while (--leftover > 0);
}
do {
process(arr[i++]); //循环再开,比单纯的循环调用更加高效
process(arr[i++]);
process(arr[i++]);
process(arr[i++]);
process(arr[i++]);
process(arr[i++]);
process(arr[i++]);
process(arr[i++]);
} while (--iterations > 0);

最小化语句数

多个声明合一,更高效:

var count=5,
    color='red',
    arr=[1,2,3];

初始化数组和对象时使用字面量

var arr=[1,2,3];
var obj={age:20};

优化DOM交互

  • 最小化更新DOM次数。
  • 使用innerHTML,一次性更新。
  • 使用事件代理,利用事件冒泡,在祖先节点上处理事件。减少事件绑定数量。
  • 减少使用HTMLCollection对象
    getElementByxxx(),childNodes,attributes,document.forms,document.images等都会返回HTMLCollection,每次访问它的属性时都会在文档上进行实时的查询,开销昂贵。

其他

  1. 原生方法更快
  2. switch语句更快
  3. 位运算符更快

新兴的API

这些API都不在H5的规范中,只是与H5相关。

Page Visibility API

探测页面是否最小化或者隐藏在其他标签后面。方便资源的暂停。

var isHide = document.hidden || document.msHidden || document.webkitHidden;

document.visibilityState:4种表示不同状态的值

visibilitychange事件

EventUtil.addHandler(document, "msvisibilitychange", handleVisibilityChange);
EventUtil.addHandler(document, "webkitvisibilitychange", handleVisibilityChange);

Geolocation API

获得用户当前地理位置,需要用户授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
navigator.geolocation.getCurrentPosition(function(position){
// position.coords对象有以下属性:latitude 维度,longitude 经度,accuracy 单位

}, function(error){
// error.message
});

var id = navigator.geolocation.watchPosition(function(position) {
// 方法第一次会取得当前位置,然后在系统发生位置改变时被调用
}, function(error) {
//
});
clearWatch(id); //取消追踪

File API

FileReader

File类型,包含name,size,type属性。event.files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
type = "default",
reader = new FileReader();
if (/image/.test(files[0].type)){
reader.readAsDataURL(files[0]);
type = "image";
} else {
reader.readAsText(files[0]);
type = "text";
}
reader.onerror = function(){
output.innerHTML = "Error code :" + reader.error.code;
//1,未找到。2,安全错误。3,读取中断。4,文件不可读。5,编码错误
};
reader.onprogress = function(event){
if (event.lengthComputable){
progress.innerHTML = event.loaded + "/" + event.total;
}
};
reader.onload = function(){
var html = "";
switch(type){
case "image":
html = "<img src=\"" + reader.result + "\">";
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});

部分读取

返回blob类型(file的父类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
function blobSlice(blob, startByte, length){
if (blob.slice){
return blob.slice(startByte, length);
} else if (blob.webkitSlice){
return blob.webkitSlice(startByte, length);
} else if (blob.mozSlice){
return blob.mozSlice(startByte, length);
} else {
return null;
}
}

var blob = blobSlice(files[0], 0, 32);

对象URL

bloblURL,指向一块内存地址

1
2
3
4
5
6
7
8
9
10
11
function createObjectURL(blob){
if (window.URL){
return window.URL.createObjectURL(blob);
} else if (window.webkitURL){
return window.webkitURL.createObjectURL(blob);
} else {
return null;
}
}
var url = createObjectURL(files[0]);
output.innerHTML = "<img src=\"" + url + "\">";

文件拖放并上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var 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", "xxx", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
alert(xhr.responseText);
}
};
xhr.send(data);
}
}

EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
//取消dragenter、dragover和drop的默认行为

Web workers

使用worker

使用后台线程/进程执行复杂的运行实现异步。如加/解密,图片处理等

1
2
3
4
5
6
7
8
9
var data = [23,4,7,9,2,14,6,651,87,41,7798,24],
worker = new Worker("WebWorkerExample01.js");
// 下载js文件,但并不立即执行
worker.onmessage = function(event){
console.log(event);
};
worker.postMessage(data);
// 可以传递文本,也可以传递对象(副本)
worker.terminate(); // 终止worker

worker中

1
2
3
4
5
6
7
8
9
10
11
12
13
self.onmessage = function(event){
var data = event.data;
data.sort(function(a, b){
return a - b;
});
self.postMessage(data);
};
self.onerror = function(event){
// event.filename
// event.lineno
// event.message
}
self.close(); // 停止工作

全局方法

importScripts("com1.js","com2.js");    // 异步加载。按顺序执行。

worker全局作用域

worker单独一个作用域,不能访问DOM元素和标签。
this引用的是worker对象。
限制版本的navigator对象。
制度的location对象。
setTimeout()、setInterval()、clearTimeout()、clearInterval()方法
XMLHttpRequest函数。

工具

链接 描述
client.js 浏览器及版本检测工具
EventUtil.js 跨浏览器事件处理工具
CookieUtil.js cookie操作工具
EventTarget.js 自定义事件操作工具
richtext.html 自制富文本编辑器
欢迎打赏