JavaScript概述.pdf

第1章 JavaScript概述

第2章 使用JavaScript

第3章 语法、关键保留字及变量

第4章 数据类型

第5章 运算符

第6章 流程控制语句

第7章 函数

//没有参数的函数
function box(){
    alert('只有函数被调用,我才会被执行');
}

box();

//带参数的函数
function box(name, age){
    alert('你的姓名:'+name+',年龄:'+age);
}

box('小明',25);

//带return 返回值
function box(){
    return '我被返回了!';
}

alert(box());

function box(name, age){
    return '你的名字:'+name+',年龄:'+age;
}

alert('小明',28);
//把函数的返回值赋给一个变量,然后通过变量进行操作。
function box(num1, num2){
    return num1*num2;
}

var num = box(10,5);
alert(num);

//退出当前函数
function box(num){
    if(num<5) return num;
    return 100;
}
alert(box(10));

//arguments对象

function box(){
    return arguments[0]+'|'+arguments[1];
}

alert(box(1,2,3,4,5,6));

//arguments对象的length属性可以得到参数的数量。
function box(){

    return arguments.length;
}

alert(box(1,2,3,4,5,6));

//实现一个加法运算,将所有传进来的数字累加,而数字的个数又不确定。
function box(){
    var sum = 0;
    if(arguments.length ==0) return sum;
    for (var i =0; i <=arguments.length; i++) {
        sum = sum + arguments[i];
    }
    return sum;
}

alert(box(5,9,12));

//ECMAScript中的函数,没有像其他高级语言那种函数重载功能。
function box(num){
    return num +100;
}

function box(num) {
    return num+200;
}
alert(box(50));
View Code

第8章 对象和数组

//object类型

//使用new运算符创建Object

var box = new Object();
box.name = '小明';
box.age = 28;

//new关键字可以省略
var box = object();

//使用字面量方式创建Object
var box = {
    name : '小明';
    age:28
};

//属性字段也可以使用字符串星矢
var box = {
    'name' : '小明';
    'age' : 28;
};

//使用字面量及传统复制方式
var box = {};
box.name = '小明';
box.age =28;

//两种属性输出方式
alert(box.age);
alert(box['age']);

//PS:在使用字面量声明Object对象时,不会调用Object()构造函数(Firefox除外)。

//给对象创建方法
var box = {
    run : function(){
        return '运行';
    }
}

alert(box.run());

//使用delete删除对象属性
delete box.name;

//字面量也是向函数传递大量可选参数的首选方式。

function box(obj){
    if(obj.name != undefined) alert(obj.name);
    if(obj.age != undefined) alert(obj.age);
}

box({
    name : '小明';
    age :25
});


/*Array类型*/

//使用new关键字创建数组
var box = new Array();
var box = new Array(10);
var box = new Array('小明',28,'学生','中国');

//以上三种方法,可以省略new关键字
var box = Array();

//使用字面量方式创建数组
var box = [];
var box = ['小明',28,'学生','中国'];
var box = [1,2,]//禁止这么做,IE会识别3个元素
var box = [,,,,,,];//同样,IE的会有识别问题 

//PS:和Object一样,字面量的写法不会调用Array()构造函数。(Firefox除外)。

//使用索引下标来读取数组的值
alert(box[2]);
box[2] = '学生';
box[4] = '计算机';

//使用length属性获取数组元素量
alert(box.length);
box.length = 10;
box[box.length] = 'JS技术';//通过length给数组增加一个元素

//创建一个稍微复杂一点的数组
var box = [
    //第一个元素是一个对象
        {
            name : '小明',
            age :28,
            run: function(){
                return 'runing';
            }
        },
        //第二个元素是数组
        ['马云','李彦宏', newe Object()],

        '江苏',    //第三个元素是字符串
        25+25,    //第四个元素是数值
        newArray(1,2,3) //第五个元素是数组


];

alert(box);

//PS:数组最多可包含4294967295个元素,超出即会发生异常。

/*对象中方法*/


//转换方法 对象或数组都具有toLocaleString()、toString()和valueOf()方法。
var box=['小明',28,'计算机编程']; //字面量数组
alert(box); //隐式调用了toString()
alert(box.toString()); //和valueOf()返回一致
alert(box.toLocaleString()); //返回值和上面两种一致

//默认情况下,数组字符串都会以逗号隔开。如果使用join()方法,则可以使用不同的分隔符来构建这个字符串。

var box=['小明', 28,'计算机编程'];
alert(box.join('|')); //小明|28|计算机编程

//栈方法

//ECMAScript为数组专门提供了push()和pop()方法。

/*push()方法可以接收任意数量的参数,把它们逐个添加到数组的末尾,并返回修改后数
组的长度。而pop()方法则从数组末尾移除最后一个元素,减少数组的length值,然后返回
移除的元素。*/

var box=['小明', 28,'计算机编程']; //字面量声明
alert(box.push('中国')); //数组末尾添加一个元素,并且返回长度
alert(box); //查看数组
box.pop(); //移除数组末尾元素,并返回移除的元素
alert(box); //查看元素

//队列方法

/*栈方法是后进先出,而列队方法就是先进先出。列队在数组的末端添加元素,从数组的
前端移除元素。通过push()向数组末端添加一个元素,然后通过shift()方法从数组前端移除
一个元素。*/

var box=['小明', 28,'计算机编程']; //字面量声明
alert(box.push('中国')); //数组末尾添加一个元素,并且返回长度
alert(box); //查看数组
alert(box.shift()); //移除数组开头元素,并返回移除的元素
alert(box); //查看数组

/*ECMAScript还为数组提供了一个unshift()方法,它和shift()方法的功能完全相反。
unshift()方法为数组的前端添加一个元素。*/
var box=['小明', 28,'计算机编程']; //字面量声明
alert(box.unshift('中国','江苏')); //数组开头添加两个元素
alert(box); //查看数组
alert(box.pop()); //移除数组末尾元素,并返回移除的元素
alert(box); //查看数组

//PS:IE浏览器对unshift()方法总是返回undefined而不是数组的新长度。

//重排序方法

//数组中已经存在两个可以直接用来排序的方法:reverse()和sort()。

//reverse() 逆向排序
var box=[1,2,3,4,5]; //数组
alert(box.reverse()); //逆向排序方法,返回排序后的数组
alert(box); //源数组也被逆向排序了,说明是引用
//sort() 从小到大排序
var box=[4,1,7,3,9,2]; //数组
alert(box.sort()); //从小到大排序,返回排序后的数组
alert(box); //源数组也被从小到大排序了

//操作方法

/*ECMAScript为操作已经包含在数组中的元素提供了很多方法。concat()方法可以基于当
前数组创建一个新数组。slice()方法可以基于当前数组获取指定区域元素并创建一个新数组。
splice()主要用途是向数组的中部插入元素。*/

var box=['小明', 28,'中国']; //当前数组
var box2=box.concat('计算机编程'); //创建新数组,并添加新元素
alert(box2); //输出新数组
alert(box); //当前数组没有任何变化
var box=['小明', 28,'中国']; //当前数组
var box2=box.slice(1); //box.slice(1,3),2-4之间的元素
alert(box2); //28,中国
alert(box); //当前数组

//splice中的删除功能:
var box=['小明', 28,'中国']; //当前数组
var box2=box.splice(0,2); //截取前两个元素
alert(box2); //返回截取的元素
alert(box); //当前数组被截取的元素被删除

//splice中的插入功能:
var box=['小明', 28,'中国']; //当前数组
var box2=box.splice(1,0,'计算机编程','江苏'); //没有截取,但插入了两条
alert(box2); //在第2个位置插入两条
alert(box); //输出

//splice中的替换功能:
var box=['小明', 28,'中国']; //当前数组
var box2=box.splice(1,1,100); //截取了第2条,替换成100
alert(box2); //输出截取的28
alert(box); //输出数组
View Code

第9章 时间与日期

//创建一个日期对象,使用new运算符和Date构造方法(构造函数)即可
var box = new Date();

//在调用Date构造方法而不传递参数的情况下,新建的对象自动获取当前的时间和日期。
alert(box); //不同浏览器显示不同

//ECMAScript提供了两个方法,Date.parse()和Date.UTC()。
alert(Date.parse('6/13/2011')); //1307894400000

//如果想输出指定的日期,那么把Date.parse()传入Date构造方法里。
var box = new Date(Date.parse('6/13/2011'));//MonJun13201100:00:00GMT+0800
var box = new Date('6/13/2011'); //直接传入,Date.parse()后台被调用

//PS:Date对象及其在不同浏览器中的实现有许多奇怪的行为。

//Date.UTC()方法同样也返回表示日期的毫秒数,但它与Date.parse()在构建值时使用不同的信息。

alert(Date.UTC(2011,11)); //1322697600000

//如果Date.UTC()参数传递错误,那么就会出现负值或者NaN等非法信息。
alert(Date.UTC()); //负值或者NaN

//如果要输出指定日期,那么直接把Date.UTC()传入Date构造方法里即可。
var box = new Date(Date.UTC(2011,11,5,15,13,16));


/*通用的方法*/

//与其他类型一样,Date类型也重写了toLocaleString()、toString()和valueOf()方法;但这
//些方法返回值与其他类型中的方法不同。
var box = new Date(Date.UTC(2011,11,5,15,13,16));
alert('toString:'+box.toString());
alert('toLocaleString:'+box.toLocaleString()); //按本地格式输出

//PS:这两个方法在不同浏览器显示的效果又不一样,但不用担心,这两个方法只是在
//调试比较有用,在显示时间和日期上,没什么价值。valueOf()方法显示毫秒数。


//日期格式化方法
var box = new Date();
alert(box.toDateString()); //以特定的格式显示星期几、月、日和年
alert(box.toTimeString()); //以特定的格式显示时、分、秒和时区
alert(box.toLocaleDateString()); //以特定地区格式显示星期几、月、日和年
alert(box.toLocaleTimeString()); //以特定地区格式显示时、分、秒和时区
alert(box.toUTCString()); //以特定的格式显示完整的UTC日期。

//组件方法
/*组件方法,是为我们单独获取你想要的各种时间/日期而提供的方法。需要注意的时候,
这些方法中,有带UTC的,有不带UTC的。UTC日期指的是在没有时区偏差的情况下的
日期值。*/

alert(box.getTime()); //获取日期的毫秒数,和valueOf()返回一致
alert(box.setTime(100)); //以毫秒数设置日期,会改变整个日期
alert(box.getFullYear()); //获取四位年份
alert(box.setFullYear(2012)); //设置四位年份,返回的是毫秒数
alert(box.getMonth()); //获取月份,没指定月份,从0开始算起
alert(box.setMonth(11)); //设置月份
alert(box.getDate()); //获取日期
alert(box.setDate(8)); //设置日期,返回毫秒数
alert(box.getDay()); //返回星期几,0表示星期日,6表示星期六
alert(box.setDay(2)); //设置星期几
alert(box.getHours()); //返回时
alert(box.setHours(12)); //设置时
alert(box.getMinutes()); //返回分钟
alert(box.setMinutes(22)); //设置分钟
alert(box.getSeconds()); //返回秒数
alert(box.setSeconds(44)); //设置秒数
alert(box.getMilliseconds()); //返回毫秒数
alert(box.setMilliseconds()); //设置毫秒数
alert(box.getTimezoneOffset()); //返回本地时间和UTC时间相差的分钟数

//PS:以上方法除了getTimezoneOffset(),其他都具有UTC功能,例如setDate()及getDate()
//获取星期几,那么就会有setUTCDate()及getUTCDate()。表示世界协调时间。
View Code

第10章 正则表达式

第11章 Function类型

//使用Function构造函数
var box = new Function ('num1','num2', 'return num1+num2'); //不推荐

//作为值的函数,ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。
Function box(sumFunction, num)  {
    return sumFunction(num);
}

Function sum(num) {
    return num +10;
}

var result = box(sum ,10);

//函数内部属性

/*在函数内部,有两个特殊的对象:arguments和this。arguments是一个类数组对象,包
含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫callee的
属性,该属性是一个指针,指向拥有这个arguments对象的函数。*/

Function box(num) {
    if(num <=1){
        return 1;
    }else {
        return num*box(num-1); //一个简单的的递归
    }
}


/*对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变
是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我
们可以使用arguments.callee来代替。*/

Function box(num) {
    if(num <=1){
        return 1;
    }else{
        return num * arguments.callee(num-1);//使用callee来执行自身
    }
}

/*函数内部另一个特殊对象是this,其行为与Java和C#中的this大致相似。换句话说,
this引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。PS:当在
全局作用域中调用函数时,this对象引用的就是window。*/

//便于理解的改写例子
window.color = '红色的'; //全局的,或者varcolor='红色的';也行
alert(this.color);       //打印全局的color

var box = {
    color:'蓝色的',                //局部的color
    sayColor:function(){
        alert(this.color);    //此时的this只能box里的color
    }
};


box.sayColor();            //打印局部的color
alert(this.color);        //还是全局的

//函数属性和方法
/*ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:
length和prototype。其中,length属性表示函数希望接收的命名参数的个数。*/

function box(name, age){
    alert(name + age);
}

alert(box.length);

//对于prototype属性,它是保存所有实例方法的真正所在,也就是原型。
/*prototype下有两个方法:apply()和call(),每个函数都
包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等
于设置函数体内this对象的值。*/

Function box(num1 ,num2){
    return num1 + num2;
}

function sayBox(num1, num2){
    return box.apply(this,[num1, num2]);//this表示作用域,这里是window
                                        //[]表示box所需要的参数
}

function sayBox2(num1, num2){
    return box.apply(this, arguments);  //arguments对象表示box所需要的参数
}

alert(sayBox(10,10));            //20
alert(sayBox2(10,10));            //20


/*call()方法于apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于call()方
法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。*/

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

function callBox(num1, num2){
    return box.call(this, num1, num2);  //和apply区别在于后面的传参
}

alert(callBox(10,10));


/*事实上,传递参数并不是apply()和call()方法真正的用武之地;它们经常使用的地方是
能够扩展函数赖以运行的作用域。*/

var color = '红色的';            //或者window.color='红色的';也行

var box - {
    color :'蓝色的'
};

function sayColor(){
    alert(this.color);
}

sayColor();                //作用域在window
sayColor.call(this);    //作用域在window
sayColor.call(window);    //作用域在window
sayColor.call(box);        //作用域在box,对象冒充    


/*这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用call(box)方法的
时候,sayColor()方法的运行环境已经变成了box对象里了。
使用call()或者apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合
关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box对象和
sayColor()方法之间不会有多余的关联操作,比如box.sayColor=sayColor;*/
View Code

第12章 变量、作用域及内存

/*在变量复制方面,基本类型和引用类型也有所不同。基本类型复制的是值本身,而引用
类型复制的是地址。*/

var box='Lee'; //在栈内存生成一个box'Lee'
var box2=box; //在栈内存再生成一个box2'Lee'

//ps:box2是虽然是box1的一个副本,但从图示可以看出,它是完全独立的。也就是说,两个变量分别操作时互不影响。

var box = new Object();    //创建一个引用类型

box.name = 'Lee';        //新增一个属性

var box2 = box;            //把引用地址赋值给box2

//ps:在引用类型中,box2其实就是box,因为他们指向的是同一个对象。如果这个对象中的
//name属性被修改了,box2.name和box.name输出的值都会被相应修改掉了。


/*传递参数
ECMAScript中所有函数的参数都是按值传递的,言下之意就是说,参数不会按引用传
递,虽然变量有基本类型和引用类型之分。*/

function box(num){ //按值传递,传递的参数是基本类型
    num+=10; //这里的num是局部变量,全局无效
    retur nnum;
}
var num=50;
var result = box(num);
alert(result);         //60
alert(num);         //50
function box(num){ //按值传递,传递的参数是基本类型
num+=10;             //这里的num是局部变量,全局无效
return num;
}
var num=50;
var result=box(num);
alert(result); //60
alert(num); //50

/*PS:以上的代码中,传递的参数是一个基本类型的值。而函数里的num是一个局部变
量,和外面的num没有任何联系。*/


//下面给出一个参数作为引用类型的例子。

function box(obj) {                //按值传递,传递的参数是引用类型
    obj.name = 'Lee';
}

var p=new Object();
box(p);
alert(p.name);

/*PS:如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可
以访问。比如PHP中,必须在参数前面加上&符号表示按引用传递。而ECMAScript没有这
些,只能是局部变量。可以在PHP中了解一下。
PS:所以按引用传递和传递引用类型是两个不同的概念。*/

function box(obj) {
    obj.name = 'lee';
    var obj = new Object();        //函数内部又创建了一个对象
    obj.name = 'Mr.';            //并没有替换掉原来的obj
}

//最后得出结论,ECMAScript函数的参数都将是局部变量,也就是说,没有按引用传递。


//检测类型

//要检测一个变量的类型,我们可以通过typeof运算符来判别。诸如:
var box='Lee';
alert(typeofbox); //string


//这时我们应该采用instanceof运算符来查看。
var box=[1,2,3];
alert(box instanceof Array); //是否是数组
var box2={};
alert(box2 instanceof Object); //是否是对象
var box3=/g/;
alert(box3 instanceof RegExp); //是否是正则表达式
var box4=newString('Lee');
alert(box4 instanceof String); //是否是字符串对象

//PS:当使用instanceof检查基本类型的值时,它会返回false。

//5.执行环境及作用域

//在Web浏览器中,全局执行环境被认为是window对象。

var box='blue'; //声明一个全局变量
function setBox(){
alert(box); //全局变量可以在函数里访问
}
setBox(); //执行函数
//全局的变量和函数,都是window对象的属性和方法。
var box='blue';
function setBox(){
alert(window.box); //全局变量即window的属性
}
window.setBox(); //全局函数即window的方法


/*PS:当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和
函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。
PS:每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量
和属性一样。局部的环境也有一个类似window的变量对象,环境中定义的所有变量和函数
都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)*/

//函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。
var box='blue';
function setBox(){
    varbox='red'; //这里是局部变量,出来就不认识了
    alert(box);
}
setBox();
alert(box);

//通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境。
var box='blue';
function setBox(box){ //通过传参,替换了全局变量
    alert(box);
}
setBox('red');
alert(box);

//函数体内还包含着函数,只有这个函数才可以访问内一层的函数。

var box='blue';
function setBox(){
    function setColor(){
        var b='orange';
        alert(box);
        alert(b);
}
    setColor(); //setColor()的执行环境在setBox()内
}
setBox();

/*PS:每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境
就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执
行环境。
PS:当代码在一个环境中执行时,就会形成一种叫做作用域链的东西。它的用途是保
证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,就是执行环境的
变量对象。*/

//6.没有块级作用域

//块级作用域表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断来定义变量。
if(true){ //if语句代码块没有局部作用域
    var box='Lee';
}
alert(box);
//for循环语句也是如此
for(var i=0;i<10;i++){ //没有局部作用域
    var box='Lee';
}
alert(i);
alert(box);

//var关键字在函数里的区别
function box(num1,num2){
    var sum=num1+num2; //如果去掉var就是全局变量了
    return sum;
}
alert(box(10,10));
alert(sum); //报错

/*PS:非常不建议不使用var就初始化变量,因为这种方法会导致各种意外发生。所以初
始化变量的时候一定要加上var。*/

//一般确定变量都是通过搜索来确定该标识符实际代表什么。

var box='blue';
function getBox(){
    return box; //代表全局box
} //如果加上函数体内加上varbox='red'
alert(getBox()); //那么最后返回值就是red
//PS:变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链。


//一旦数据不再有用,那么将其设置为null来释放引用,这个做法叫做解除引用。
//这一做法适用于大多数全局变量和全局对象。
var o={
    name:'Lee'
};
o=null; //解除对象引用,等待垃圾收集器回收
View Code

第13章 基本包装类型

/*为了便于操作基本类型值,ECMAScript提供了3个特殊的引用类型:Boolean、Number
和String。这些类型与其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。*/


//基本包装类型概述
var box='Mr. Lee';         //定义一个字符串
var box2=box.substring(2); //截掉字符串前两位
alert(box2);     //输出新字符串


/*变量box是一个字符串类型,而box.substring(2)又说明它是一个对象(PS:只有对象才
会调用方法),最后把处理结果赋值给box2。'Mr. Lee'是一个字符串类型的值,按道理它不
应该是对象,不应该会有自己的方法,比如:*/
alert('Mr.Lee'.substring(2)); //直接通过值来调用方法

//字面量写法:

var box = 'Mr.Lee';  //字面量
box.name = 'Lee';    //无效属性
box.age = function(){ //无效方法
    return 100;
};

alert(box); //Mr.Lee
alert(box.substring(2)); //.Lee
alert(typeof box); //string
alert(box.name); //undefined
alert(box.age()); //错误

//new运算符写法
var box=new String('Mr.Lee'); //new运算符
box.name='Lee'; //有效属性
box.age=function(){ //有效方法
return 100;
};

alert(box); //Mr.Lee
alert(box.substring(2)); //.Lee
alert(typeof box); //object
alert(box.name); //Lee
alert(box.age()); //100

/*不管字面量形式还是new运算符形式,都可以使用它的内置方法。并且
Boolean和Number特性与String相同,三种类型可以成为基本包装类型。*/

/*PS:在使用new运算符创建以上三种类型的对象时,可以给自己添加属性和方法,但
我们建议不要这样使用,因为这样会导致根本分不清到底是基本类型值还是引用类型值。*/

//Boolean类型
//Boolean类型没有特定的属性或者方法。


//Number类型

//Number类型有一些静态属性(直接通过Number调用的属性,而无须new运算符)和方法。

/*
    Number静态属性:
    MAX_VALUE:表示最大数
    MIN_VALUE:表示最小值
    NaN:非数值
    NEGATIVE_INFINITY:负无穷大,溢出返回该值
    POSITIVE_INFINITY:无穷大,溢出返回该值
    prototype:原型,用于增加新属性和方法

*/


/*
    方法:
    toString(): 将数值转化为字符串,并且可以转换进制
    toLocaleString() ::根据本地数字格式转换为字符串
    toFixed() :将数字保留小数点后指定位数并转化为字符串
    toExponential():将数字以指数形式表示,保留小数点后指定位数并转化为字符串
    toPrecision():指数形式或点形式表述数,保留小数点后面指定位数并转化为字符串

*/
var box=1000.789;
alert(box.toString()); //转换为字符串,传参可以转换进制
alert(box.toLocaleString()); //本地形式,1,000.789
alert(box.toFixed(2)); //小数点保留,1000.78
alert(box.toExponential()); //指数形式,传参会保留小数点
alert(box.toPrecision(3)); //指数或点形式,传参保留小数点


//String类型

//String类型包含了三个属性和大量的可用内置方法。

/*
    String对象属性
    length :返回字符串的字符长度
    constructor :返回创建String对象的函数
    prototype: 通过添加属性和方法扩展字符串定义
*/

/*
String也包含对象的通用方法,比如valueOf()、toLocaleString()和toString()方法,但这
些方法都返回字符串的基本值。
*/

/*
    字符方法
    charAt(n)     返回指定索引位置的字符
    charCodeAt(n)     以Unicode编码形式返回指定索引位置的字符

*/
var box='Mr.Lee';
alert(box.charAt(1)); //r
alert(box.charCodeAt(1)); //114
alert(box[1]); //r,通过数组方式截取

//PS:box[1]在IE浏览器会显示undefined,所以使用时要慎重。

/*
    字符串操作方法
    concat(str1...str2)     将字符串参数串联到调用该方法的字符串
    slice(n,m)     返回字符串n到m之间位置的字符串
    substring(n,m) 同上
    substr(n,m) 返回字符串n开始的m个字符串

*/


va rbox='Mr.Lee';
alert(box.concat('is', ' Teacher', '!')); //Mr.LeeisTeacher!
alert(box.slice(3)); //Lee
alert(box.slice(3,5)); //Le
alert(box.substring(3)); //Lee
alert(box.substring(3,5)); //Le
alert(box.substr(3)); //Lee
alert(box.substr(3,5)); //Lee
var box='Mr.Lee';
alert(box.slice(-3)); //Lee,6+(-3)=3位开始
alert(box.substring(-3)); //Mr.Lee 负数返回全部
alert(box.substr(-3)); //Lee,6+(-3)=3位开始
var box='Mr.Lee';
alert(box.slice(3,-1)); //Le6+(-1)=5,(3,5)
alert(box.substring(3,-1)); //Mr. 第二参为负,直接转0,
//并且方法会把较小的数字提前,(0,3)
alert(box.substr(3,-1)); //'' 第二参数为负,直接转0,(3,0)

/*
PS:IE的JavaScript实现在处理向substr()方法传递负值的情况下存在问题,它会返回
原始字符串,使用时要切记。
*/

/*
    字符串位置方法
    indexOf(str,n) 从n开始搜索的第一个str,并将搜索的索引值返回
    lastIndexOf(str,n) 从n开始搜索的最后一个str,并将搜索的索引值返回
*/
var box='Mr.Lee isLee';
alert(box.indexOf('L')); //3
alert(box.indexOf('L',5)); //10
alert(box.lastIndexOf('L')); //10
alert(box.lastIndexOf('L',5)); //3,从指定的位置向前搜索


//PS:如果没有找到想要的字符串,则返回-1。

//示例:找出全部的L
var box='Mr.Lee isLee'; //包含两个L的字符串
var boxarr=[]; //存放L位置的数组
varpos=box.indexOf('L'); //先获取第一个L的位置
while(pos>-1){ //如果位置大于-1,说明还存在L
    boxarr.push(pos); //添加到数组
    pos=box.indexOf('L',pos+1); //从新赋值pos目前的位置
}
alert(boxarr); //输出

/*
    大小写转换方法
    toLowerCase(str) 将字符串全部转换为小写
    toUpperCase(str) 将字符串全部转换为大写
    toLocaleLowerCase(str) 将字符串全部转换为小写,并且本地化
    toLocaleupperCase(str) 将字符串全部转换为大写,并且本地化
*/
var box='Mr.Lee isLee';
alert(box.toLowerCase()); //全部小写
alert(box.toUpperCase()); //全部大写
alert(box.toLocaleLowerCase()); //
alert(box.toLocaleUpperCase()); //

//PS:只有几种语言(如土耳其语)具有地方特有的大小写本地性,一般来说,是否本
//地化效果都是一致的。


/*
    字符串的模式匹配方法
    match(pattern) 返回pattern中的子串或null
    replace(pattern,replacement) 用replacement替换pattern
    search(pattern) 返回字符串中pattern开始位置
    split(pattern) 返回字符串按指定pattern拆分的数组
*/

var box='Mr.Lee isLee';
alert(box.match('L')); //找到L,返回L否则返回null
alert(box.search('L')); //找到L的位置,和indexOf类型
alert(box.replace('L','Q')); //把L替换成Q
alert(box.split('')); //以空格分割成字符串
/*
    其他方法
    fromCharCode(ascii) 静态方法,输出Ascii码对应值
    localeCompare(str1,str2) 比较两个字符串,并返回相应的值
*/

alert(String.fromCharCode(76)); //L,输出Ascii码对应值

/*
    localeCompare(str1,str2)方法详解:比较两个字符串并返回以下值中的一个;
    1.如果字符串在字母表中应该排在字符串参数之前,则返回一个负数。(多数-1)
    2.如果字符串等于字符串参数,则返回0。
    3.如果字符串在自附表中应该排在字符串参数之后,则返回一个正数。(多数1)
*/

var box='Lee';
alert(box.localeCompare('apple')); //1
alert(box.localeCompare('Lee')); //0
alert(box.localeCompare('zoo')); //-1

/*
    HTML方法
    anchor(name)     <aname="name">str</a>
    big()             <big>str</big>
    blink()             <blink>str</blink>
    bold()             <b>Str</b>
    fixed()             <tt>Str</tt>
    fontcolor(color) <fontcolor="color">str</font>
    fontsize(size)     <fontsize="size">str</font>
    link(URL)         <ahref="URL">str</a>
    small()             <small>str</small>
    strike()         <strike>str</strike>
    italics()         <i>italics</i>
    sub()             <sub>str</sub>
    sup()             <sup>str</sup>
以上是通过JS生成一个html标签,根据经验,没什么太大用处,做个了解。
*/

varbox='Lee'; //
alert(box.link('http://www.yc60.com')); //超链接
View Code

第14章 内置对象

/*ECMA-262对内置对象的定义是:“由ECMAScript实现提供的、不依赖宿主环境的对
象,这些对象在ECMAScript程序执行之前就已经存在了。”意思就是说,开发人员不必显
示地实例化内置对象;因为它们已经实例化了。ECMA-262只定义了两个内置对象:Global
和Math。*/

//Global对象
/*Global(全局)对象是ECMAScript中一个特别的对象,因为这个对象是不存在的。在
ECMAScript中不属于任何其他对象的属性和方法,都属于它的属性和方法。所以,事实上,
并不存在全局变量和全局函数;所有在全局作用域定义的变量和函数,都是Global对象的
属性和方法。
PS:因为ECMAScript没有定义怎么调用Global对象,所以,Global.属性或者Global.
方法()都是无效的。(Web浏览器将Global作为window对象的一部分加以实现)*/

//1.URI编码方法

    /*
        URI编码可以对链接进行编码,以便发送给浏览器。它们采用特殊的UTF-8编码替换
        所有无效字符,从而让浏览器能够接受和理解。
        encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和#
        号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码
    */

var box='//Lee 李';
alert(encodeURI(box)); //只编码了中文
varbox='//Lee李';
alert(encodeURIComponent(box)); //特殊字符和中文编码了

// /PS:因为encodeURIComponent()编码比encodeURI()编码来的更加彻底,一般来说
//encodeURIComponent()使用频率要高一些。

//使用了URI编码过后,还可以进行解码,通过decodeURI()和decodeURIComponent()
//来进行解码
var box='//Lee 李';
alert(decodeURI(encodeURI(box))); //还原

varbox='//Lee 李';
alert(decodeURIComponent(encodeURIComponent(box))); //还原

/*PS:URI方法如上所述的四种,用于代替已经被ECMA-262第3版废弃的escape()和
unescape()方法。URI方法能够编码所有的Unicode字符,而原来的只能正确地编码ASCII
字符。所以建议不要再使用escape()和unescape()方法。*/

//eval()方法
/*eval()方法主要担当一个字符串解析器的作用,他只接受一个参数,而这个参数就是要
执行的JavaScript代码的字符串。*/

eval('varbox=100'); //解析了字符串代码
alert(box);
eval('alert(100)'); //同上
eval('functionbox(){return123}'); //函数也可以
alert(box());

/*eval()方法的功能非常强大,但也非常危险。因此使用的时候必须极为谨慎。特别是在
用户输入数据的情况下,非常有可能导致程序的安全性,比如代码注入等等。*/

//Global对象属性    
//Global对象包含了一些属性:undefined、NaN、Object、Array、Function等等。
alert(Array); //返回构造函数
//window对象

//之前已经说明,Global没有办法直接访问,而Web浏览器可以使用window对象来实现一全局访问。
alert(window.Array); //同上

/*-----------------------------------------------------------------------------*/

//Math对象

//ECMAScript还为保存数学公式和信息提供了一个对象,即Math对象。


/*
    1.Math对象的属性
    Math对象包含的属性大都是数学计算中可能会用到的一些特殊值。
    Math.E 自然对数的底数,即常量e的值
    Math.LN10 10的自然对数
    Math.LN2 2的自然对数
    Math.LOG2E 以2为底e的对数
    Math.LOG10E 以10为底e的对数
    Math.PI ∏的值
    Math.SQRT1_2 1/2的平方根
    Math.SQRT2 2的平方根
*/
alert(Math.E);
alert(Math.LN10);
alert(Math.LN2);
alert(Math.LOG2E);
alert(Math.LOG10E);
alert(Math.PI);
alert(Math.SQRT1_2);
alert(Math.SQRT2);

//min()和max()方法
//Math.min()用于确定一组数值中的最小值。Math.max()用于确定一组数值中的最大值。

alert(Math.min(2,4,3,6,3,8,0,1,3)); //最小值
alert(Math.max(4,7,8,3,1,9,6,0,3,2)); //最大值

//舍入方法
    /*
        Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
        Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
        Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数;
    */

alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25

//random()方法

/*
    Math.random()方法返回介于0到1之间一个随机数,不包括0和1。如果想大于这个范
围的话,可以套用一下公式:
值=Math.floor(Math.random()*总数+第一个值)
*/
alert(Math.floor(Math.random()*10+1)); //随机产生1-10之间的任意数

for(var i=0;i<10;i++){
    document.write(Math.floor(Math.random()*10+5)); //5-14之间的任意数
    document.write('<br/>');
}
//为了更加方便的传递想要范围,可以写成函数:
function selectFrom(lower,upper){
    varsum=upper-lower+1; //总数-第一个数+1
    returnMath.floor(Math.random()*sum+lower);
}

for(var i=0;i<10;i++){
    document.write(selectFrom(5,10)); //直接传递范围即可
    document.write('<br/>');
}


/*
    其他方法

    Math.abs(num) 返回num的绝对值
    Math.exp(num) 返回Math.E的num次幂
    Math.log(num) 返回num的自然对数
    Math.pow(num,power) 返回num的power次幂
    Math.sqrt(num) 返回num的平方根
    Math.acos(x) 返回x的反余弦值
    Math.asin(x) 返回x的反正弦值
    Math.atan(x) 返回x的反正切值
    Math.atan2(y,x) 返回y/x的反正切值
    Math.cos(x) 返回x的余弦值
    Math.sin(x) 返回x的正弦值
    Math.tan(x) 返回x的正切值
*/
View Code

第15章 面向对象与原型

//创建对象

var box=new Object();     //创建一个Object对象
box.name='Lee';         //创建一个name属性并赋值
box.age=100;         //创建一个age属性并赋值
box.run=function(){ //创建一个run()方法并返回值
    return this.name+this.age+'运行中...';
};
alert(box.run()); //输出属性和方法的值

/*上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象
本身。*/

var box2=box; //得到box的引用
box2.name='Jack'; //直接改变了name属性

alert(box2.run()); //用box.run()发现name也改变了
var box2= new Object();
box2.name='Jack';
box2.age=200;
box2.run=function(){
    return this.name+this.age+'运行中...';
};
alert(box2.run()); //这样才避免和box混淆,从而保持独立

/*为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法
就是为了解决实例化对象产生大量重复的问题。*/

function createObject(name,age){ //集中实例化的函数
    varobj=newObject();
    obj.name=name;
    obj.age=age;
    obj.run=function(){
    return this.name+this.age+'运行中...';
        };
    returnobj;
}
var box1=createObject('Lee',100); //第一个实例
var box2=createObject('Jack',200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立

/*工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
搞清楚他们到底是哪个对象的实例。*/

alert(typeof box1); //Object
alert(box1 instanceof Object); //true

/*ECMAScript中可以采用构造函数(构造方法)可用来创建特定的对象。类型于Object对
象。*/

function Box(name,age){ //构造函数模式
    this.name=name;
    this.age=age;
    this.run=function(){
    return this.name+this.age+'运行中...';
};
}
var box1=new Box('Lee',100); //newBox()即可
var box2=new Box('Jack',200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box

/*
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题
是,这里并没有newObject(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(newObject());
2.直接将属性和方法赋值给this对象;
3.没有renturn语句。
*/


/*
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和
普通函数);
2.通过构造函数创建对象,必须使用new运算符。
既然通过构造函数可以创建对象,那么这个对象是哪里来的,newObject()在什么地方
执行了?执行的过程如下:
1.当使用了构造函数,并且new构造函数(),那么就后台执行了newObject();
2.将构造函数的作用域给新对象,(即newObject()创建出的对象),而函数体内的this就
代表newObject()出来的对象。
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。
关于this的使用,this其实就是代表当前作用域对象的引用。如果在全局范围this就代
表window对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
*/

var box = 2;
alert(this.box); //全局,代表window

/*构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函
数,必须用new运算符来调用,否则就是普通函数。*/

var box = new Box('Lee',100); //构造模式调用
alert(box.run());
Box('Lee',20); //普通模式调用,无效
var o= new Object();
Box.call(o,'Jack', 200) //对象冒充调用
alert(o.run());

//探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。

var box1 = new Box('Lee',100); //传递一致
var box2 = new Box('Lee',100); //同上


alert(box1.name == box2.name); //true,属性的值相等
alert(box1.run == box2.run); //false,方法其实也是一种引用地址
alert(box1.run() == box2.run()); //true,方法的值相等,因为传参一致

/*可以把构造函数里的方法(或函数)用newFunction()方法来代替,得到一样的效果,更加
证明,他们最终判断的是引用地址,唯一性。*/

function Box(name,age){ //newFunction()唯一性
    this.name=name;
    this.age=age;
    this.run =new Function("return this.name+this.age+'运行中...'");
}

/*我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,但这种做
法没什么必要,只是加深学习了解:*/

function Box(name,age){
    this.name=name;
    this.age=age;
    this.run=run;
}

function run(){ //通过外面调用,保证引用地址一致
    returnt his.name+this.age+'运行中...';
}

/*虽然使用了全局的函数run()来解决了保证引用地址一致的问题,但这种方式又带来了
一个新的问题,全局中的this在对象调用的时候是Box本身,而当作普通函数调用的时候,
this又代表window。*/

/*-----------------------------------------------------------------------*/

//原型

/*我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是
包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype通过
调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所
包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息
添加到原型中。*/

function Box(){} //声明一个构造函数
    Box.prototype.name='Lee'; //在原型里添加属性
    Box.prototype.age=100;
    Box.prototype.run=function(){ //在原型里添加方法
    return this.name+this.age+'运行中...';
};


//比较一下原型内的方法地址是否一致:

var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致

/*在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__
属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。
通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器
均能识别。虽然可以输出,但无法获取内部信息。*/

alert(box1.__proto__); //[objectObject]

//判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。

alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向

/*原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原
型中的值。*/

var box1 = new Box();
alert(box1.name); //Lee,原型里的值
box1.name='Jack';
alert(box.1name); //Jack,就近原则,
var box2 = new Box();
alert(box2.name); //Lee,原型里的值,没有被box1修改

//如果想要box1也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可,
//具体如下:

delete box1.name; //删除属性
alert(box1.name);

//如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数
//来验证:

alert(box.hasOwnProperty('name')); //实例里有返回true,否则返回false


//in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原
//型中。

alert('name'inbox); //true,存在实例中或原型中

//我们可以通过hasOwnProperty()方法检测属性是否存在实例中,也可以通过in来判断
//实例或原型中是否存在属性。那么结合这两种方法,可以判断原型中是否存在属性。

function isProperty(object,property){ //判断原型中是否存在属性
    return !object.hasOwnProperty(property)&&(propertyinobject);
}
var box = new Box();
alert(isProperty(box,'name')) //true,如果原型有

//为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使
//用字面量的方式:

function Box(){};
    Box.prototype={ //使用字面量的方式
    name:'Lee',
    age:100,
    run:function(){
    return this.name+this.age+'运行中...';
}
};

/*使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区
别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建
的方式则相反。*/

var box = newBox();
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor==Box); //字面量方式,返回false,否则,true
alert(box.constructor==Object); //字面量方式,返回true,否则,false

// /如果想让字面量方式的constructor指向实例对象,那么可以这么做:

Box.prototype={
    constructor:Box, //直接强制指向即可
};

/*PS:字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实
就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自
动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会
指向新对象,那个新对象没有指定构造函数,那么就默认为Object。*/

//原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。

function Box(){};
Box.prototype={ //原型被重写了
    constructor:Box,
    name:'Lee',
    age:100,
    run:function(){
    return this.name+this.age+'运行中...';
    }
};

Box.prototype={
    age=200
};
var box = new Box(); //在这里声明
alert(box.run()); //box只是最初声明的原型

//原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript内置的引用类型都可
//以使用这种方式,并且内置的引用类型本身也使用了原型。

alert(Array.prototype.sort); //sort就是Array类型的原型方法
alert(String.prototype.substring); //substring就是String类型的原型方
String.prototype.addstring=function(){ //给String类型添加一个方法
    return this+',被添加了!'; //this代表调用的字符串
};
alert('Lee'.addstring()); //使用这个方法


/*PS:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种
方法。因为它可能会导致命名冲突,不利于代码维护。*/

/*原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺
点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性
也还可以。但如果属性包含引用类型,就存在一定的问题:*/

function Box(){};
Box.prototype={
    constructor:Box,
    name:'Lee',
    age:100,
    family:['父亲', '母亲', '妹妹'], //添加了一个数组属性
    run:function(){
    return this.name+this.age+this.family;
    }
};
var box1 = new Box();

box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了

/*PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要
保留自己的特性,而不能共享。*/

//为了解决构造传参和共享问题,可以组合构造函数+原型模式:

function Box(name,age){ //不共享的使用构造函数
    this.name=name;
    this.age=age;
    this.family=['父亲', '母亲', '妹妹'];
};

Box.prototype={ //共享的使用原型模式
    constructor:Box,
    run:function(){
    return this.name+this.age+this.family;
    }
};


//PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。

/*原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在
声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装
到一起。为了解决这个问题,我们可以使用动态原型模式。*/

function Box(name,age){ //将所有信息封装到函数体内
    this.name=name;
    this.age=age;
    if(typeofthis.run!='function') { //仅在第一次调用的初始化
        Box.prototype.run=function(){
        return this.name+this.age+'运行中...';
        };
    }
}
var box = new Box('Lee',100);
alert(box.run());

/*当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就
不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现
了原型方法共享,并且属性都保持独立。*/

if(typeof this.run!='function') {
    alert('第一次初始化'); //测试用
    Box.prototype.run=function(){
    return this.name+this.age+'运行中...';
};
}
var box = new Box('Lee',100); //第一次创建对象
alert(box.run()); //第一次调用
alert(box.run()); //第二次调用
var box2 =new Box('Jack',200); //第二次创建对象
alert(box2.run());
alert(box2.run());

/*PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会
切断实例和新原型之间的联系。
以上讲解了各种方式对象创建的方法,如果这几种方式都不能满足需求,可以使用一开
始那种模式:寄生构造函数。*/

function Box(name,age){
    var obj = new Object();
    obj.name=name;
    obj.age=age;
    obj.run=function(){
    return this.name+this.age+'运行中...';
    };
    return obj;
}

/*寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对
象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类
型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。*/

function myString(string){
    var str = new String(string);
    str.addstring=function(){
    return this+',被添加了!';
    };
    return str;
}
var box=new myString('Lee'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());

/*在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,
这里的new是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。*/

function Box(name,age){
    var obj = new Object();
    obj.run=function(){
    return name+age+'运行中...'; //直接打印参数即可
    };
    return obj;
}
var box=Box('Lee',100); //直接调用函数
alert(box.run());
//PS:稳妥构造函数和寄生类似。

// /继承

/*继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继
承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现
继承的方式依靠原型链完成。*/

function Box(){ //Box构造
    this.name='Lee';
}
function Desk(){ //Desk构造
    this.age=100;
}
Desk.prototype = new Box(); //Desc继承了Box,通过原型,形成链条
var desk = new Desk();
alert(desk.age);
alert(desk.name); //得到被继承的属性
function Table(){ //Table构造
    this.level='AAAAA';
}

Table.prototype = new Desk(); //继续原型链继承
var table = new Table();
alert(table.name); //继承了Box和Desk

/*如果要实例化table,那么Desk实例中有age=100,原型中增加相同的属性age=200,
最后结果是多少呢?*/

Desk.prototype.age=200; //实例和原型中均包含age

/*PS:以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而
继承Object是自动完成的,并不需要程序员手动继承。
经过继承后的实例,他们的从属关系会怎样呢?*/

alert(table instanceof Object); //true
alert(desk instanceof Table); //false,desk是table的超类
alert(table instanceof Desk); //true
alert(table instanceof Box); //true

/*在JavaScript里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的
函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使
用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或
者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。*/

function Box(age){
    this.name=['Lee','Jack', 'Hello']
    this.age=age;
}
function Desk(age){
Box.call(this,age); //对象冒充,给超类型传参
}

var desk = new Desk(200);
alert(desk.age);
alert(desk.name);
desk.name.push('AAA'); //添加的新数据,只给desk
alert(desk.name);

//借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需
//要原型链+借用构造函数的模式,这种模式成为组合继承。

function Box(age){
    this.name=['Lee','Jack', 'Hello']
    this.age=age;
}
Box.prototype.run=function(){
return this.name+this.age;
};
function Desk(age){
    Box.call(this,age); //对象冒充
}
Desk.prototype=new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.run());

/*还有一种继承模式叫做:原型式继承;这种继承借助原型并基于已有的对象创建新对象,
同时还不必因此创建自定义类型。*/

function obj(o){ //传递一个字面量函数
    function F(){} //创建一个构造函数
    F.prototype=o; //把字面量函数赋值给构造函数的原型
    return new F(); //最终返回出实例化的构造函数
}
var box={ //字面量对象
    name:'Lee',
    arr:['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //传递
alert(box1.name);

box1.name='Jack';
alert(box1.name);
alert(box1.arr);
box1.arr.push('父母');
alert(box1.arr);
varbox2=obj(box); //传递
alert(box2.name);
alert(box2.arr); //引用类型共享了


//寄生式继承把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。


function create(o){ //封装创建过程
    var f = obj(o);
    f.run=function(){
    return this.arr; //同样,会共享引用
    };
    return f;
}

/*组合式继承是JavaScript最常用的继承模式;但,组合式继承也有一点小问题,就是超
类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的
内部。*/

function Box(name){
    this.name=name;
    this.arr=['哥哥','妹妹','父母'];
}
Box.prototype.run=function(){
return this.name;
};
function Desk(name,age){
    Box.call(this,name); //第二次调用Box
    this.age=age;
}
Desk.prototype = new Box(); //第一次调用Box

// /以上代码是之前的组合继承,那么寄生组合继承,解决了两次调用的问题。

function obj(o){
    function F(){}
    F.prototype=o;
    return new F();
}

function create(box,desk){
var f = obj(box.prototype);
f.constructor=desk;
desk.prototype=f;
}
function Box(name){
    this.name=name;
    this.arr=['哥哥','妹妹','父母'];
}
Box.prototype.run=function(){
    return this.name;
};

function Desk(name,age){
    Box.call(this,name);
    this.age=age;
}
inPrototype(Box,Desk); //通过这里实现继承
var desk = new Desk('Lee',100);
desk.arr.push('姐姐');
alert(desk.arr);
alert(desk.run()); //只共享了方法
var desk2 = new Desk('Jack',200);
alert(desk2.arr); //引用问题解决
View Code

第16章 匿名函数和闭包

//匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数。

//匿名函数

//普通函数
function box(){ //函数名是box
    return 'Lee';
}

//匿名函数
function (){ //匿名函数,会报错
    return 'Lee';
}

//通过表达式自我执行
(function box(){ //封装成表达式
    alert('Lee');
})(); //()表示执行函数,并且传参

//把匿名函数赋值给变量
var box = function(){ //将匿名函数赋给变量
    return'Lee';
};
alert(box()); //调用方式和函数调用相似

//函数里的匿名函数
function box(){
    return function(){ //函数里的匿名函数,产生闭包
    return'Lee';
    }
}
alert(box()()); //调用匿名函数


//闭包
/*闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在
一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。*/

//通过闭包可以返回局部变量

function box(){
    var user = 'Lee';
    return function(){ //通过匿名函数返回box()局部变量
    return user;
};
}
alert(box()()); //通过box()()来直接调用匿名函数返回值
var b = box();
alert(b()); //另一种调用匿名函数返回值

/*使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使
用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,
所以推荐使用私有的,封装的局部变量)。*/

//通过全局变量来累加
var age = 100; //全局变量
function box(){
    age++; //模块级可以调用全局变量,进行累加
}
box(); //执行函数,累加了
alert(age); //输出全局变量
//通过局部变量无法实现累加
function box(){
    var age=100;
    age++; //累加
    return age;
}
alert(box()); //101
alert(box()); //101,无法实现,因为又被初始化了

//通过闭包可以实现局部变量的累加
function box(){
    var age=100;
    return function(){
    age++;
    return age;
    }
}
var b = box(); //获得函数
alert(b()); //调用匿名函数
alert(b()); //第二次调用匿名函数,实现累加

/*PS:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更
多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。*/

//作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值。

//循环里包含匿名函数
function box(){
    var arr=[];
    for(var i=0;i<5;i++){
    arr[i]=function(){
    return i;
    };
    }
    return arr;
}
var b = box(); //得到函数数组
alert(b.length); //得到函数集合长度
for(var i=0;i<b.length;i++){
alert(b[i]()); //输出每个函数的值,都是最后一个值
}

/*上面的例子输出的结果都是5,也就是循环后得到的最大的i值。因为b[i]调用的是匿
名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i早已变成5,所
以最终的结果就是5个5。*/

//循环里包含匿名函数-改1,自我执行匿名函数

function box(){
    var arr=[];
    for(var i=0;i<5;i++){
    arr[i]=(function(num){ //自我执行
    return num;
    })(i); //并且传参
    }
    returnarr;
}


var b = box();
for(var i=0;i<b.length;i++){
    alert(b[i]);
}

/*改1中,我们让匿名函数进行自我执行,导致最终返回给a[i]的是数组而不是函数了。
最终导致b[0]-b[4]中保留了0,1,2,3,4的值。*/

//循环里包含匿名函数-改2,匿名函数下再做个匿名函数

function box(){
    var arr=[];
    for(vari=0;i<5;i++){
    arr[i]=(function(num){
    return function(){ //直接返回值,改2变成返回函数
    return num; //原理和改1一样
        }
    })(i);
    }
    return arr;
}
var b = box();
for(var i=0;i<b.length;i++){
alert(b[i]()); //这里通过b[i]()函数调用即可
}

/*改1和改2中,我们通过匿名函数自我执行,立即把结果赋值给a[i]。每一个i,是调
用方通过按值传递的,所以最终返回的都是指定的递增的i。而不是box()函数里的i。*/

/*关于this对象
在闭包中使用this对象也可能会导致一些问题,this对象是在运行时基于函数的执行环
境绑定的,如果this在全局范围就是window,如果在对象内部就指向这个对象。而闭包却
在运行时指向window的,因为闭包并不属于这个对象的属性或方法。*/

var user='The Window';
var obj={
    user:'The Object',
    getUserFunction:function(){
    return function(){ //闭包不属于obj,里面的this指向window
    return this.user;

        };
    }
};

alert(obj.getUserFunction()()); //Thewindow

//可以强制指向某个对象
alert(obj.getUserFunction().call(obj)); //TheObject

//也可以从上一个作用域中得到对象
getUserFunction:function(){
    var that=this; //从对象的方法里得对象
    return function(){
    return that.user;
    };
}

/*内存泄漏
由于IE的JScript对象和DOM对象使用不同的垃圾收集方式,因此闭包在IE中会导致
一些问题。就是内存泄漏的问题,也就是无法销毁驻留在内存中的元素。以下代码有两个知
识点还没有学习到,一个是DOM,一个是事件。*/

function box(){
    var oDiv = document.getElementById('oDiv'); //oDiv用完之后一直驻留在内存
    oDiv.onclick=function(){
    alert(oDiv.innerHTML); //这里用oDiv导致内存泄漏
    };
}
box();

//那么在最后应该将oDiv解除引用来避免内存泄漏。
function box(){
    var oDiv=document.getElementById('oDiv');
    var text=oDiv.innerHTML;
    oDiv.onclick=function(){
    alert(text);
};
oDiv=null; //解除引用
}

//PS:如果并没有使用解除引用,那么需要等到浏览器关闭才得以释放。

//模仿块级作用域
//JavaScript没有块级作用域的概念。
function box(count){
    for(vari=0;i<count;i++){}
    alert(i); //i不会因为离开了for块就失效
}
box(2);
function box(count){
for(vari=0;i<count;i++){}
vari; //就算重新声明,也不会前面的值
alert(i);
}
box(2);

/*以上两个例子,说明JavaScript没有块级语句的作用域,if(){}for(){}等没有作用域,
如果有,出了这个范围i就应该被销毁了。就算重新声明同一个变量也不会改变它的值。
JavaScript不会提醒你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声
明视而不见(如果初始化了,当然还会执行的)。使用模仿块级作用域可避免这个问题。*/

//模仿块级作用域(私有作用域)
(function(){
//这里是块级作用域
})();
//使用块级作用域(私有作用域)改写
function box(count){
(function(){
for( var i=0; i<count; i++){}
    })();
    alert(i); //报错,无法访问
}
box(2);

/*使用了块级作用域(私有作用域)后,匿名函数中定义的任何变量,都会在执行结束时被
销毁。这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的
变量和函数。一般来说,我们都应该尽可能少向全局作用域中添加变量和函数。在大型项目
中,多人开发的时候,过多的全局变量和函数很容易导致命名冲突,引起灾难性的后果。如
果采用块级作用域(私有作用域),每个开发者既可以使用自己的变量,又不必担心搞乱全局
作用域。*/

(function(){
    var box=[1,2,3,4];
    alert(box); //box出来就不认识了
})();

/*在全局作用域中使用块级作用域可以减少闭包占用的内存问题,因为没有指向匿名函数
的引用。只要函数执行完毕,就可以立即销毁其作用域链了。*/

/*私有变量
JavaScript没有私有属性的概念;所有的对象属性都是公有的。不过,却有一个私有变
量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问
这些变量。*/

function box(){
    var age=100; //私有变量,外部无法访问
}

//而通过函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而
//利用这一点,可以创建用于访问私有变量的公有方法。

function Box(){
    var age=100; //私有变量
    function run(){ //私有函数
    return'运行中...';
}
this.get=function(){ //对外公共的特权方法
return age+run();
};
}
var box = new Box();
alert(box.get());

//可以通过构造方法传参来访问私有变量。

function Person(value){
    var user = value; //这句其实可以省略
    this.getUser=function(){
    return user;
    };
    this.setUser=function(value){
    user =v alue;
    };
}

/*但是对象的方法,在多次调用的时候,会多次创建。可以使用静态私有变量来避免这个
问题。
静态私有变量
通过块级作用域(私有作用域)中定义私有变量或函数,同样可以创建对外公共的特权方
法。*/


(function(){
    varage=100;
    functionrun(){
    return'运行中...';
    }
    Box=function(){}; //构造方法
    Box.prototype.go=function(){ //原型方法
    returnage+run();
    };
})();

var box = new Box();
alert(box.go());

/*上面的对象声明,采用的是Box=function(){}而不是functionBox(){}因为如果用后
面这种,就变成私有函数了,无法在全局访问到了,所以使用了前面这种。*/

(function(){
    var user='';
    Person = function(value){
    user = value;
    };
    Person.prototype.getUser=function(){
    return user;
    };
    Person.prototype.setUser=function(value){
    user=value;
    }
})();

/*使用了prototype导致方法共享了,而user也就变成静态属性了。(所谓静态属性,即共
享于不同对象中的属性)。
模块模式
之前采用的都是构造函数的方式来创建私有变量和特权方法。那么对象字面量方式就采
用模块模式来创建。*/

var box={ //字面量对象,也是单例对象
    age:100, //这是公有属性,将要改成私有
    run:function(){ //这时公有函数,将要改成私有
    return'运行中...';
    };
};

//私有化变量和函数:

var box = function(){
    var age=100;
    function run(){
    return'运行中...';
}
    return{ //直接返回对象
    go:function(){
    return age+run();
    }
    };
}();

//上面的直接返回对象的例子,也可以这么写:

var box = function(){
    var age=100;
    function run(){
    return'运行中...';
    }
    var obj= { //创建字面量对象
    go:function(){
    return age+run();
    }
    };
    return obj; //返回这个对象
}();

/*字面量的对象声明,其实在设计模式中可以看作是一种单例模式,所谓单例模式,就是
永远保持对象的一个实例。
增强的模块模式,这种模式适合返回自定义对象,也就是构造函数。*/

function Desk(){};
var box = function(){
    var age=100;
    function run(){
    return'运行中...';
}
var desk = new Desk(); //可以实例化特定的对象
desk.go=function(){
return age + run();
};
return desk;
}(); 
alert(box.go());
View Code

第17章 BOM

//BOM也叫浏览器对象模型,它提供了很多对象,用于访问浏览器的功能。
//BOM本身是没有标准的或者还没有哪个组织去标准它。

//window对象

/*BOM的核心对象是window,它表示浏览器的一个实例。window对象处于JavaScript结
构的最顶层,对于每个打开的窗口,系统都会自动为其定义window对象。*/

/*1.对象的属性和方法
window对象有一系列的属性,这些属性本身也是对象。*/

/*    
    window对象的属性
    
    closed 当窗口关闭时为真
    defaultStatus 窗口底部状态栏显示的默认状态消息
    document 窗口中当前显示的文档对象
    frames 窗口中的框架对象数组
    history 保存有窗口最近加载的URL
    length 窗口中的框架数
    location 当前窗口的URL
    name 窗口名
    offscreenBuffering 用于绘制新窗口内容并在完成后复制已存在的内容,控制屏幕更新
    opener 打开当前窗口的窗口
    parent 指向包含另一个窗口的窗口(由框架使用)
    screen 显示屏幕相关信息,如高度、宽度(以像素为单位)
    self 指示当前窗口。
    status 描述由用户交互导致的状态栏的临时消息
    top 包含特定窗口的最顶层窗口(由框架使用)
    window 指示当前窗口,与self等效
*/

/*
    window对象的方法
    alert(text) 创建一个警告对话框,显示一条信息
    blur() 将焦点从窗口移除
    clearInterval(interval) 清除之前设置的定时器间隔
    clearTimeOut(timer) 清除之前设置的超时
    close() 关闭窗口
    confirm() 创建一个需要用户确认的对话框
    focus() 将焦点移至窗口
    open(url,name,[options]) 打开一个新窗口并返回新window对象
    prompt(text,defaultInput) 创建一个对话框要求用户输入信息
    scroll(x,y) 在窗口中滚动到一个像素点的位置
    setInterval(expression,milliseconds)经过指定时间间隔计算一个表达式
    setInterval(function,millisenconds,[arguments])经过指定时间间隔后调用一个函数
    setTimeout(expression,milliseconds)在定时器超过后计算一个表达式
    setTimeout(expression,milliseconds,[arguments])在定时器超过时后计算一个函数
    print() 调出打印对话框
    find() 调出查找对话框

*/

/*window下的属性和方法,可以使用window.属性、window.方法()或者直接属性、方法()
的方式调用。例如:window.alert()和alert()是一个意思。*/

/*2.系统对话框
浏览器通过alert()、confirm()和prompt()方法可以调用系统对话框向用户显示信息。系
统对话框与浏览器中显示的网页没有关系,也不包含HTML。*/

//弹出警告
alert('Lee'); //直接弹出警告


//确定和取消
confirm('请确定或者取消'); //这里按哪个都无效
if(confirm('请确定或者取消')) { //confirm本身有返回值
        alert('您按了确定!'); //按确定返回true
    }else{
        alert('您按了取消!'); //按取消返回false
}

//输入提示框
var num = prompt('请输入一个数字', 0); //两个参数,一个提示,一个值
alert(num); //返回值可以得到

//调出打印及查找对话框
print(); //打印
find(); //查找

defaultStatus='状态栏默认文本'; //浏览器底部状态栏初始默认值
status='状态栏文本'; //浏览器底部状态栏设置值

/*3.新建窗口
使用window.open()方法可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。
它可以接受四个参数:1.要加载的URL;2.窗口的名称或窗口目标;3.一个特性字符串;4.
一个表示新页面是否取代浏览器记录中当前加载页面的布尔值。*/
open('http://www.baidu.com'); //新建页面并打开百度
open('http://www.baidu.com','baidu'); //新建页面并命名窗口并打开百度
open('http://www.baidu.com','_parent'); //在本页窗口打开百度,_blank是新建

/*PS:不命名会每次打开新窗口,命名的第一次打开新窗口,之后在这个窗口中加载。
窗口目标是提供页面的打开的方式,比如本页面,还是新建。*/

/*
    第三字符串参数
    设置         值                     说明
    width         数值         新窗口的宽度。不能小于100
    height         数值         新窗口的高度。不能小于100
    top         数值         新窗口的Y坐标。不能是负值
    left         数值         新窗口的X坐标。不能是负值
    location     yes或no     是否在浏览器窗口中显示地址栏。不同浏览器默认值不同
    menubar     yes或no     是否在浏览器窗口显示菜单栏。默认为no
    resizable     yes或no     是否可以通过拖动浏览器窗口的边框改变大小。默认为no
    scrollbars     yes或no     如果内容在页面中显示不下,是否允许滚动。默认为no
    status         yes或no     是否在浏览器窗口中显示状态栏。默认为no
    toolbar     yes或no     是否在浏览器窗口中显示工具栏。默认为no
    fullscreen    yes或no     浏览器窗口是否最大化,仅限IE
*/
//第三参数字符串
open('http://www.baidu.com','baidu','width=400,height=400,top=200,left=200,toolbar=yes');

//open本身返回window对象
var box = open();
box.alert(''); //可以指定弹出的窗口执行alert();

//子窗口操作父窗口
document.onclick=function(){
    opener.document.write('子窗口让我输出的!');
}

/*3.窗口的位置和大小
用来确定和修改window对象位置的属性和方法有很多。IE、Safari、Opera和Chrome
都提供了screenLeft和screenTop属性,分别用于表示窗口相对于屏幕左边和上边的位置。
Firefox则在screenX和screenY属性中提供相同的窗口位置信息,Safari和Chrome也同时
支持这两个属性。*/

//确定窗口的位置,IE支持
alert(screenLeft); //IE支持
alert(typeof screenLeft); //IE显示number,不支持的显示undefined

//确定窗口的位置,Firefox支持
alert(screenX); //Firefox支持
alert(typeof screenX); //Firefox显示number,不支持的同上

/*PS:screenX属性IE浏览器不认识,直接alert(screenX),screenX会当作一个为声明的
变量,导致不执行。那么必须将它将至为window属性才能显示为初始化变量应有的值,所
以应该写成:alert(window.screenX)。*/

//跨浏览器的方法
var leftX=(typeof screenLeft=='number') ? screenLeft : screenX;
var topY=(typeof screenTop=='number') ? screenTop : screenY;

/*窗口页面大小,Firefox、Safari、Opera和Chrome均为此提供了4个属性:innerWidth
和innerHeight,返回浏览器窗口本身的尺寸;outerWidth和outerHeight,返回浏览器窗口本
身及边框的尺寸。*/

alert(innerWidth); //页面长度
alert(innerHeight); //页面高度
alert(outerWidth); //页面长度+边框
alert(outerHeight); //页面高度+边框

/*
    PS:在Chrome中,innerWidth=outerWidth、innerHeight=outerHeight;
    PS:IE没有提供当前浏览器窗口尺寸的属性;不过,在后面的DOM课程中有提供相
    关的方法。
    在IE以及Firefox、Safari、Opera和Chrome中,document.documentElement.clientWidth
    和document.documentElement.clientHeight中保存了页面窗口的信息。
    PS:在IE6中,这些属性必须在标准模式下才有效;如果是怪异模式,就必须通过
    document.body.clientWidth和document.body.clientHeight取得相同的信息。
*/


//如果是Firefox浏览器,直接使用innerWidth和innerHeight

var width=window.innerWidth; //这里要加window,因为IE会无效
var height=window.innerHeight;
if(typeof width!='number') { //如果是IE,就使用document
    if(document.compatMode=='CSS1Compat') {
        width=document.documentElement.clientWidth;
        height=document.documentElement.clientHeight;
    }else{
        width=document.body.clientWidth; //非标准模式使用body
        height=document.body.clientHeight;
    }
}

/*PS:以上方法可以通过不同浏览器取得各自的浏览器窗口页面可视部分的大小。
document.compatMode可以确定页面是否处于标准模式,如果返回CSS1Compat即标准模式。*/

//调整浏览器位置
moveTo(0,0); //IE有效,移动到0,0坐标
moveBy(10,10); //IE有效,向下和右分别移动10像素
//调整浏览器大小
resizeTo(200,200); //IE有效,调正大小
resizeBy(200,200); //IE有效,扩展收缩大小
//PS:由于此类方法被浏览器禁用较多,用处不大。

/*
4.间歇调用和超时调用
JavaScript是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的
时刻执行。前者在指定的时间过后执行代码,而后者则是每隔指定的时间就执行一次代码。
超时调用需要使用window对象的setTimeout()方法,它接受两个参数:要执行的代码
和毫秒数的超时时间。
*/
setTimeout("alert('Lee')",1000); //不建议直接使用字符串

function box(){
    alert('Lee');
}

setTimeout(box,1000); //直接传入函数名即可
setTimeout(function(){ //推荐做法
    alert('Lee');
},1000);
//PS:直接使用函数传入的方法,扩展性好,性能更佳。

/*调用setTimeout()之后,该方法会返回一个数值ID,表示超时调用。这个超时调用的ID
是计划执行代码的唯一标识符,可以通过它来取消超时调用。
要取消尚未执行的超时调用计划,可以调用clearTimeout()方法并将相应的超时调用ID
作为参数传递给它。*/

var box = setTimeout(function(){ //把超时调用的ID复制给box
    alert('Lee');
},1000);

clearTimeout(box); //把ID传入,取消超时调用

/*间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直至间歇调
用被取消或者页面被卸载。设置间歇调用的方法是setInterval(),它接受的参数与setTimeout()
相同:要执行的代码和每次执行之前需要等待的毫秒数。*/

setInterval(function(){ //重复不停执行
    alert('Lee');
},1000);


/*取消间歇调用方法和取消超时调用类似,使用clearInterval()方法。但取消间歇调用的重
要性要远远高于取消超时调用,因为在不加干涉的情况下,间歇调用将会一直执行到页面关
闭。*/

var box=setInterval(function(){ //获取间歇调用的ID
    alert('Lee');
},1000);
clearInterval(box); //取消间歇调用

//但上面的代码是没有意义的,我们需要一个能设置5秒的定时器,需要如下代码:
var num=0; //设置起始秒
var max=5; //设置最终秒
setInterval(function(){ //间歇调用
num++; //递增num
if(num==max){ //如果得到5秒
    clearInterval(this); //取消间歇调用,this表示方法本身
    alert('5秒后弹窗!');
    }
},1000); //1秒


/*一般认为,使用超时调用来模拟间歇调用是一种最佳模式。在开发环境下,很少使用真
正的间歇调用,因为需要根据情况来取消ID,并且可能造成同步的一些问题,我们建议不
使用间歇调用,而去使用超时调用。*/

var num=0;
var max=5;
function box(){
    num++;
        if(num==max){
        alert('5秒后结束!');
    }else{
        setTimeout(box,1000);
    }
}
setTimeout(box,1000); //执行定时器

/*PS:在使用超时调用时,没必要跟踪超时调用ID,因为每次执行代码之后,如果不再
设置另一次超时调用,调用就会自行停止。*/


/*location对象
location是BOM对象之一,它提供了与当前窗口中加载的文档有关的信息,还提供了
一些导航功能。事实上,location对象是window对象的属性,也是document对象的属性;
所以window.location和document.location等效。*/


alert(location); //获取当前的URL

/*
location对象的属性


属性                         描述的URL内容
hash                 如果该部分存在,表示锚点部分
host                 主机名:端口号
hostname             主机名
href                 整个URL
pathname             路径名
port                 端口号
protocol            协议部分
search                 查询字符串

*/

/*
location对象的方法

方法                     功能
assign()         跳转到指定页面,与href等效
reload()         重载当前URL
repalce()         用新的URL替换当前页面
*/

location.hash='#1'; //设置#后的字符串,并跳转
alert(location.hash); //获取#后的字符串

location.port=8888; //设置端口号,并跳转
alert(location.port); //获取当前端口号,

location.hostname='Lee'; //设置主机名,并跳转
alert(location.hostname); //获取当前主机名,

location.pathname='Lee'; //设置当前路径,并跳转
alert(location.pathname); //获取当前路径,

location.protocal='ftp:'; //设置协议,没有跳转
alert(location.protocol); //获取当前协议

location.search='?id=5'; //设置?后的字符串,并跳转
alert(location.search); //获取?后的字符串

location.href='http://www.baidu.com'; //设置跳转的URL,并跳转
alert(location.href); //获取当前的URL

/*在Web开发中,我们经常需要获取诸如?id=5&search=ok这种类型的URL的键值对,
那么通过location,我们可以写一个函数,来一一获取。*/


function getArgs(){
//创建一个存放键值对的数组
var args=[];
//去除?号
var qs=location.search.length>0?location.search.substring(1):'';
//按&字符串拆分数组

    var items=qs.split('&');
    var item=null,name=null,value=null;
    //遍历
    for(var i =0;i<items.length;i++){
    item=items[i].split('=');
    name=item[0];
    value=item[1];
    //把键值对存放到数组中去
    args[name]=value;
    }
    return args;
}
var args=getArgs();
alert(args['id']);
alert(args['search']);
location.assign('http://www.baidu.com'); //跳转到指定的URL
location.reload(); //最有效的重新加载,有可能从缓存加载
location.reload(true); //强制加载,从服务器源头重新加载
location.replace('http://www.baidu.com'); //可以避免产生跳转前的历史记录


/*三.history对象
history对象是window对象的属性,它保存着用户上网的记录,从窗口被打开的那一刻
算起。*/


/*
        history对象的属性
    属性         描述URL中的哪部分
    length         history对象中的记录数
*/

/*
    history对象的方法


方法                         功能
back()             前往浏览器历史条目前一个URL,类似后退
forward()         前往浏览器历史条目下一个URL,类似前进
go(num)         浏览器在history对象中向前或向后

*/


function back(){ //跳转到前一个URL
    history.back();
}


function forward(){ //跳转到下一个URL
    history.forward();
}
function go(num){ //跳转指定历史记录的URL
    history.go(num);
}
//PS:可以通过判断history.length==0,得到是否有历史记录。
View Code

第18章 浏览器检测

第19章 DOM基础

/*DOM(DocumentObjectModel)即文档对象模型,针对HTML和XML文档的API(应
用程序接口)。DOM描绘了一个层次化的节点树,运行开发人员添加、移除和修改页面的
某一部分。DOM脱胎于Netscape及微软公司创始的DHTML(动态HTML),但现在它已
经成为表现和操作页面标记的真正跨平台、语言中立的方式。*/

/*一.DOM介绍
DOM中的三个字母,D(文档)可以理解为整个Web加载的网页文档;O(对象)可
以理解为类似window对象之类的东西,可以调用属性和方法,这里我们说的是document
对象;M(模型)可以理解为网页文档的树型结构。
DOM有三个等级,分别是DOM1、DOM2、DOM3,并且DOM1在1998年10月成为
W3C标准。DOM1所支持的浏览器包括IE6+、Firefox、Safari、Chrome和Opera1.7+。
PS:IE中的所有DOM对象都是以COM对象的形式实现的,这意味着IE中的DOM
可能会和其他浏览器有一定的差异。
1.节点
加载HTML页面时,Web浏览器生成一个树型结构,用来表示页面内部结构。DOM将
这种树型结构理解为由节点组成。*/
View Code

/*从上图的树型结构,我们理解几个概念,html标签没有父辈,没有兄弟,所以html标
签为根标签。head标签是html子标签,meta和title标签之间是兄弟关系。如果把每个标签*/

/*当作一个节点的话,那么这些节点组合成了一棵节点树。
PS:后面我们经常把标签称作为元素,是一个意思。
2.节点种类:元素节点、文本节点、属性节点。
*/
View Code

/*二.查找元素
W3C提供了比较方便简单的定位节点的方法和属性,以便我们快速的对节点进行操作。
分别为:getElementById()、getElementsByTagName()、getElementsByName()、getAttribute()、
setAttribute()和removeAttribute()。*/

/*元素节点方法
    方法                     说明
getElementById()         获取特定ID元素的节点
getElementsByTagName()    获取相同元素的节点列表
getElementsByName()     获取相同名称的节点列表
getAttribute()             获取特定元素节点属性的值
setAttribute()             设置特定元素节点属性的值
removeAttribute()         移除特定元素节点属性
*/

/*1.getElementById()方法
getElementById()方法,接受一个参数:获取元素的ID。如果找到相应的元素则返回该
元素的HTMLDivElement对象,如果不存在,则返回null。*/

document.getElementById('box'); //获取id为box的元素节点

/*PS:上面的例子,默认情况返回null,这无关是否存在id="box"的标签,而是执行顺序
问题。解决方法,1.把script调用标签移到html末尾即可;2.使用onload事件来处理JS,等
待html加载完毕再加载onload事件里的JS。*/

window.onload=function(){ //预加载html后执行
    document.getElementById('box');
};

/*PS:id表示一个元素节点的唯一性,不能同时给两个或以上的元素节点创建同一个命
名的id。某些低版本的浏览器会无法识别getElementById()方法,比如IE5.0-,这时需要做
一些判断,可以结合上章的浏览器检测来操作。*/

if(document.getElementById){ //判断是否支持getElementById
    alert('当前浏览器支持getElementById');
}

//当我们通过getElementById()获取到特定元素节点时,这个节点对象就被我们获取到了,
//而通过这个节点对象,我们可以访问它的一系列属性。


/*元素节点属性
    属性     说明
tagName 获取元素节点的标签名
innerHTML 获取元素节点里的内容,非W3CDOM规范*/
document.getElementById('box').tagName; //DIV
document.getElementById('box').innerHTML; //测试Div

/*
HTML属性的属性
属性             说明
id             元素节点的id名称
title         元素节点的title属性值
style         CSS内联样式属性值
className     CSS元素的类
*/
document.getElementById('box').id; //获取id
document.getElementById('box').id='person'; //设置id

document.getElementById('box').title; //获取title
document.getElementById('box').title='标题' //设置title

document.getElementById('box').style; //获取CSSStyleDeclaration对象
document.getElementById('box').style.color; //获取style对象中color的值

document.getElementById('box').style.color='red'; //设置style对象中color的值
document.getElementById('box').className; //获取class

document.getElementById('box').className='box'; //设置class
alert(document.getElementById('box').bbb); //获取自定义属性的值,非IE不支持

//2.getElementsByTagName()方法

/*getElementsByTagName()方法将返回一个对象数组HTMLCollection(NodeList),这个数
组保存着所有相同元素名的节点列表。*/

document.getElementsByTagName('*'); //获取所有元素

//PS:IE浏览器在使用通配符的时候,会把文档最开始的html的规范声明当作第一个元素节点。
document.getElementsByTagName('li'); //获取所有li元素,返回数组
document.getElementsByTagName('li')[0]; //获取第一个li元素,HTMLLIElement
document.getElementsByTagName('li').item(0) //获取第一个li元素,HTMLLIElement
document.getElementsByTagName('li').length; //获取所有li元素的数目


/*PS:不管是getElementById还是getElementsByTagName,在传递参数的时候,并不是
所有浏览器都必须区分大小写,为了防止不必要的错误和麻烦,我们必须坚持养成区分大小
写的习惯。*/

/*3.getElementsByName()方法
getElementsByName()方法可以获取相同名称(name)的元素,返回一个对象数组
HTMLCollection(NodeList)。*/

document.getElementsByName('add') //获取input元素
document.getElementsByName('add')[0].value //获取input元素的value值
document.getElementsByName('add')[0].checked//获取input元素的checked值

/*PS:对于并不是HTML合法的属性,那么在JS获取的兼容性上也会存在差异,IE浏
览器支持本身合法的name属性,而不合法的就会出现不兼容的问题。*/

/*4.getAttribute()方法
getAttribute()方法将获取元素中某个属性的值。它和直接使用.属性获取属性值的方法有
一定区别。*/

document.getElementById('box').getAttribute('id');//获取元素的id值
document.getElementById('box').id; //获取元素的id值
document.getElementById('box').getAttribute('mydiv');//获取元素的自定义属性值
document.getElementById('box').mydiv //获取元素的自定义属性值,非IE不支持
document.getElementById('box').getAttribute('class');//获取元素的class值,IE不支持
document.getElementById('box').getAttribute('className');//非IE不支持

/*PS:HTML通用属性style和onclick,IE7更低的版本style返回一个对象,onclick返回
一个函数式。虽然IE8已经修复这个bug,但为了更好的兼容,开发人员只有尽可能避免使
用getAttribute()访问HTML属性了,或者碰到特殊的属性获取做特殊的兼容处理。*/

/*5.setAttribute()方法
setAttribute()方法将设置元素中某个属性和值。它需要接受两个参数:属性名和值。如果
属性本身已存在,那么就会被覆盖。*/

document.getElementById('box').setAttribute('align','center');//设置属性和值
document.getElementById('box').setAttribute('bbb','ccc');//设置自定义的属性和值

/*PS:在IE7及更低的版本中,使用setAttribute()方法设置class和style属性是没有效果
的,虽然IE8解决了这个bug,但还是不建议使用。*/

//6.removeAttribute()方法
removeAttribute() //可以移除HTML属性。
document.getElementById('box').removeAttribute('style');//移除属性

//PS:IE6及更低版本不支持removeAttribute()方法。
/*三.DOM节点
1.node节点属性
节点可以分为元素节点、属性节点和文本节点,而这些节点又有三个非常有用的属性,
分别为:nodeName、nodeType和nodeValue。*/

/*
信息节点属性
节点类型     nodeName     nodeType     nodeValue
元素         元素名称         1             null
属性         属性名称         2             属性值
文本         #text            3             文本内容(不包含html)

*/

document.getElementById('box').nodeType; //1,元素节点
View Code

第20章 动态加载脚本和样式

//一.元素位置
//这节课补充一个DOM的方法:getBoundingClientRect()。
/*这个方法返回一个矩形对象,包含四个属性:left、top、right
和bottom。分别表示元素各边与页面上边和左边的距离。*/

var box = document.getElementById('box'); //获取元素
alert(box.getBoundingClientRect().top); //元素上边距离页面上边的距离
alert(box.getBoundingClientRect().right); //元素右边距离页面左边的距离
alert(box.getBoundingClientRect().bottom); //元素下边距离页面上边的距离
alert(box.getBoundingClientRect().left); //元素左边距离页面左边的距离

/*PS:IE、Firefox3+、Opera9.5、Chrome、Safari支持,在IE中,默认坐标从(2,2)开始计
算,导致最终距离比其他浏览器多出两个像素,我们需要做个兼容。*/

/*PS:IE、Firefox3+、Opera9.5、Chrome、Safari支持,在IE中,默认坐标从(2,2)开始计
算,导致最终距离比其他浏览器多出两个像素,我们需要做个兼容。*/

document.documentElement.clientTop; //非IE为0,IE为2
document.documentElement.clientLeft; //非IE为0,IE为2

function getRect(element){
    var rect=element.getBoundingClientRect();
    var top=document.documentElement.clientTop;
    var left=document.documentElement.clientLeft;
    return{
    top:rect.top-top,
    bottom:rect.bottom-top,
    left:rect.left-left,
    right:rect.right-left
    }
}

//PS:分别加上外边据、内边距、边框和滚动条,用于测试所有浏览器是否一致。


/*
    二.动态脚本
当网站需求变大,脚本的需求也逐步变大。我们就不得不引入太多的JS脚本而降低了
整站的性能,所以就出现了动态脚本的概念,在适时的时候加载相应的脚本。
比如:我们想在需要检测浏览器的时候,再引入检测文件。
*/

var flag=true; //设置true再加载
    if(flag){
    loadScript('browserdetect.js'); //设置加载的js
}

function loadScript(url){
    var script=document.createElement('script');
    script.type='text/javascript';
    script.src=url;
    //document.head.appendChild(script); //document.head表示<head>
    document.getElementsByTagName('head')[0].appendChild(script);
}


//PS:document.head调用,IE不支持,会报错!

//动态执行js
var script=document.createElement('script');
script.type='text/javascript';
vartext=document.createTextNode("alert('Lee')"); //IE浏览器报错
script.appendChild(text);
document.getElementsByTagName('head')[0].appendChild(script);

/*PS:IE浏览器认为script是特殊元素,不能在访问子节点。为了兼容,可以使用text
属性来代替。*/

script.text="alert('')"; //IE可以支持了。

/*PS:当然,如果不支持text,那么就可以针对不同的浏览器特性来使用不同的方法。这
里就忽略写法了。*/

/*三.动态样式
为了动态的加载样式表,比如切换网站皮肤。样式表有两种方式进行加载,一种是<link>
标签,一种是<style>标签。*/

//动态执行link
var flag=true;
if(flag){
    loadStyles('basic.css');
}
function loadStyles(url){
    var link=document.createElement('link');
    link.rel='stylesheet';
    link.type='text/css';
    link.href=url;
    document.getElementsByTagName('head')[0].appendChild(link);
}

//动态执行style
var flag=true;
if(flag){
    var style=document.createElement('style');
    style.type='text/css';
    //var box=document.createTextNode(#box{background:red}');IE不支持
    //style.appendChild(box);
    document.getElementsByTagName('head')[0].appendChild(style);
    insertRule(document.styleSheets[0],'#box', 'background:red', 0);
}

function insertRule(sheet,selectorText,cssText,position){
    //如果是非IE
    if(sheet.insertRule){
        sheet.insertRule(selectorText+"{"+cssText+"}",position);
    //如果是IE
    }elseif(sheet.addRule){
        sheet.addRule(selectorText,cssText,position);
    }
}
View Code

第24章 事件

  ①。事件入门

/*JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击。当用户
执行某些操作的时候,再去执行一系列代码。*/

/*一.事件介绍
事件一般是用于浏览器和用户操作进行交互。最早是IE和NetscapeNavigator中出现,
作为分担服务器端运算负载的一种手段。直到几乎所有的浏览器都支持事件处理。而DOM2
级规范开始尝试以一种复合逻辑的方式标准化DOM事件。IE9、Firefox、Opera、Safari和
Chrome全都已经实现了“DOM2级事件”模块的核心部分。IE8之前浏览器仍然使用其专
有事件模型。
JavaScript有三种事件模型:内联模型、脚本模型和DOM2模型。*/

/*二.内联模型
这种模型是最传统接单的一种处理事件的方法。在内联模型中,事件处理函数是HTML
标签的一个属性,用于处理指定事件。虽然内联在早期使用较多,但它是和HTML混写的,
并没有与HTML分离。*/

//在HTML中把事件处理函数作为属性执行JS代码
<input type="button" value="按钮" onclick="alert('Lee');" /> //注意单双引号
//在HTML中把事件处理函数作为属性执行JS函数
<input type="button" value="按钮" onclick="box();" /> //执行JS的函数
//PS:函数不得放到window.onload里面,这样就看不见了。


/*三.脚本模型
由于内联模型违反了HTML与JavaScript代码层次分离的原则。为了解决这个问题,我
们可以在JavaScript中处理事件。这种处理方式就是脚本模型。*/

var input=document.getElementsByTagName('input')[0]; //得到input对象
input.onclick=function(){ //匿名函数执行
alert('Lee');
};

/*PS:通过匿名函数,可以直接触发对应的代码。也可以通过指定的函数名赋值的方式
来执行函数(赋值的函数名不要跟着括号)。*/

input.onclick=box; //把函数名赋值给事件处理函数

//四.事件处理函数
//JavaScript可以处理的事件类型为:鼠标事件、键盘事件、HTML事件。

/*
    JavaScript事件处理函数及其使用列表
事件处理函数            影响的元素                             何时发生
onabort                     图像                         当图像加载被中断时
onblur             窗口、框架、所有表单对象                 当焦点从对象上移开时
onchange         输入框,选择框和文本区域                当改变一个元素的值且失去焦点时
onclick         链接、按钮、表单对象、图像映射区域        当用户单击对象时
ondblclick         链接、按钮、表单对象                     当用户双击对象时
ondragdrop         窗口                                     当用户将一个对象拖放到浏览器窗口时
onError         脚本                                     当脚本中发生语法错误时
onfocus         窗口、框架、所有表单对象                 当单击鼠标或者将鼠标移动聚焦到窗口或框架时
onkeydown         文档、图像、链接、表单                     当按键被按下时
onkeypress         文档、图像、链接、表单                     当按键被按下然后松开时
onkeyup         文档、图像、链接、表单                     当按键被松开时
onload             主题、框架集、图像                         文档或图像加载后
onunload         主体、框架集                             文档或框架集卸载后
onmouseout         链接                                     当图标移除链接时
onmouseover     链接                                     当鼠标移到链接时
onmove             窗口                                     当浏览器窗口移动时
onreset         表单复位按钮                             单击表单的reset按钮
onresize         窗口                                     当选择一个表单对象时
onselect         表单元素                                 当选择一个表单对象时
onsubmit         表单                                     当发送表格到服务器时
*/

/*PS:所有的事件处理函数都会都有两个部分组成,on+事件名称,例如click事件的事件
处理函数就是:onclick。在这里,我们主要谈论脚本模型的方式来构建事件,违反分离原
则的内联模式,我们忽略掉。*/

/*对于每一个事件,它都有自己的触发范围和方式,如果超出了触发范围和方式,事件处
理将失效。*/

//1.鼠标事件,页面所有元素都可触发
//click:当用户单击鼠标按钮或按下回车键时触发。
input.onclick=function(){
    alert('Lee');
};

//dblclick:当用户双击主鼠标按钮时触发。
input.ondblclick=function(){
    alert('Lee');
};

//mousedown:当用户按下了鼠标还未弹起时触发。
input.onmousedown=function(){
    alert('Lee');
};


//mouseup:当用户释放鼠标按钮时触发。
input.onmouseup=function(){
    alert('Lee');
};
//mouseover:当鼠标移到某个元素上方时触发。
input.onmouseover=function(){
    alert('Lee');
};
//mouseout:当鼠标移出某个元素上方时触发。
input.onmouseout=function(){
    alert('Lee');
};

//mousemove:当鼠标指针在元素上移动时触发。
input.onmousemove=function(){
    alert('Lee');
};
//2.键盘事件
//keydown:当用户按下键盘上任意键触发,如果按住不放,会重复触发。

onkeydown=function(){
    alert('Lee');
};


//keypress:当用户按下键盘上的字符键触发,如果按住不放,会重复触发。
onkeypress=function(){
    alert('Lee');
};
//keyup:当用户释放键盘上的键触发。
onkeyup=function(){
    alert('Lee');
};

//3.HTML事件
//load:当页面完全加载后在window上面触发,或当框架集加载完毕后在框架集上触发。
window.onload=function(){
    alert('Lee');
};
//unload:当页面完全卸载后在window上面触发,或当框架集卸载后在框架集上触发。
window.onunload=function(){
    alert('Lee');
};

//select:当用户选择文本框(input或textarea)中的一个或多个字符触发。
input.onselect=function(){
    alert('Lee');
};
//change:当文本框(input或textarea)内容改变且失去焦点后触发。
input.onchange=function(){
    alert('Lee');
};
//focus:当页面或者元素获得焦点时在window及相关元素上面触发。
input.onfocus=function(){
    alert('Lee');
};

//blur:当页面或元素失去焦点时在window及相关元素上触发。
input.onblur=function(){
    alert('Lee');
};


//submit:当用户点击提交按钮在<form>元素上触发。
form.onsubmit=function(){
    alert('Lee');
};
//reset:当用户点击重置按钮在<form>元素上触发。
form.onreset=function(){
    alert('Lee');
};
//resize:当窗口或框架的大小变化时在window或框架上触发。
window.onresize=function(){
    alert('Lee');
};

//scroll:当用户滚动带滚动条的元素时触发。
window.onscroll=function(){
    alert('Lee');
};
View Code

  ②。事件对象

/*JavaScript事件的一个重要方面是它们拥有一些相对一致的特点,可以给你的开发提供
更多的强大功能。最方便和强大的就是事件对象,他们可以帮你处理鼠标事件和键盘敲击方
面的情况,此外还可以修改一般事件的捕获/冒泡流的函数。*/

/*一.事件对象
事件处理函数的一个标准特性是,以某些方式访问的事件对象包含有关于当前事件的上
下文信息。
事件处理三部分组成:对象.事件处理函数=函数。例如:单击文档任意处。*/
document.onclick=function(){
    alert('Lee');
};

/*PS:以上程序的名词解释:click表示一个事件类型,单击。onclick表示一个事件处理
函数或绑定对象的属性(或者叫事件监听器、侦听器)。document表示一个绑定的对象,用于
触发某个元素区域。function()匿名函数是被执行的函数,用于触发后执行。*/

//除了用匿名函数的方法作为被执行的函数,也可以设置成独立的函数。
document.onclick=box; //直接赋值函数名即可,无须括号
    function box(){
    alert('Lee');
}

/*this关键字和上下文
在面向对象那章我们了解到:在一个对象里,由于作用域的关系,this代表着离它最近
对象。*/

var input=document.getElementsByTagName('input')[0];
input.onclick=function(){
    alert(this.value); //HTMLInputElement,this表示input对象
};

/*从上面的拆分,我们并没有发现本章的重点:事件对象。那么事件对象是什么?它在哪
里呢?当触发某个事件时,会产生一个事件对象,这个对象包含着所有与事件有关的信息。
包括导致事件的元素、事件的类型、以及其它与特定事件相关的信息。
事件对象,我们一般称作为event对象,这个对象是浏览器通过函数把这个对象作为参
数传递过来的。那么首先,我们就必须验证一下,在执行函数中没有传递参数,是否可以得
到隐藏的参数。*/

function box(){ //普通空参函数
    alert(arguments.length); //0,没有得到任何传递的参数
}
input.onclick=function(){ //事件绑定的执行函数
    alert(arguments.length); //1,得到一个隐藏参数
};

/*通过上面两组函数中,我们发现,通过事件绑定的执行函数是可以得到一个隐藏参数的。
说明,浏览器会自动分配一个参数,这个参数其实就是event对象。*/

input.onclick=function(){
alert(arguments[0]); //MouseEvent,鼠标事件对象
};


//上面这种做法比较累,那么比较简单的做法是,直接通过接收参数来得到即可。
input.onclick=function(evt){ //接受event对象,名称不一定非要event
alert(evt); //MouseEvent,鼠标事件对象
};

//直接接收event对象,是W3C的做法,IE不支持,IE自己定义了一个event对象,直
//接在window.event获取即可。
input.onclick=function(evt){
    vare=evt|| window.event; //实现跨浏览器兼容获取event对象
    alert(e);
};

/*二.鼠标事件
鼠标事件是Web上面最常用的一类事件,毕竟鼠标还是最主要的定位设备。那么通过
事件对象可以获取到鼠标按钮信息和屏幕坐标获取等。
1.鼠标按钮
只有在主鼠标按钮被单击时(常规一般是鼠标左键)才会触发click事件,因此检测按钮
的信息并不是必要的。但对于mousedown和mouseup事件来说,则在其event对象存在一
个button属性,表示按下或释放按钮。*/

/*
    非IE(W3C)中的button属性
    值                 说明
    0         表示主鼠标按钮(常规一般是鼠标左键)
    1         表示中间的鼠标按钮(鼠标滚轮按钮)
    2         表示次鼠标按钮(常规一般是鼠标右键)

*/

/*
    IE中的button属性
值                     说明
0                 表示没有按下按钮
1                 表示主鼠标按钮(常规一般是鼠标左键)
2                 表示次鼠标按钮(常规一般是鼠标右键)
3                 表示同时按下了主、次鼠标按钮
4                 表示按下了中间的鼠标按钮
5                 表示同时按下了主鼠标按钮和中间的鼠标按钮
6                 表示同时按下了次鼠标按钮和中间的鼠标按钮
7                 表示同时按下了三个鼠标按钮    
*/

/*
    PS:在绝大部分情况下,我们最多只使用主次中三个单击键,IE给出的其他组合键一
般无法使用上。所以,我们只需要做上这三种兼容即可。
*/


function getButton(evt){ //跨浏览器左中右键单击相应
    var e=evt|| window.event;
    if(evt){ //Chrome浏览器支持W3C和IE
        return e.button; //要注意判断顺序
    }else if(window.event){
        switch(e.button){
        case 1:
        return 0;
        case 4:
        return 1;
        case 2:
        return 2;
        }
    }
}


document.onmouseup=function(evt){ //调用
    if(getButton(evt)==0){
        alert('按下了左键!');
    }elseif(getButton(evt)==1){
        alert('按下了中键!');
    }elseif(getButton(evt)==2){
        alert('按下了右键!' );
    }
};


/*2.可视区及屏幕坐标
事件对象提供了两组来获取浏览器坐标的属性,一组是页面可视区左边,另一组是屏幕
坐标。*/

/*
    坐标属性
属性             说明
clientX     可视区X坐标,距离左边框的位置
clientY     可视区Y坐标,距离上边框的位置
screenX     屏幕区X坐标,距离左屏幕的位置
screenY     屏幕区Y坐标,距离上屏幕的位置
*/
document.onclick=function(evt){
    var e=evt|| window.event;
    alert(e.clientX+',' +e.clientY);
    alert(e.screenX+',' +e.screenY);
};


/*3.修改键
有时,我们需要通过键盘上的某些键来配合鼠标来触发一些特殊的事件。这些键为:
Shfit、Ctrl、Alt和Meat(Windows中就是Windows键,苹果机中是Cmd键),它们经常被用
来修改鼠标事件和行为,所以叫修改键。*/
/*
    修改键属性
属性             说明
shiftKey     判断是否按下了Shfit键
ctrlKey     判断是否按下了ctrlKey键
altKey         判断是否按下了alt键
metaKey     判断是否按下了windows键,IE不支持
*/

function getKey(evt){
    var e=evt|| window.event;
    var keys=[];
    if(e.shiftKey) keys.push('shift'); //给数组添加元素
    if(e.ctrlKey) keys.push('ctrl');
    if(e.altKey) keys.push('alt');
    return keys;
}

document.onclick=function(evt){
    alert(getKey(evt));
};

/*
    三.键盘事件
用户在使用键盘时会触发键盘事件。“DOM2级事件”最初规定了键盘事件,结果又删
除了相应的内容。最终还是使用最初的键盘事件,不过IE9已经率先支持“DOM3”级键盘
事件。
1.键码
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键
盘上一个特定的键对应。对数字字母字符集,keyCode属性的值与ASCII码中对应小写字母
或数字的编码相同。字母中大小写不影响。

*/

document.onkeydown=function(evt){
    alert(evt.keyCode); //按任意键,得到相应的keyCode
};

/*
不同的浏览器在keydown和keyup事件中,会有一些特殊的情况:
在Firefox和Opera中,分号键时keyCode值为59,也就是ASCII中分号的编码;而IE
和Safari返回186,即键盘中按键的键码。
PS:其他一些特殊情况由于浏览器版本太老和市场份额太低,这里不做补充。
2.字符编码
Firefox、Chrome和Safari的event对象都支持一个charCode属性,这个属性只有在发
生keypress事件时才包含值,而且这个值是按下的那个键所代表字符的ASCII编码。此时
的keyCode通常等于0或者也可能等于所按键的编码。IE和Opera则是在keyCode中保存
字符的ASCII编码。
*/

function getCharCode(evt){
    var e=evt|| window.event;
    if(typeof e.charCode=='number') {
    return e.charCode;
    }else{
    return e.keyCode;
    }
}

//PS:可以使用String.fromCharCode()将ASCII编码转换成实际的字符。

/*
keyCode和charCode区别如下:比如当按下“a键(重视是小写的字母)时,
在Firefox中会获得
keydown:keyCodeis65 charCodeis0
keyup: keyCodeis65charCodeis0
keypress:keyCodeis0 charCodeis97
在IE中会获得
keydown:keyCodeis65 charCodeisundefined
keyup: keyCodeis65 charCodeisundefined
keypress:keyCodeis97 charCodeisundefined
而当按下shift键时,在Firefox中会获得
keydown:keyCodeis16 charCodeis0
keyup:keyCodeis16 charCodeis0
在IE中会获得
keydown:keyCodeis16 charCodeisundefined
keyup:keyCodeis16 charCodeisundefined
keypress:不会获得任何的charCode值,因为按shift并没输入任何的字符,并且也不
会触发keypress事务

*/

/*
    PS:在keydown事务里面,事务包含了keyCode–用户按下的按键的物理编码。
在keypress里,keyCode包含了字符编码,即默示字符的ASCII码。如许的情势实用于
所有的浏览器–除了火狐,它在keypress事务中的keyCode返回值为0。
*/
/*
四.W3C与IE
在标准的DOM事件中,event对象包含与创建它的特定事件有关的属性和方法。触发
的事件类型不一样,可用的属性和方法也不一样。
*/


/*
                            W3C中event对象的属性和方法
属性/方法                 类型             读/写             说明
bubbles                 Boolean         只读        表明事件是否冒泡
cancelable                 Boolean         只读         表明是否可以取消事件的默认行为
currentTarget             Element         只读        其事件处理程序当前正在处理事件的那个元素
detail                     Integer         只读         与事件相关的细节信息
eventPhase                 Integer         只读        调用事件处理程序的阶段:1表示捕获阶段,2表示“处理目标”,3表示冒泡阶段
preventDefault()         Function         只读        取消事件的默认行为。如果cancelabel是true,则可以使用这个方法
stopPropagation()         Function         只读        取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法
target                     Element         只读         事件的目标
type                     String             只读         被触发的事件的类型
view                     AbstractView     只读        与事件关联的抽象视图。等同于发生事件的window对象
cancelBubble             Boolean         读/写       默认值为false,但将其设置为true就可以取消事件冒泡
returnValue             Boolean         读/写       默认值为true,但将其设置为false就可以取消事件的默认行为
srcElement                Element         只读         事件的目标
type                     String             只读         被触发的事件类型
*/

/*在这里,我们只看所有浏览器都兼容的属性或方法。首先第一个我们了解一下W3C中
的target和IE中的srcElement,都表示事件的目标。*/

function getTarget(evt){
    var e=evt|| window.event;
    return e.target|| e.srcElement; //兼容得到事件目标DOM对象
}
document.onclick=function(evt){
    var target=getTarget(evt);
    alert(target);
};

/*
事件流
事件流是描述的从页面接受事件的顺序,当几个都具有事件的元素层叠在一起的时候,
那么你点击其中一个元素,并不是只有当前被点击的元素会触发事件,而层叠在你点击范围
的所有元素都会触发事件。事件流包括两种模式:冒泡和捕获。
事件冒泡,是从里往外逐个触发。事件捕获,是从外往里逐个触发。那么现代的浏览器
默认情况下都是冒泡模型,而捕获模式则是早期的Netscape默认情况。而现在的浏览器要
使用DOM2级模型的事件绑定机制才能手动定义事件流模式。
*/

document.onclick=function(){
    alert('我是document');
};
document.documentElement.onclick=function(){
    alert('我是html');
};
document.body.onclick=function(){
    alert('我是body');
};
document.getElementById('box').onclick=function(){
    alert('我是div');
};
document.getElementsByTagName('input')[0].onclick=function(){
    alert('我是input');
};
//在阻止冒泡的过程中,W3C和IE采用的不同的方法,那么我们必须做一下兼容。
function stopPro(evt){
    vare=evt|| window.event;
    window.event?e.cancelBubble=true:e.stopPropagation();
}
View Code

  ③。事件绑定及深入

/*
    事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定
(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。
*/

/*
一.传统事件绑定的问题
传统事件绑定有内联模型和脚本模型,内联模型我们不做讨论,基本很少去用。先来看
一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。
*/

var box=document.getElementById('box'); //获取元素
box.onclick=function(){ //元素点击触发事件
    alert('Lee');
};


//问题一:一个事件处理函数触发两次事件
window.onload=function(){ //第一组程序项目或第一个JS文件
    alert('Lee');
};
window.onload=function(){ //第二组程序项目或第二个JS文件
    alert('Mr.Lee');
};

//当两组程序或两个JS文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致
//前面的window.onload完全失效了。

//解决覆盖问题,我们可以这样去解决:
window.onload=function(){ //第一个要执行的事件,会被覆盖
    alert('Lee');
};
if(typeof window.onload=='function') { //判断之前是否有window.onload
    var saved=null; //创建一个保存器
    saved=window.onload; //把之前的window.onload保存起来
}

window.onload=function(){ //最终一个要执行事件
    if(saved)saved(); //执行之前一个事件
    alert('Mr.Lee'); //执行本事件的代码
};

//问题二:事件切换器
box.onclick=toBlue; //第一次执行boBlue()
function toRed(){
    this.className='red';
    this.onclick=toBlue; //第三次执行toBlue(),然后来回切换
}

function toBlue(){
    this.className='blue';
    this.onclick=toRed; //第二次执行toRed()
}

//这个切换器在扩展的时候,会出现一些问题:
//1.如果增加一个执行函数,那么会被覆盖
box.onclick=toAlert; //被增加的函数
box.onclick=toBlue; //toAlert被覆盖了
//2.如果解决覆盖问题,就必须包含同时执行,但又出新问题
box.onclick=function(){ //包含进去,但可读性降低
    toAlert(); //第一次不会被覆盖,但第二次又被覆盖
    toBlue.call(this); //还必须把this传递到切换器里
};

/*
    综上的三个问题:覆盖问题、可读性问题、this传递问题。我们来创建一个自定义的事
件处理函数,来解决以上三个问题。
*/

function addEvent(obj,type,fn){ //取代传统事件处理函数
    var saved=null; //保存每次触发的事件处理函数
    if(typeof obj['on'+type]=='function') { //判断是不是事件
        saved=obj['on'+type]; //如果有,保存起来
    }
    obj['on'+type]=function(){ //然后执行
        if(saved)saved(); //执行上一个
        fn.call(this); //执行函数,把this传递过去
    };
}

addEvent(window,'load', function(){ //执行到了

    alert('Lee');
});
addEvent(window,'load', function(){ //执行到了
    alert('Mr.Lee');
});


/*
    PS:以上编写的自定义事件处理函数,还有一个问题没有处理,就是两个相同函数名
的函数误注册了两次或多次,那么应该把多余的屏蔽掉。那,我们就需要把事件处理函数进
行遍历,如果有同样名称的函数名就不添加即可。(这里就不做了)
*/

addEvent(window,'load', init); //注册第一次
addEvent(window,'load', init); //注册第二次,应该忽略
function init(){
    alert('Lee');
}


//用自定义事件函数注册到切换器上查看效果:
addEvent(window,'load', function(){
    var box=document.getElementById('box');
    addEvent(box,'click', toBlue);
});
function toRed(){
    this.className='red';
    addEvent(this,'click', toBlue);
}
functiontoBlue(){
    this.className='blue';
    addEvent(this,'click', toRed);
}

/*PS:当你单击很多很多次切换后,浏览器直接卡死,或者弹出一个错误:toomuch
recursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的
移除,导致越积越多,最后卡死。*/

function removeEvent(obj,type){
    if(obj['on']+type)obj['on'+type]=null; //删除事件处理函数
}

/*
    以上的删除事件处理函数只不过是一刀切的删除了,这样虽然解决了卡死和太多递归的
问题。但其他的事件处理函数也一并被删除了,导致最后得不到自己想要的结果。如果想要
只删除指定的函数中的事件处理函数,那就需要遍历,查找。(这里就不做了)
*/

/*
    二.W3C事件处理函数
“DOM2级事件”定义了两个方法,用于添加事件和删除事件处理程序的操作:
addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法,并且它
们都接受3个参数;事件名、函数、冒泡或捕获的布尔值(true表示捕获,false表示冒泡)。
*/

window.addEventListener('load',function(){
    alert('Lee');
},false);
window.addEventListener('load',function(){
    alert('Mr.Lee');
},false);

/*PS:W3C的现代事件绑定比我们自定义的好处就是:1.不需要自定义了;2.可以屏蔽相
同的函数;3.可以设置冒泡和捕获。*/

window.addEventListener('load',init,false); //第一次执行了
window.addEventListener('load',init,false); //第二次被屏蔽了
function init(){
    alert('Lee');
}


//事件切换器
window.addEventListener('load',function(){
    var box=document.getElementById('box');
    box.addEventListener('click',function(){ //不会被误删
    alert('Lee');
},false);
box.addEventListener('click',toBlue,false); //引入切换也不会太多递归卡死
},false);
function toRed(){
    this.className='red';
    this.removeEventListener('click',toRed,false);
    this.addEventListener('click',toBlue,false);
}

function toBlue(){
    this.className='blue';
    this.removeEventListener('click',toBlue,false);
    this.addEventListener('click',toRed,false);
}

/*
    设置冒泡和捕获阶段
之前我们上一章了解了事件冒泡,即从里到外触发。我们也可以通过event对象来阻止
某一阶段的冒泡。那么W3C现代事件绑定可以设置冒泡和捕获。
*/

document.addEventListener('click',function(){
    alert('document');
},true); //把布尔值设置成true,则为捕获
box.addEventListener('click',function(){
    alert('Lee');
},true); //把布尔值设置成false,则为冒泡

/*三.IE事件处理函数
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受
相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE不支持捕获,只支持冒泡;2.IE添加
事件不能屏蔽重复的函数;3.IE中的this指向的是window而不是DOM对象。4.在传统事
件上,IE是无法接受到event对象的,但使用了attchEvent()却可以,但有些区别。*/

window.attachEvent('onload',function(){
    var box=document.getElementById('box');
    box.attachEvent('onclick',toBlue);
});
function toRed(){
    var that=window.event.srcElement;
    that.className='red';
    that.detachEvent('onclick',toRed);
    that.attachEvent('onclick',toBlue);
}
function toBlue(){
    var that=window.event.srcElement;
    that.className='blue';
    that.detachEvent('onclick',toBlue);
    that.attachEvent('onclick',toRed);
}

/*
    PS:IE不支持捕获,无解。IE不能屏蔽,需要单独扩展或者自定义事件处理。IE不能
传递this,可以call过去。
*/
window.attachEvent('onload',function(){
    var box=document.getElementById('box');
    box.attachEvent('onclick',function(){
    alert(this===window); //this指向的window
    });
});
window.attachEvent('onload',function(){
var box=document.getElementById('box');
    box.attachEvent('onclick',function(){
    toBlue.call(box); //把this直接call过去
    });
});
function toThis(){
   alert(this.tagName);
}

/*在传统绑定上,IE是无法像W3C那样通过传参接受event对象,但如果使用了
attachEvent()却可以。*/

box.onclick=function(evt){
    alert(evt); //undefined
}
box.attachEvent('onclick',function(evt){
    alert(evt); //object
    alert(evt.type); //click
});
box.attachEvent('onclick',function(evt){
    alert(evt.srcElement===box); //true
    alert(window.event.srcElement===box); //true
});

//最后,为了让IE和W3C可以兼容这个事件切换器,我们可以写成如下方式:
function addEvent(obj,type,fn){ //添加事件兼容
    if(obj.addEventListener){
    obj.addEventListener(type,fn);
    }else if(obj.attachEvent){
    obj.attachEvent('on'+type,fn);
    }
}
function removeEvent(obj,type,fn){ //移除事件兼容
    if(obj.removeEventListener){
    obj.removeEventListener(type,fn);
    }else if(obj.detachEvent){
    obj.detachEvent('on'+type,fn);
    }
}

function getTarget(evt){ //得到事件目标
    if(evt.target){
        return evt.target;
    }else if(window.event.srcElement){
        return window.event.srcElement;
    }
}
/*PS:调用忽略,IE兼容的事件,如果要传递this,改成call即可。
PS:IE中的事件绑定函数attachEvent()和detachEvent()可能在实践中不去使用,有几个
原因:1.IE9就将全面支持W3C中的事件绑定函数;2.IE的事件绑定函数无法传递this;3.IE
的事件绑定函数不支持捕获;4.同一个函数注册绑定后,没有屏蔽掉;5.有内存泄漏的问题。
至于怎么替代,我们将在以后的项目课程中探讨。*/

/*四.事件对象的其他补充
在W3C提供了一个属性:relatedTarget;这个属性可以在mouseover和mouseout事件
中获取从哪里移入和从哪里移出的DOM对象。*/

box.onmouseover=function(evt){ //鼠标移入box
    alert(evt.relatedTarget); //获取移入box最近的那个元素对象
} //span
box.onmouseout=function(evt){ //鼠标移出box
    alert(evt.relatedTarget); //获取移出box最近的那个元素对象
} //span

//IE提供了两组分别用于移入移出的属性:fromElement和toElement,分别对应mouseover和mouseout。

box.onmouseover=function(evt){ //鼠标移入box
    alert(window.event.fromElement.tagName);//获取移入box最近的那个元素对象span
}
box.onmouseout=function(evt){ //鼠标移入box
    alert(window.event.toElement.tagName); //获取移入box最近的那个元素对象span
}

//PS:fromElement和toElement如果分别对应相反的鼠标事件,没有任何意义。
//剩下要做的就是跨浏览器兼容操作:

function getTarget(evt){
    var e=evt|| window.event; //得到事件对象
    if(e.srcElement){ //如果支持srcElement,表示IE
    if(e.type=='mouseover') { //如果是over
    return e.fromElement; //就使用from
    }elseif(e.type=='mouseout') { //如果是out
    return e.toElement; //就使用to
    }
    }else if(e.relatedTarget){ //如果支持relatedTarget,表示W3C
    return e.relatedTarget;
}
}

/*有时我们需要阻止事件的默认行为,比如:一个超链接的默认行为就点击然后跳转到指
定的页面。那么阻止默认行为就可以屏蔽跳转的这种操作,而实现自定义操作。
取消事件默认行为还有一种不规范的做法,就是返回false。*/

link.onclick=function(){
    alert('Lee');
    return false; //直接给个假,就不会跳转了。
};

/*PS:虽然returnfalse;可以实现这个功能,但有漏洞;第一:必须写到最后,这样导致
中间的代码执行后,有可能执行不到returnfalse;第二:returnfalse写到最前那么之后的自
定义操作就失效了。所以,最好的方法应该是在最前面就阻止默认行为,并且后面还能执行
代码。*/

link.onclick=function(evt){
    evt.preventDefault(); //W3C,阻止默认行为,放哪里都可以
    alert('Lee');
};
link.onclick=function(evt){ //IE,阻止默认行为
    window.event.returnValue=false;
    alert('Lee');
};

//跨浏览器兼容
function preDef(evt){
    var e=evt|| window.event;
    if(e.preventDefault){
    e.preventDefault();
    }else{
    e.returnValue=false;
    }
}

/*上下文菜单事件:contextmenu,当我们右击网页的时候,会自动出现windows自带的
菜单。那么我们可以使用contextmenu事件来修改我们指定的菜单,但前提是把右击的默认
行为取消掉。*/

addEvent(window,'load', function(){
    var text=document.getElementById('text');
    addEvent(text,'contextmenu', function(evt){
        var e=evt|| window.event;
        preDef(e);
        var menu=document.getElementById('menu');
        menu.style.left=e.clientX+'px';
        menu.style.top=e.clientY+'px';
        menu.style.visibility='visible';
        addEvent(document,'click', function(){
        document.getElementById('myMenu').style.visibility='hidden';
    });
    });
});

/*
PS:contextmenu事件很常用,这直接导致浏览器兼容性较为稳定。
卸载前事件:beforeunload,这个事件可以帮助在离开本页的时候给出相应的提示,“离
开”或者“返回”操作。

*/
addEvent(window,'beforeunload', function(evt){
    preDef(evt);
});

//鼠标滚轮(mousewheel)和DOMMouseScroll,用于获取鼠标上下滚轮的距离。
addEvent(document,'mousewheel', function(evt){ //非火狐
    alert(getWD(evt));
});
addEvent(document,'DOMMouseScroll', function(evt){ //火狐
    alert(getWD(evt));
});
function getWD(evt){
    var e=evt|| window.event;
    if(e.wheelDelta){
    return e.wheelDelta;
    }else if(e.detail){
    return-evt.detail*30; //保持计算的统一
    }
}

/*
    PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll。
DOMContentLoaded事件和readystatechange事件,有关DOM加载方面的事件,关于这
两个事件的内容非常多且繁杂,我们先点明在这里,在项目课程中使用的时候详细讨论。
*/
View Code

第25章 表单处理

/*
    为了分担服务器处理表单的压力,JavaScript提供了一些解决方案,从而大大打破了处
处依赖服务器的局面。
*/

/*一.表单介绍
在HTML中,表单是由<form>元素来表示的,而在JavaScript中,表单对应的则是
HTMLFormElement类型。HTMLFormElement继承了HTMLElement,因此它拥有HTML元
素具有的默认属性,并且还独有自己的属性和方法:*/

/*
        HTMLFormElement属性和方法

    属性或方法                     说明
    acceptCharset             服务器能够处理的字符集
    action                     接受请求的URL
    elements                 表单中所有控件的集合
    enctype                 请求的编码类型
    length                     表单中控件的数量
    name                     表单的名称
    target                     用于发送请求和接受响应的窗口名称
    reset()                 将所有表单重置
    submit()                 提交表单

*/

//获取表单<form>对象的方法有很多种,如下:
document.getElementById('myForm'); //使用ID获取<form>元素
document.getElementsByTagName('form')[0]; //使用获取第一个元素方式获取
document.forms[0]; //使用forms的数字下标获取元素
document.forms['yourForm']; //使用forms的名称下标获取元素
document.yourForm; //使用name名称直接获取元素

/*PS:最后一种方法使用name名称直接获取元素,已经不推荐使用,这是向下兼容的早
期用法。问题颇多,比如有两个相同名称的,变成数组;而且这种方式以后有可能会不兼容。*/

/*提交表单
通过事件对象,可以阻止submit的默认行为,submit事件的默认行为就是携带数据跳
转到指定页面。*/

addEvent(fm,'submit', function(evt){
    preDef(evt);
});

/*我们可以可以使用submit()方法来自定义触发submit事件,也就是说,并不一定非要点
击submit按钮才能提交。*/

if(e.ctrlKey&&e.keyCode==13)fm.submit(); //判断按住了ctrl和enter键触发

//PS:在表单中尽量避免使用name="submit"或id="submit"等命名,这会和submit()方法
//发生冲突导致无法提交。

/*提交数据最大的问题就是重复提交表单。因为各种原因,当一条数据提交到服务器的时
候会出现延迟等长时间没反映,导致用户不停的点击提交,从而使得重复提交了很多相同的
请求,或造成错误、或写入数据库多条相同信息。*/

addEvent(fm,'submit', function(evt){ //模拟延迟
    preDef(evt);
    setTimeout(function(){
    fm.submit();
    },3000);
});

/*有两种方法可以解决这种问题:第一种就是提交之后,立刻禁用点击按钮;第二种就是
提交之后取消后续的表单提交操作。*/

document.getElementById('sub').disabled=true; //将按钮禁用
var flag=false //设置一个监听变量
if(flag==true)return //如果存在返回退出事件
flag=true; //否则确定是第一次,设置为true

//PS:在某些浏览器,F5只能起到缓存刷新的效果,有可能获取不到真正的源头更新的
//数据。那么使用ctrl+F5就可以把源头给刷出来。

/*重置表单
用户点击重置按钮时,表单会被初始化。虽然这个按钮还得以保留,但目前的Web已
经很少去使用了。因为用户已经填写好各种数据,不小心点了重置就会全部清空,用户体验
极差。
有两种方法调用reset事件,第一个就是直接type="reset"即可;第二个就是使用fm.reset()
方法调用即可。*/

<input type="reset" value="重置"/> //不需要JS代码即可实现
addEvent(document,'click',function(){
    fm.reset(); //使用JS方法实现重置
});
addEvent(fm,'reset',function(){ //获取重置按钮
//
});

/*表单字段
如果想访问表单元素,可以使用之前章节讲到的DOM方法访问。但使用原生的DOM
访问虽然比较通用,但不是很便利。表单处理中,我们建议使用HTMLDOM,它有自己的
elements属性,该属性是表单中所有元素的集合。*/

fm.elements[0]; //获取第一个表单字段元素
fm.elements['user']; //获取name是user的表单字段元素
fm.elements.length; //获取所有表单字段的数量
//如果多个表单字段都使用同一个name,那么就会返回该name的NodeList表单列表。
fm.elements['sex']; //获取相同name表单字段列表

/*PS:我们是通过fm.elements[0]来获取第一个表单字段的,但也可以使用fm[0]直接访问
第一个字段。因为fm[0]访问方式是为了向下兼容的,所以,我们建议大家使用elements属
性来获取。
共有的表单字段属性
除了<fieldset>元素之外,所有表单字段都拥有相同的一组属性。由于<input>类型可以
表示多种表单字段,因此有些属性只适用于某些字段。以下罗列出共有的属性:*/

/*
属性或方法                     说明
disabled         布尔值,表示当前字段是否被禁用
form             指向当前字段所属表单的指针,只读
name             当前字段的名称
readOnly         布尔值,表示当前字段是否只读
tabIndex         表示当前字段的切换
type             当前字段的类型
value             当前字段的值
*/

/*
    这些属性其实就是HTML表单里的属性,在XHTML课程中已经详细讲解过,这里不
一个个赘述,重点看几个最常用的。
*/

fm.elements[0].value; //获取和设置value
fm.elements[0].form==fm; //查看当前字段所属表单
fm.elements[0].disabled=true; //禁用当前字段
fm.elements[0].type='checkbox'; //修改字段类型,极不推荐

/*除了<fieldset>字段之外,所有表单字段都有type属性。对于<input>元素,这个值等于
HTML属性的type值。对于非<input>元素,这个type的属性值如下:*/

/*
元素说明                     HTML标签                         type属性的值
单选列表                 <select>...</select>                 select-one
多选列表                 <selectmultiple>...</select>         select-multiple
自定义按钮                 <button>...</button>                 button
自定义非提交按钮         <buttontype="button">...</button>     button
自定义重置按钮             <buttontype="reset">...</button>     reset
自定义提交按钮             <buttontype="submit">...</button>     submit
*/

/*PS:<input>和<button>元素的type属性是可以动态修改的,而<select>元素的type属性
则是只读的。(在不必要的情况下,建议不修改type)。
共有的表单字段方法
每个表单字段都有两个方法:foucs()和blur()。*/

/*方法 说明
focus() 将焦点定位到表单字段里
blur() 从元素中将焦点移走*/

fm.elements[0].focus(); //将焦点移入
fm.elements[0].blur(); //将焦点移出

/*
共有的表单字段事件
表单共有的字段事件有以下三种:
*/

/*
事件名                     说明
blur                 当字段失去焦点时触发
change                对于<input>和<textarea>元素,在改变value并失去焦点时触发;对于<select>元素,在改变选项时触发
focus                 当前字段获取焦点时触发
*/

addEvent(textField,'focus', function(){ //缓存blur和change再测试一下
    alert('Lee');
});

/*
PS:关于blur和change事件的关系,并没有严格的规定。在某些浏览器中,blur事件
会先于change事件发生;而在其他浏览器中,则恰好相反。
*/

/*
二.文本框脚本
在HTML中,有两种方式来表现文本框:一种是单行文本框<inputtype="text">,一种
是多行文本框<textarea>。虽然<input>在字面上有value值,而<textarea>却没有,但通过都
可以通过value获取他们的值。
*/

var textField=fm.elements[0];
var areaField=fm.elements[1];
alert(textField.value+',' +areaField.value); //得到value值

/*
    PS:使用表单的value是最推荐使用的,它是HTMLDOM中的属性,不建议使用标准
DOM的方法。也就是说不要使用getAttribute()获取value值。原因很简单,对value属性的
修改,不一定会反映在DOM中。
除了value值,还有一个属性对应的是defaultValue,可以得到原本的value值,不会因
为值的改变而变化。
*/

alert(textField.defaultValue); //得到最初的value值

//选择文本
//使用select()方法,可以将文本框里的文本选中,并且将焦点设置到文本框中。
textField.select(); //选中文本框中的文本

/*选择部分文本
在使用文本框内容的时候,我们有时要直接选定部分文本,这个行为还没有标准。Firefox
的解决方案是:setSelectionRange()方法。这个方法接受两个参数:索引和长度。*/

textField.setSelectionRange(0,1); //选择第一个字符
textField.focus(); //焦点移入
textField.setSelectionRange(0,textField.value.length); //选择全部
textField.focus(); //焦点移入

/*除了IE,其他浏览器都支持这种写法(IE9+支持),那么IE想要选择部分文本,可以使
用IE的范围操作。*/

var range=textField.createTextRange(); //创建一个文本范围对象
range.collapse(true); //将指针移到起点
range.moveStart('character',0); //移动起点,character表示逐字移动
range.moveEnd('character',1); //移动终点,同上
range.select(); //焦点选定

/*PS:关于IE范围的详细讲解,我们将在今后的课程中继续讨论。并且W3C也有自己
的范围。*/

//选择部分文本实现跨浏览器兼容
function selectText(text,start,stop){
    if(text.setSelectionRange){
        text.setSelectionRange(start,stop);
        text.focus();
    }elseif(text.createTextRange){
    var range=text.createTextRange();
    range.collapse(true);
    range.moveStart('character',start);
    range.moveEnd('character',stop-start); //IE用终点减去起点得到字符数
    range.select();
    }
}

//使用select事件,可以选中文本框文本后触发。
    addEvent(textField,'select', function(){
    alert(this.value); //IE事件需要传递this才可以这么写
});

/*取得选择的文本
如果我们想要取得选择的那个文本,就必须使用一些手段。目前位置,没有任何规范解
决这个问题。Firefox为文本框提供了两个属性:selectionStart和selectionEnd。*/

addEvent(textField,'select', function(){
alert(this.value.substring(this.selectionStart,this.selectionEnd));
});

/*除了IE,其他浏览器均支持这两个属性(IE9+已支持)。IE不支持,而提供了另一个方
案:selection对象,属于document。这个对象保存着用户在整个文档范围内选择的文本信息。
导致我们需要做浏览器兼容。*/

function getSelectText(text){
    if(typeof text.selectionStart=='number') { //非IE
    return text.value.substring(text.selectionStart,text.selectionEnd);
    }else if(document.selection){ //IE
    return document.selection.createRange().text; //获取IE选择的文本
    }
}

/*
PS:有一个最大的问题,就是IE在触发select事件的时候,在选择一个字符后立即触
发,而其他浏览器是选择想要的字符释放鼠标键后才触发。所以,如果使用alert()的话,导
致跨浏览器的不兼容。我们没有办法让浏览器行为保持统一,但可以通过不去使用alert()来
解决。
*/

addEvent(textField,'select', function(){
    //alert(getSelectText(this)); //导致用户行为结果不一致
    document.getElementById('box').innerHTML=getSelectText(this);
});

/*过滤输入
为了使文本框输入指定的字符,我们必须对输入进的字符进行验证。有一种做法是判断
字符是否合法,这是提交后操作的。那么我们还可以在提交前限制某些字符,还过滤输入。*/

addEvent(areaField,'keypress', function(evt){
    var e=evt|| window.event;
    var charCode=getCharCode(evt); //得到字符编码
    if(!/d/.test(String.fromCharCode(charCode))&&charCode>8){ //条件阻止默认
    preDef(evt);
    }
});

/*
    PS:前半段条件判断只有数字才可以输入,导致常规按键,比如光标键、退格键、删
除键等无法使用。部分浏览器比如Firfox,需要解放这些键,而非字符触发的编码均为0;
在Safari3之前的浏览器,也会被阻止,而它对应的字符编码全部为8,所以最后就加上
charCode>8的判断即可。
PS:当然,这种过滤还是比较脆落的,我们还希望能够阻止裁剪、复制、粘贴和中文
字符输入操作才能真正屏蔽掉这些。
如果要阻止裁剪、复制和粘贴,那么我们可以在剪贴板相关的事件上进行处理,
JavaScript提供了六组剪贴板相关的事件:
*/

/*
    事件名             说明
    copy           在发生复制操作时触发
    cut         在发生裁剪操作时触发
    paste         在发生粘贴操作时触发
    beforecopy     在发生复制操作前触发
    beforecut     在发生裁剪操作前触发
    beforepaste 在发生粘贴操作前触发
*/

/*
由于剪贴板没有标准,导致不同的浏览器有不同的解释。Safari、Chrome和Firefox中,
凡是before前缀的事件,都需要在特定条件下触发。而IE则会在操作时之前触发带before
前缀的事件。
如果我们想要禁用裁剪、复制、粘贴,那么只要阻止默认行为即可。
*/
addEvent(areaField,'cut', function(evt){ //阻止裁剪
    preDef(evt);
});
addEvent(areaField,'copy', function(evt){ //阻止复制
    preDef(evt);
});
addEvent(areaField,'paste', function(evt){ //阻止粘贴
    preDef(evt);
});

/*当我们裁剪和复制的时候,我们可以访问剪贴板里的内容,但问题是FireFox,Opera
浏览器不支持访问剪贴板。并且,不同的浏览器也有自己不同的理解。所以,这里我们就不
在赘述。
最后一个问题影响到可能会影响输入的因素就是:输入法。我们知道,中文输入法,它的
原理是在输入法面板上先存储文本,按下回车就写入英文文本,按下空格就写入中文文本。
有一种解决方案是通过CSS来禁止调出输入法:*/
style="ime-mode:disabled" //CSS直接编写
areaField.style.imeMode='disabled'; //或在JS里设置也可以

/*PS:但我们也发先,Chrome浏览器却无法禁止输入法调出。所以,为了解决谷歌浏览
器的问题,最好还要使用正则验证已输入的文本。*/

addEvent(areaField,'keyup', function(evt){ //keyup弹起的时候
    this.value=this.value.replace(/[^d]/g,''); //把非数字都替换成空
});

/*
自动切换焦点
为了增加表单字段的易用性,很多字段在满足一定条件时(比如长度),就会自动切换到
下一个字段上继续填写。
*/
<input type="text" name="user1" maxlength="1"/> //只能写1个
<input type="text" name="user2" maxlength="2"/> //只能写2个
<input type="text" name="user3" maxlength="3"/> //只能写3个

function tabForward(evt){
    var e=evt|| window.event;
    var target=getTarget(evt);
    //判断当前长度是否和指定长度一致
    if(target.value.length==target.maxLength){
    //遍历所有字段
    for(vari=0;i<fm.elements.length;i++){
    //找到当前字段
    if(fm.elements[i]==target){
    //就把焦点移入下一个
    fm.elements[i+1].focus();
    //中途返回
    return;
    }
    }
    }
}

/*三.选择框脚本
选择框是通过<select>和<option>元素创建的,除了通用的一些属性和方法外,
HTMLSelectElement类型还提供了如下的属性和方法:*/

/*
            HTMLSelectElement对象
属性/方法                 说明
add(new,rel)         插入新元素,并指定位置
multiple             布尔值,是否允许多项选择
options             <option>元素的HTMLColletion集合
remove(index)         移除给定位置的选项
selectedIndex         基于0的选中项的索引,如果没有选中项,则值为-1
size                 选择框中可见的行数
*/

/*在DOM中,每个<option>元素都有一个HTMLOptionElement对象,以便访问数据,这
个对象有如下一些属性:*/

/*
                    HTMLOptionElement对象
属性             说明
index             当前选项在options集合中的索引
label             当前选项的标签
selected          布尔值,表示当前选项是否被选中
text             选项的文本
value             选项的值
*/

var city=fm.elements['city']; //HTMLSelectElement
alert(city.options); //HTMLOptionsCollection
alert(city.options[0]); //HTMLOptionElement
alert(city.type); //select-one

/*
    PS:选择框里的type属性有可能是:select-one,也有可能是:select-multiple,这取决
于HTML代码中有没有multiple属性。
*/
alert(city.options[0].firstChild.nodeValue); //上海t,获取text值,不推荐的做法
alert(city.options[0].getAttribute('value')); //上海v,获取value值,不推荐的做法
alert(city.options[0].text); //上海t,获取text值,推荐
alert(city.options[0].value); //上海v,获取value值,推荐

/*PS:操作select时,最好使用HTMLDOM,因为所有浏览器兼容的很好。而如果使用
标准DOM,会因为不同的浏览器导致不同的结果。
PS:当选项没有value值的时候,IE会返回空字符串,其他浏览器会返回text值。
选择选项
对于只能选择一项的选择框,使用selectedIndex属性最为简单。*/
addEvent(city,'change', function(){
    alert(this.selectedIndex); //得到当前选项的索引,从0开始
    alert(this.options[this.selectedIndex].text); //得到当前选项的text值
    alert(this.options[this.selectedIndex].value); //得到当前选项的value值
});

//PS:如果是多项选择,他始终返回的是第一个项。

city.selectedIndex=1; //设置selectedIndex可以定位某个索引

//通过option的属性(布尔值),也可以设置某个索引,设置为true即可。
city.options[0].selected=true; //设置第一个索引

/*而selected和selectedIndex在用途上最大的区别是,selected是返回的布尔值,所以一
般用于判断上;而selectedIndex是数值,一般用于设置和获取。*/
addEvent(city,'change', function(){
if(this.options[2].selected==true){ //判断第三个选项是否被选定
    alert('选择正确!');
}
});
//添加选项
//如需动态的添加选项我们有两种方案:DOM和Option构造函数。
var option=document.createElement('option');
option.appendChild(document.createTextNode('北京t'));
option.setAttribute('value','北京v')
city.appendChild(option);
//使用Option构造函数创建:
var option=newOption('北京t','北京v');
city.appendChild(option); //IE出现bug
//使用add()方法来添加选项:
var option=newOption('北京t','北京v');
city.add(option,0); //0,表示添加到第一位

/*PS:在DOM规定,add()中两个参数是必须的,如果不确定索引,那么第二个参数设
置null即可,即默认移入最后一个选项。但这是IE中规定第二个参数是可选的,所以设置
null表示放入不存在的位置,导致失踪,为了兼容性,我们传递undefined即可兼容。*/

city.add(option,null); //IE不显示了
city.add(option,undefined); //兼容了

//移除选项
//有三种方式可以移除某一个选项:DOM移除、remove()方法移除和null移除。
city.removeChild(city.options[0]); //DOM移除
city.remove(0); //remove()移除,推荐
city.options[0]=null; //null移除

//PS:当第一项移除后,下面的项,往上顶,所以不停的移除第一项,即可全部移除。

//移动选项

//如果有两个选择框,把第一个选择框里的第一项移到第二个选择框里,并且第一个选择
//框里的第一项被移除。
var city=fm.elements['city']; //第一个选择框
var info=fm.elements['info']; //第二个选择框
info.appendChild(city.options[0]); //移动,被自我删除

/*排列选项
选择框提供了一个index属性,可以得到当前选项的索引值,和selectedIndex的区别是,
一个是选择框对象的调用,一个是选项对象的调用。*/

var option1=city.options[1];
city.insertBefore(option1,city.options[option1.index-1]); //往下移动移位

//单选按钮
//通过checked属性来获取单选按钮的值。
for(var i=0;i<fm.sex.length;i++){ //循环单选按钮
if(fm.sex[i].checked==true){ //遍历每一个找出选中的那个
    alert(fm.sex[i].value); //得到值
    }
}

//PS:除了checked属性之外,单选按钮还有一个defaultChecked按钮,它获取的是原本
//的checked按钮对象,而不会因为checked的改变而改变。
if(fm.sex[i].defaultChecked==true){
    alert(fm.sex[i].value);
}

//复选按钮
//通过checked属性来获取复选按钮的值。复选按钮也具有defaultChecked属性。
var love='';
for(vari=0;i<fm.love.length;i++){
    if(fm.love[i].checked==true){
    love+=fm.love[i].value;
    }
}
alert(love);
View Code

第26章 错误处理与调试

/*JavaScript在错误处理调试上一直是它的软肋,如果脚本出错,给出的提示经常也让人
摸不着头脑。ECMAScript第3版为了解决这个问题引入了try...catch和throw语句以及一些
错误类型,让开发人员更加适时的处理错误。*/

/*一.浏览器错误报告
随着浏览器的不断升级,JavaScript代码的调试能力也逐渐变强。IE、Firefox、Safari、
Chrome和Opera等浏览器,都具备报告JavaScript错误的机制。只不过,浏览器一般面向
的是普通用户,默认情况下会隐藏此类信息。
IE:在默认情况下,左下角会出现错误报告,双击这个图标,可以看到错误消息对话框。
如果开启禁止脚本调试,那么出错的时候,会弹出错误调试框。设置方法为:工具->Internet
Options选项->高级->禁用脚本调试,取消勾选即可。
Firefox:在默认情况下,错误不会通过浏览器给出提示。但在后台的错误控制台可以查
看。查看方法为:工具->[Web开发者]->Web控制台|错误控制台。除了浏览器自带的,开发
人员为Firefox提供了一个强大的插件:Firebug。它不但可以提示错误,还可以调试JavaScript
和CSS、DOM、网络链接错误等。
Safari:在默认情况下,错误不会通过浏览器给出提示。所以,我们需要开启它。查看
方法为:显示菜单栏->编辑->偏好设置->高级->在菜单栏中显示开发->显示Web检查器|显示
错误控制器。
Opera:在默认情况下,错误会被隐藏起来。打开错误记录的方式为:显示菜单栏->查
看->开发者工具->错误控制台。
Chrome:在默认情况下,错误会被隐藏起来。打开错误记录的方法为:工具->JavaScript
控制台。*/

/*二.错误处理
良好的错误处理机制可以及时的提醒用户,知道发生了什么事,而不会惊慌失措。为此,
作为开发人员,我们必须理解在处理JavaScript错误的时候,都有哪些手段和工具可以利用。
try-catch语句
ECMA262第3版引入了try-catch语句,作为JavaScript中处理异常的一种标准方式。*/

try{ //尝试着执行try包含的代码
    window.abcdefg(); //不存在的方法
}catch(e){ //如果有错误,执行catch,e是异常对象
    alert('发生错误啦,错误信息为:' +e); //直接打印调用toString()方法
}


//在e对象中,ECMA-262还规定了两个属性:message和name,分别打印出信息和名称。
alert('错误名称:' +e.name);
alert('错误名称:' +e.message);

/*PS:Opera9之前的版本不支持这个属性。并且IE提供了和message完全相同的
description属性、还添加了number属性提示内部错误数量。Firefox提供了fileName(文件名)、
lineNumber(错误行号)和stack(栈跟踪信息)。Safari添加了line(行号)、sourceId(内部错误代
码)和sourceURL(内部错误URL)。所以,要跨浏览器使用,那么最好只使用通用的message。
finally子句
finally语句作为try-catch的可选语句,不管是否发生异常处理,都会执行。并且不管try
或是catch里包含return语句,也不会阻止finally执行。*/

try{
    window.abcdefg();
}catch(e){
    alert('发生错误啦,错误信息为:' +e.stack);
}finally{ //总是会被执行
    alert('我都会执行!');
}

/*
PS:finally的作用一般是为了防止出现异常后,无法往下再执行的备用。也就是说,如
果有一些清理操作,那么出现异常后,就执行不到清理操作,那么可以把这些清理操作放到
finally里即可。
错误类型
执行代码时可能会发生的错误有很多种。每种错误都有对应的错误类型,ECMA-262
定义了7种错误类型:
1.Error
2.EvalError
3.RangeError
4.ReferenceError
5.SyntaxError
6.TypeError
7.URIError
其中,Error是基类型(其他六种类型的父类型),其他类型继承自它。Error类型很少见,
一般由浏览器抛出的。这个基类型主要用于开发人员抛出自定义错误。
PS:抛出的意思,就是当前错误无法处理,丢给另外一个人,比如丢给一个错误对象。
*/
new Array(-5); //抛出RangeError(范围)
/*错误信息为:RangeError:invalidarraylength(无效的数组的长度)
PS:RangeError错误一般在数值超出相应范围时触发*/

var box=a; //抛出ReferenceError(引用)
/*错误信息为:ReferenceError:aisnotdefined(a是没有定义的)
PS:ReferenceError通常访问不存在的变量产生这种错误*/

a $ b; //抛出SyntaxError(语法)
//错误信息为:SyntaxError:missing;beforestatement(失踪;语句之前)
//PS:SyntaxError通常是语法错误导致的
new 10; //抛出TypeError(类型)
//错误信息为:TypeError:10isnotaconstructor(10不是一个构造函数)
//PS:TypeError通常是类型不匹配导致的

/*
    PS:EvalError类型表示全局函数eval()的使用方式与定义的不同时抛出,但实际上并不
能产生这个错误,所以实际上碰到的可能性不大。
PS:在使用encodeURI()和decodeURI()时,如果URI格式不正确时,会导致URIError
错误。但因为URI的兼容性非常强,导致这种错误几乎见不到。
*/
alert(encodeURI('李炎恢'));

//利用不同的错误类型,可以更加恰当的给出错误信息或处理。
try{
    new 10;
}catch(e){
if(e instanceof TypeError){ //如果是类型错误,那就执行这里
    alert('发生了类型错误,错误信息为:' +e.message);
    }else{
    alert('发生了未知错误!');
    }
}

/*
善用try-catch
在明明知道某个地方会产生错误,可以通过修改代码来解决的地方,是不适合用
try-catch的。或者是那种不同浏览器兼容性错误导致错误的也不太适合,因为可以通过判断
浏览器或者判断这款浏览器是否存在此属性和方法来解决。
*/
try{
    var box=document.getElementbyid('box'); //单词大小写错误,导致类型错误
}catch(e){ //这种情况没必要try-catch
    alert(e);
}

try{
    alert(innerWidth); //W3C支持,IE报错
}catch(e){
    alert(document.documentElement.clientWidth); //兼容IE
}

/*
PS:常规错误和这种浏览器兼容错误,我们都不建议使用try-catch。因为常规错误可以
修改代码即可解决,浏览器兼容错误,可以通过普通if判断即可。并且try-catch比一般语
句消耗资源更多,负担更大。所以,在万不得已,无法修改代码,不能通过普通判断的情况
下才去使用try-catch,比如后面的Ajax技术。
抛出错误
使用catch来处理错误信息,如果处理不了,我们就把它抛出丢掉。抛出错误,其实就
是在浏览器显示一个错误信息,只不过,错误信息可以自定义,更加精确和具体。
*/

try{
    new 10;
}catch(e){
if(e instanceof TypeError){
    throw new TypeError('实例化的类型导致错误!'); //直接中文解释错误信息
}else{
    throw new Error('抛出未知错误!');
}
}
//PS:IE浏览器只支持Error抛出的错误,其他错误类型不支持。

//三.错误事件
//error事件是当某个DOM对象产生错误的时候触发。
addEvent(window,'error', function(){
    alert('发生错误啦!')
});
new 10; //写在后面
<img src="123.jpg" onerror="alert('图像加载错误!')" />

/*
四.错误处理策略
由于JavaScript错误都可能导致网页无法使用,所以何时搞清楚及为什么发生错误至关
重要。这样,我们才能对此采取正确的应对方案。
常见的错误类型
因为JavaScript是松散弱类型语言,很多错误的产生是在运行期间的。一般来说,需要
关注3种错误:
1.类型转换错误;2.数据类型错误;3.通信错误,这三种错误一般会在特定的模式下或
者没有对值进行充分检查的情况下发生。
类型转换错误
在一些判断比较的时候,比如数组比较,有相等和全等两种:
*/
alert(1=='1'); //true
alert(1==='1'); //false
alert(1==true); //true
alert(1===true); //false

/*PS:由于这个特性,我们建议在这种会类型转换的判断,强烈推荐使用全等,以保证
判断的正确性。*/

var box=10; //可以试试0
if(box){ //10自动转换为布尔值为true
    alert(box);
}

/*PS:因为0会自动转换为false,其实0也是数值,也是有值的,不应该认为是false,
所以我们要判断box是不是数值再去打印。*/

var box=0;
if(typeof box=='number') { //判断box是number类型即可
    alert(box);
}

/*PS:typeofbox=='number'这里也是用的相等,没有用全等呀?原因是typeofbox本身
返回的就是类型的字符串,右边也是字符串,那没必要验证类型,所以相等就够了。
数据类型错误
由于JavaScript是弱类型语言,在使用变量和传递参数之前,不会对它们进行比较来确
保数据类型的正确。所以,这样开发人员必须需要靠自己去检测。*/

function getQueryString(url){ //传递了非字符串,导致错误
    var pos=url.indexOf('?');
    return pos;
}
alert(getQueryString(1));
//PS:为了避免这种错误的出现,我们应该使用类型比较。
function getQueryString(url){
    if(typeofurl=='string') { //判断了指定类型,就不会出错了
    var pos=url.indexOf('?');
    return pos;
    }
}
alert(getQueryString(1));

//对于传递参数除了限制数字、字符串之外,我们对数组也要进行限制。
function sortArray(arr){
if(arr){ //只判断布尔值远远不够
    alert(arr.sort());
    }
}

varbox=[3,5,1];
sortArray(box);

/*
PS:只用if(arr)判断布尔值,那么数值、字符串、对象等都会自动转换为true,而这些
类型调用sort()方法比如会产生错误,这里提一下:空数组会自动转换为true而非false。
*/

function sortArray(arr){
    if(typeof arr.sort=='function') { //判断传递过来arr是否有sort方法
    alert(arr.sort()); //就算这个绕过去了
    alert(arr.reverse()); //这个就又绕不过去了
    }
}
var box={ //创建一个自定义对象,添加sort方法
sort:function(){}
};
sortArray(box);

/*PS:这断代码本意是判断arr是否有sort方法,因为只有数组有sort方法,从而判断arr
是数组。但忘记了,自定义对象添加了sort方法就可以绕过这个判断,且arr还不是数组。*/

function sortArray(arr){
if(arr instanceof Array){ //使用instanceof判断是Array最为合适
    alert(arr.sort());
    }
}
var box=[3,5,1];
sortArray(box);

/*
通信错误
在使用url进行参数传递时,经常会传递一些中文名的参数或URL地址,在后台处理
时会发生转换乱码或错误,因为不同的浏览器对传递的参数解释是不同的,所以有必要使用
编码进行统一传递。
比如:?user=李炎恢&age=100
*/

var url='?user=' +encodeURIComponent('李炎恢') +'&age=100'; //编码

/*
PS:在AJAX章节中我们会继续探讨通信错误和编码问题。
五.调试技术
在JavaScript初期,浏览器并没有针对JavaScript提供调试工具,所以开发人员就想出
了一套自己的调试方法,比如alert()。这个方法可以打印你怀疑的是否得到相应的值,或者
放在程序的某处来看看是否能执行,得知之前的代码无误。
*/

var num1=1;
var num2=b; //在这段前后加上alert('')调试错误
var result=num1+num2;
alert(result);

/*
PS:使用alert('')来调试错误比较麻烦,重要裁剪和粘贴alert(''),如果遗忘掉没有删掉
用于调试的alert('')将特别头疼。所以,我们现在需要更好的调试方法。
将消息记录到控制台
IE8、Firefox、Opera、Chrome和Safari都有JavaScript控制台,可以用来查看JavaScript
错误。对于Firefox,需要安装Firebug,其他浏览器直接使用console对象写入消息即可。
*/

/*
        console    对象的方法
方法名                     说明
error(message)        将错误消息记录到控制台
info(message)         将信息性消息记录到控制台
log(message)         将一般消息记录到控制台
warn(message)         将警告消息记录到控制台
*/

console.error('错误!'); //红色带叉
console.info('信息!'); //白色带信息号
console.log('日志!'); //白色
console.warn('警告!'); //黄色带感叹号

//PS:这里以Firefox为标准,其他浏览器会稍有差异。
var num1=1;
console.log(typeofnum1); //得到num1的类型
var num2='b';
console.log(typeofnum2); //得到num2的类型
var result=num1+num2;
alert(result); //结果是1b,匪夷所思

/*
PS:我们误把num2赋值成字符串了,其实应该是数值,导致最后的结果是1b。那么
传统调试就必须使用alert(typeonum1)来看看是不是数值类型,比较麻烦,因为alert()会阻
断后面的执行,看过之后还要删,删完估计一会儿又忘了,然后又要alert(typeofnum1)来加
深印象。如果用了console.log的话,所有要调试的变量一目了然,也不需要删除,放着也
没事。
将错误抛出
之前已经将结果错误的抛出,这里不在赘述。
*/

if(typeof num2 !='number') thrownewError('变量必须是数值!');

/*
六.调试工具
IE8、Firefox、Chrome、Opera、Safari都自带了自己的调试工具,而开发人员只习惯了
Firefox一种,所以很多情况下,在Firefox开发调试,然后去其他浏览器做兼容。其实Firebug
工具提供了一种Web版的调试工具:Firebuglite。
以下是网页版直接调用调试工具的代码:直接复制到浏览器网址即可。
*/

javascript:(function(F,i,r,e,b,u,g,L,I,T,E){if(F.getElementById(b))return;E=F[i+'NS']&&F.doc
umentElement.namespaceURI;E=E?F[i+'NS'](E,'script'):F[i]('script');E[r]('id',b);E[r]('src',I+g+T);
E[r](b,u);(F[e]('head')[0]||F[e]('body')[0]).appendChild(E);E=new%20Image;E[r]('src',I+L);})(doc
ument,'createElement','setAttribute','getElementsByTagName','FirebugLite','4','firebug-lite.js','rele
ases/lite/latest/skin/xp/sprite.png','https://getfirebug.com/','#startOpened');

/*
还有一种离线版,把firebug-lite下载好,载入工具即可,导致最终工具无法运行,其他
浏览器运行完好。虽然Web版本的FirebugLite可以跨浏览器使用Firebug,但除了Firefox
原生的之外,都不支持断点、单步调试、监视、控制台等功能。好在,其他浏览器自己的调
试器都有。
PS:Chrome浏览器必须在服务器端方可有效。测试也发现,只能简单调试,如果遇到
错误,系统不能自动抛出错误给firebug-lite。
1.设置断点
我们可以选择Script(脚本),点击要设置断点的JS脚本处,即可设置断点。当我们需要
调试的时候,从断点初开始模拟运行,发现代码执行的流程和变化。
2.单步调试
设置完断点后,可以点击单步调试,一步步看代码执行的步骤和流程。上面有五个按钮:
重新运行:重新单步调试
断继:正常执行代码
单步进入:一步一步执行流程
单步跳过:跳到下一个函数块
单步退出:跳出执行到内部的函数
3.监控
单击“监控”选项卡上,可以查看在单步进入是,所有变量值的变化。你也可以新建监
控表达式来重点查看自己所关心的变量。
4.控制台
显示各种信息。之前已了解过。
PS:其他浏览器除IE8以上均可实现以上的调试功能,大家可以自己常识下。而我们
主要采用Firebug进行调试然后兼容到其他浏览器的做法以提高开发效率。
*/
View Code

第27章 Cookie与存储

/*
随着Web越来越复杂,开发者急切的需要能够本地化存储的脚本功能。这个时候,第
一个出现的方案:cookie诞生了。cookie的意图是:在本地的客户端的磁盘上以很小的文件
形式保存数据。
一.Cookie
cookie也叫HTTPCookie,最初是客户端与服务器端进行会话使用的。比如,会员登录,
下次回访网站时无须登录了;或者是购物车,购买的商品没有及时付款,过两天发现购物车
里还有之前的商品列表。
HTTPCookie要求服务器对任意HTTP请求发送Set-Cookie,因此,Cookie的处理原则
上需要在服务器环境下进行。当然,现在大部分浏览器在客户端也能实现Cookie的生成和
获取。(目前Chrome不可以在客户端操作,其他浏览器均可)
cookie的组成
cookie由名/值对形式的文本组成:name=value。完整格式为:
name=value;[expires=date];[path=path];[domain=somewhere.com];[secure]
中括号是可选,name=value是必选。
*/

document.cookie='user=' +encodeURIComponent('小明'); //编码写入
alert(decodeURIComponent(document.cookie)); //解码读取

/*
expires=date 失效时间,如果没有声明,则为浏览器关闭后即失效。声明了失效时间,
那么时间到期后方能失效。
*/

var date=newDate(); //创建一个
date.setDate(date.getDate()+7);
document.cookie="user="+encodeURIComponent('小明') +";expires="+date;

/*
PS:可以通过Firefox浏览器查看和验证失效时间。如果要提前删除cookie也非常简单,
只要重新创建cookie把时间设置当前时间之前即可:date.getDate()-1或newDate(0)。
path=path访问路径,当设置了路径,那么只有设置的那个路径文件才可以访问cookie。
*/

var path='/E:/%E5%A4%87%E8%AF%BE%E7%AC%94%E8%AE%B0/JS1/29/demo';
document.cookie="user="+encodeURIComponent('小明') +";path="+path;

/*
PS:为了操作方便,我直接把路径复制下来,并且增加了一个目录以强调效果。
domain=domain访问域名,用于限制只有设置的域名才可以访问,那么没有设置,会
默认限制为创建cookie的域名。
*/

var domain='yc60.com';
document.cookie="user="+encodeURIComponent('小明') +";domain="+domain;

/*
PS:如果定义了yc60.com,那么在这个域名下的任何网页都可访问,如果定义了
v.yc60.com,那么只能在这个二级域名访问该cookie,而主域名和其他子域名则不能访问。
PS:设置域名,必须在当前域名绑定的服务器上设置,如果在yc60.com服务器上随意
设置其他域名,则会无法创建cookie。
secure 安全设置,指明必须通过安全的通信通道来传输(HTTPS)才能获取cookie。
*/

document.cookie="user="+encodeURIComponent('小明') +";secure";

/*PS:https安全通信链接需要单独配置。
JavaScript设置、读取和删除并不是特别的直观方便,我们可以封装成函数来方便调用。*/

//创建cookie
function setCookie(name,value,expires,path,domain,secure){
    var cookieText=encodeURIComponent(name)+'=' +encodeURIComponent(value);
    if(expiresinstanceofDate) {
    cookieText+='; expires='+expires;
    }
    if(path){
    cookieText+='; expires='+expires;
    }
    if(domain){
    cookieText+='; domain='+domain;
    }
    if(secure){
    cookieText+='; secure';
    }
    document.cookie=cookieText;
}

//获取cookie
function getCookie(name){
var cookieName=encodeURIComponent(name)+'=';
var cookieStart=document.cookie.indexOf(cookieName);
var cookieValue=null;
if(cookieStart>-1){
    var cookieEnd=document.cookie.indexOf(';',cookieStart);
    if(cookieEnd==-1){
    cookieEnd=document.cookie.length;
    }
    cookieValue=decodeURIComponent(
    document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
    }
    return cookieValue;
}

//删除cookie
function unsetCookie(name){
    document.cookie=name+"=;expires="+newDate(0);
}
//失效天数,直接传一个天数即可
function setCookieDate(day){
    if(typeof day=='number' &&day>0){
    var date = new Date();
    date.setDate(date.getDate()+day);
    }else{
    throw new Error('传递的day必须是一个天数,必须比0大');
    }
    return date;
}

/*
二.cookie局限性
cookie虽然在持久保存客户端用户数据提供了方便,分担了服务器存储的负担。但是还
有很多局限性的。
第一:每个特定的域名下最多生成20个cookie(根据不同的浏览器有所区别)。
1.IE6或更低版本最多20个cookie
2.IE7和之后的版本最多可以50个cookie。IE7最初也只能20个,之后因被升级不定后
增加了。
3.Firefox最多50个cookie
4.Opera最多30个cookie
5.Safari和Chrome没有做硬性限制。
PS:为了更好的兼容性,所以按照最低的要求来,也就是最多不得超过20个cookie。
当超过指定的cookie时,浏览器会清理掉早期的cookie。IE和Opera会清理近期最少使用
的cookie,Firefox会随机清理cookie。
第二:cookie的最大大约为4096字节(4k),为了更好的兼容性,一般不能超过4095字
节即可。
第三:cookie存储在客户端的文本文件,所以特别重要和敏感的数据是不建议保存在
cookie的。比如银行卡号,用户密码等。
三.其他存储
IE提供了一种存储可以持久化用户数据,叫做userData,从IE5.0就开始支持。每个数
据最多128K,每个域名下最多1M。这个持久化数据存放在缓存中,如果缓存没有清理,
那么会一直存在。
*/
<div style="behavior:url(#default#userData)" id="box"></div>
addEvent(window,'load', function(){
    var box=document.getElementById('box');
    box.setAttribute('name',encodeURIComponent('小明'));
    box.expires=setCookieDate(7);
    box.save('bookinfo');
    //box.removeAttribute('name'); //删除userDate
    //box.save('bookinfo');
    box.load('bookinfo');
    alert(decodeURIComponent(box.getAttribute('name')));
});

/*
PS:这个数据文件也是保存在cookie目录中,只要清除cookie即可。如果指定过期日
期,则到期后自动删除,如果没有指定就是永久保存。
Web存储
在比较高版本的浏览器,JavaScript提供了sessionStorage和globalStorage。在HTML5
中提供了localStorage来取代globalStorage。而浏览器最低版本为:IE8+、Firefox3.5+、Chrome
4+和Opera10.5+。
PS:由于这三个对浏览器版本要求较高,我们就只简单的在Firefox了解一下,有兴趣
的可以通过关键字搜索查询。
*/

//通过方法存储和获取
sessionStorage.setItem('name','小明');
alert(sessionStorage.getItem('name'));
//通过属性存储和获取
sessionStorage.book='小明';
alert(sessionStorage.book);
//删除存储
sessionStorage.removeItem('name');

/*PS:由于localStorage代替了globalStorage,所以在Firefox、Opera和Chrome目前的最
新版本已不支持。*/

//通过方法存储和获取
localStorage.setItem('name','小明');
alert(localStorage.getItem('name'));
//通过属性存储和获取
localStorage.book='小明';
alert(localStorage.book);
//删除存储
localStorage.removeItem('name');

/*PS:这三个对象都是永久保存的,保存在缓存里,只有手工删除或者清理浏览器缓存
方可失效。在容量上也有一些限制,主要看浏览器的差异,Firefox3+、IE8+、Opera为5M,
Chrome和Safari为2.5M。*/
View Code

第28章 XML

/*
随着互联网的发展,Web应用程序的丰富,开发人员越来越希望能够使用客户端来操
作XML技术。而XML技术一度成为存储和传输结构化数据的标准。所以,本章就详细探
讨一下JavaScript中使用XML的技术。
对于什么是XML,干什么用的,这里就不在赘述了,在以往的XHTML或PHP课程都
有涉及到,可以理解成一个微型的结构化的数据库,保存一些小型数据用的。
一.IE中的XML
在统一的正式规范出来以前,浏览器对于XML的解决方案各不相同。DOM2级提出了
动态创建XMLDOM规范,DOM3进一步增强了XMLDOM。所以,在不同的浏览器实现
XML的处理是一件比较麻烦的事情。
1.创建XMLDOM对象
IE浏览器是第一个原生支持XML的浏览器,而它是通过ActiveX对象实现的。这个对
象,只有IE有,一般是IE9之前采用。微软当年为了开发人员方便的处理XML,创建了
MSXML库,但却没有让Web开发人员通过浏览器访问相同的对象。
*/


var xmlDom = new ActiveXObject('MSXML2.DOMDocument');


/*
        ActiveXObject类型
XML版本字符串                             说明
Microsoft.XmlDom             最初随同IE发布,不建议使用
MSXML2.DOMDocument             脚本处理而更新的版本,仅在特殊情况作为备份用
MSXML2.DOMDocument.3.0         在JavaScript中使用,这是最低的建议版本
MSXML2.DOMDocument.4.0         脚本处理时并不可靠,使用这个版本导致安全警告
MSXML2.DOMDocument.5.0         脚本处理时并不可靠,使用这个版本导致安全警告
MSXML2.DOMDocument.6.0         脚本能够可靠处理的最新版本

*/

/*
PS:在这六个版本中微软只推荐三种:
1.MSXML2.DOMDocument.6.0最可靠最新的版本
2.MSXML2.DOMDocument.3.0兼容性较好的版本
3.MSXML2.DOMDocument 仅针对IE5.5之前的版本
*/

/*PS:这三个版本在不同的windows平台和浏览器下会有不同的支持,那么为了实现兼
容,我们应该考虑这样操作:从6.0->3.0->备用版本这条路线进行实现。*/

function createXMLDOM(){
var version=[
    'MSXML2.DOMDocument.6.0',
    'MSXML2.DOMDocument.3.0',
    'MSXML2.DOMDocument'
    ];
    for(vari=0;i<version.length;i++){
        try{
        varxmlDom=newActiveXObject(version[i]);
        returnxmlDom;
        }catch(e){
        //跳过
        }
    }
    throw new Error('您的系统或浏览器不支持MSXML!'); //循环后抛出错误
}

/*2.载入XML
如果已经获取了XMLDOM对象,那么可以使用loadXML()和load()这两个方法可以分
别载入XML字符串或XML文件。*/

xmlDom.loadXML('<rootversion="1.0"><user>Lee</user></root>');
alert(xmlDom.xml);

/*PS:loadXML参数直接就是XML字符串,如果想效果更好,可以添加换行符
。.xml
属性可以序列化XML,获取整个XML字符串。*/

xmlDom.load('test.xml'); //载入一个XML文件
alert(xmlDom.xml);

/*当你已经可以加载了XML,那么你就可以用之前学习的DOM来获取XML数据,比如
标签内的某个文本。*/

var user=xmlDom.getElementsByTagName('user')[0]; //获取<user>节点
alert(user.tagName); //获取<user>元素标签
alert(user.firstChild.nodeValue); //获取<user>里的值Lee

//DOM不单单可以获取XML节点,也可以创建。
var email=xmlDom.createElement('email');
xmlDom.documentElement.appendChild(email);

/*3.同步及异步
load()方法是用于服务器端载入XML的,并且限制在同一台服务器上的XML文件。那
么在载入的时候有两种模式:同步和异步。
所谓同步:就是在加载XML完成之前,代码不会继续执行,直到完全加载了XML再
返回。好处就是简单方便、坏处就是如果加载的数据停止响应或延迟太久,浏览器会一直堵
塞从而造成假死状态。*/

xmlDom.async=false; //设置同步,false,可以用PHP测试假死

/*所谓异步:就是在加载XML时,JavaScript会把任务丢给浏览器内部后台去处理,不
会造成堵塞,但要配合readystatechange事件使用,所以,通常我们都使用异步方式。*/

xmlDom.async=true; //设置异步,默认

/*通过异步加载,我们发现获取不到XML的信息。原因是,它并没有完全加载XML就
返回了,也就是说,在浏览器内部加载一点,返回一点,加载一点,返回一点。这个时候,
我们需要判断是否完全加载,并且可以使用了,再进行获取输出。*/

/*
XMLDOM中readystatechange事件
就绪状态             说明
1                 DOM正在加载
2                 DOM已经加载完数据
3                 DOM已经可以使用,但某些部分还无法访问
4                 DOM已经完全可以
PS:readyState可以获取就绪状态值

*/

var xmlDom=createXMLDOM();
    xmlDom.async=true; //异步,可以不写
xmlDom.onreadystatechange=function(){
    if(xmlDom.readyState==4){ //完全加载了,再去获取XML
    alert(xmlDom.xml);
    }
}
xmlDom.load('test.xml'); //放在后面重点体现异步的作用

/*PS:可以通过readyState来了解事件的执行次数,将load()方法放到最后不会因为代码
的顺序而导致没有加载。并且load()方法必须放在onreadystatechange之后,才能保证就绪状
态变化时调用该事件处理程序,因为要先触发。用PHP来测试,在浏览器内部执行时,是
否能操作,是否会假死。
PS:不能够使用this,不能够用IE的事件处理函数,原因是ActiveX控件为了预防安
全性问题。
PS:虽然可以通过XMLDOM文档加载XML文件,但公认的还是XMLHttpRequest
对象比较好。这方面内容,我们在Ajax章节详细了解。
4.解析错误
在加载XML时,无论使用loadXML()或load()方法,都有可能遇到XML格式不正确的
情况。为了解决这个问题,微软的XMLDOM提供了parseError属性。*/

/*
        parseError属性对象
属性                     说明
errorCode         发生的错误类型的数字代号
filepos         发生错误文件中的位置
line             错误行号
linepos         遇到错误行号那一行上的字符的位置
reason             错误的解释信息

*/

if(xmlDom.parseError==0){
    alert(xmlDom.xml);
    }else{
    throw new Error('错误行号:' +xmlDom.parseError.line+
        '
 错误代号:' +xmlDom.parseError.errorCode+
        '
 错误解释:' +xmlDom.parseError.reason);
}

/*二.DOM2中的XML
IE可以实现了对XML字符串或XML文件的读取,其他浏览器也各自实现了对XML
处理功能。DOM2级在document.implementaion中引入了createDocument()方法。IE9、Firefox、
Opera、Chrome和Safari都支持这个方法。*/

//1.创建XMLDOM对象
var xmlDom=document.implementation.createDocument('','root',null); //创建xmlDom
var user=xmlDom.createElement('user'); //创建user元素
xmlDom.getElementsByTagName('root')[0].appendChild(user); //添加到root下
var value=xmlDom.createTextNode('Lee'); //创建文本
xmlDom.getElementsByTagName('user')[0].appendChild(value); //添加到user下
alert(xmlDom.getElementsByTagName('root')[0].tagName);
alert(xmlDom.getElementsByTagName('user')[0].tagName);
alert(xmlDom.getElementsByTagName('user')[0].firstChild.nodeValue);

/*PS:由于DOM2中不支持loadXML()方法,所以,无法简易的直接创建XML字符串。
所以,只能采用以上的做法。
PS:createDocument()方法需要传递三个参数,命名空间,根标签名和文档声明,由于
JavaScript管理命名空间比较困难,所以留空即可。文档声明一般根本用不到,直接null即
可。命名空间和文档声明留空,表示创建XMLDOM对象不需要命名空间和文档声明。
PS:命名空间的用途是防止太多的重名而进行的分类,文档类型表明此文档符合哪种
规范,而这里创建XMLDOM不需要使用这两个参数,所以留空即可。
2.载入XML
DOM2只支持load()方法,载入一个同一台服务器的外部XML文件。当然,DOM2也
有async属性,来表面同步或异步,默认异步。*/

//同步情况下
var xmlDom=document.implementation.createDocument('','root',null);
xmlDom.async=false;
xmlDom.load('test.xml');
alert(xmlDom.getElementsByTagName('user')[0].tagName);
//异步情况下
var xmlDom=document.implementation.createDocument('','root',null);
xmlDom.async=true;
addEvent(xmlDom,'load', function(){ //异步直接用onload即可
    alert(this.getElementsByTagName('user')[0].tagName);
});
xmlDom.load('test.xml');

/*PS:不管在同步或异步来获取load()方法只有Mozilla的Firefox才能支持,只不过新版
的Opera也是支持的,其他浏览器则不支持。
3.DOMParser类型
由于DOM2没有loadXML()方法直接解析XML字符串,所以提供了DOMParser类型
来创建XMLDOM对象。IE9、Safari、Chrome和Opera都支持这个类型。*/

var xmlParser=newDOMParser(); //创建DOMParser对象
var xmlStr='<user>Lee</user></root>'; //XML字符串
var xmlDom=xmlParser.parseFromString(xmlStr,'text/xml'); //创建XMLDOM对象
alert(xmlDom.getElementsByTagName('user')[0].tagName); //获取user元素标签名

/*PS:XMLDOM对象是通过DOMParser对象中的parseFromString方法来创建的,两个
参数:XML字符串和内容类型text/xml。
4.XMLSerializer类型
由于DOM2没有序列化XML的属性,所以提供了XMLSerializer类型来帮助序列化
XML字符串。IE9、Safari、Chrome和Opera都支持这个类型。*/

var serializer=newXMLSerializer(); //创建XMLSerializer对象
var xml=serializer.serializeToString(xmlDom); //序列化XML
alert(xml);

/*5.解析错误
在DOM2级处理XML发生错误时,并没有提供特有的对象来捕获错误,而是直接生
成另一个错误的XML文档,通过这个文档可以获取错误信息。*/

var errors=xmlDom.getElementsByTagName('parsererror');
if(errors.length>0){
    throw new Error('XML格式有误:' +errors[0].textContent);
}

//PS:errors[0].firstChild.nodeValue也可以使用errors[0].textContent来代替。

/*三.跨浏览器处理XML
如果要实现跨浏览器就要思考几个个问题:1.load()只有IE、Firefox、Opera支持,所以
无法跨浏览器;2.获取XMLDOM对象顺序问题,先判断先进的DOM2的,然后再去判断
落后的IE;3.针对不同的IE和DOM2级要使用不同的序列化。4.针对不同的报错进行不同
的报错机制。*/

//首先,我们需要跨浏览器获取XMLDOM
function getXMLDOM(xmlStr){
    var xmlDom=null;
    if(typeof window.DOMParser!='undefined') { //W3C
    xmlDom=(new DOMParser()).parseFromString(xmlStr,'text/xml');
    var errors=xmlDom.getElementsByTagName('parsererror');
    if(errors.length>0){
    throw new Error('XML解析错误:' +errors[0].firstChild.nodeValue);
    }
    }elseif(typeof window.ActiveXObject!='undefined') { //IE
    varversion=[
    'MSXML2.DOMDocument.6.0',
    'MSXML2.DOMDocument.3.0',
    'MSXML2.DOMDocument'
    ];
    for(var i=0;i<version.length;i++){
    try{
    xmlDom = new ActiveXObject(version[i]);
    }catch(e){
    //跳过
    }
}
xmlDom.loadXML(xmlStr);
if(xmlDom.parseError!=0){
    throw new Error('XML解析错误:' +xmlDom.parseError.reason);
    }
    }else{
    throw new Error('您所使用的系统或浏览器不支持XMLDOM!');
    }
    return xmlDom;
}

//其次,我们还必须跨浏览器序列化XML
function serializeXML(xmlDom){
    var xml='';
    if(typeof XMLSerializer!='undefined') {
    xml=(new XMLSerializer()).serializeToString(xmlDom);
    }else if(typeof xmlDom.xml!='undefined') {
        xml=xmlDom.xml;
    }else{
    throw new Error('无法解析XML!');
    }
    return xml;
}
//PS:由于兼容性序列化过程有一定的差异,可能返回的结果字符串可能会有一些不同。
//至于load()加载XML文件则因为只有部分浏览器支持而无法跨浏览器。
View Code

第29章 XPath

/*XPath是一种节点查找手段,对比之前使用标准DOM去查找XML中的节点方式,大
大降低了查找难度,方便开发者使用。但是,DOM3级以前的标准并没有就XPath做出规
范;直到DOM3在首次推荐到标准规范行列。大部分浏览器实现了这个标准,IE则以自己
的方式实现了XPath。*/

/*一.IE中的XPath
在IE8及之前的浏览器,XPath是采用内置基于ActiveX的XMLDOM文档对象实现的。
在每一个节点上提供了两个方法:selectSingleNode()和selectNodes()。
selectSingleNode()方法接受一个XPath模式(也就是查找路径),找到匹配的第一个节
点并将它返回,没有则返回null。*/

var user=xmlDom.selectSingleNode('root/user'); //得到第一个user节点
alert(user.xml); //查看xml序列
alert(user.tagName); //节点元素名
alert(user.firstChild.nodeValue); //节点内的值

/*上下文节点:我们通过xmlDom这个对象实例调用方法,而xmlDom这个对象实例其
实就是一个上下文节点,这个节点指针指向的是根,也就是root元素之前。那么如果我们
把这个指针指向user元素之前,那么结果就会有所变化。*/

//通过xmlDom,并且使用root/user的路径
var user=xmlDom.selectSingleNode('root/user');
alert(user.tagName); //user
//通过xmlDom.documentElement,并且使用user路径,省去了root
var user=xmlDom.documentElement.selectSingleNode('user');
alert(user.tagName); //user
//通过xmlDom,并且使用user路径,省去了root
var user=xmlDom.selectSingleNode('user');
alert(user.tagName); //找不到了,出错

/*PS:xmlDom和xmlDom.documentElement都是上下文节点,主要就是定位当前路径查
找的指针,而xmlDom对象实例的指针就是在最根上。*/

//XPath XPath常用语法
//通过user[n]来获取第n+1条节点,PS:XPath其实是按1为起始值的
var user=xmlDom.selectSingleNode('root/user[1]');
alert(user.xml);
//通过text()获取节点内的值
var user=xmlDom.selectSingleNode('root/user/text()');
alert(user.xml);
alert(user.nodeValue);
//通过//user表示在整个xml获取到user节点,不关心任何层次
var user=xmlDom.selectSingleNode('//user');
alert(user.xml);
//通过root//user表示在root包含的层次下获取到user节点,在root内不关心任何层次
var user=xmlDom.selectSingleNode('root//user');
alert(user.tagName);
//通过root/user[@id=6]表示获取user中id=6的节点
var user=xmlDom.selectSingleNode('root/user[@id=6]');
alert(user.xml);
/*PS:更多的XPath语法,可以参考XPath手册或者XMLDOM手册进行参考,这里只
提供了最常用的语法。
selectSingleNode()方法是获取单一节点,而selectNodes()方法则是获取一个节点集合。*/

var users=xmlDom.selectNodes('root/user'); //获取user节点集合
alert(users.length);
alert(users[1].xml);

/*二.W3C下的XPath
在DOM3级XPath规范定义的类型中,最重要的两个类型是XPathEvaluator和
XPathResult。其中,XPathEvaluator用于在特定上下文对XPath表达式求值。*/

/*
XPathEvaluator的方法
方法                                     说明
createExpression(e,n)         将XPath表达式及命名空间转化成XPathExpression
createNSResolver(n)         根据n命名空间创建一个新的XPathNSResolver对象
evaluate(e,c,n,t,r)         结合上下文来获取XPath表达式的值

*/

/*W3C实现XPath查询节点比IE来的复杂,首先第一步就是需要得到XPathResult对象
的实例。得到这个对象实例有两种方法,一种是通过创建XPathEvaluator对象执行evaluate()
方法,另一种是直接通过上下文节点对象(比如xmlDom)来执行evaluate()方法。*/

//使用XPathEvaluator对象创建XPathResult
var eva=newXPathEvaluator();
var result=eva.evaluate('root/user',xmlDom,null,
                                    XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
alert(result);
//使用上下文节点对象(xmlDom)创建XPathResult
var result=xmlDom.evaluate('root/user',xmlDom,null,
                                        XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
alert(result);

/*相对而言,第二种简单方便一点,但evaluate方法有五个属性:1.XPath路径、2.上下
文节点对象、3.命名空间求解器(通常是null)、4.返回结果类型、5保存结果的XPathResult
对象(通常是null)。*/

/*对于返回的结果类型,有10中不同的类型
常量                                                 说明
XPathResult.ANY_TYPE                         返回符合XPath表达式类型的数据
XPathResult.ANY_UNORDERED_NODE_TYPE           返回匹配节点的节点集合,但顺序可能与文档中的节点的顺序不匹配
XPathResult.BOOLEAN_TYPE                     返回布尔值
XPathResult.FIRST_ORDERED_NODE_TYPE         返回只包含一个节点的节点集合,且这个节点是在文档中第一个匹配的节点
XPathResult.NUMBER_TYPE                     返回数字值
XPathResult.ORDERED_NODE_ITERATOR_TYPE      返回匹配节点的节点集合,顺序为节点在文档中出现的顺序。这是最常用到的结果类型
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE      返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表
XPathResult.STRING_TYPE                     返回字符串值
XPathResult.UNORDERED_NODE_ITERATOR_TYPE    返回匹配节点的节点集合,不过顺序可能不会按照节点在文档中出现的顺序排列
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE    返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表*/

/*PS:上面的常量过于繁重,对于我们只需要学习了解,其实也就需要两个:1.获取一个
单一节、2.获取一个节点集合。*/

//1.获取一个单一节点

var result=xmlDom.evaluate('root/user',xmlDom,null,
                            XPathResult.FIRST_ORDERED_NODE_TYPE,null);
if(result!==null){
            alert(result.singleNodeValue.tagName); //singleNodeValue属性得到节点对象
}
//2.获取节点集合
var result = xmlDom.evaluate('root/user',xmlDom,null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
var nodes=[];
if(result!==null){
    while((node=result.iterateNext())!==null){
        nodes.push(node);
    }
}

/*PS:节点集合的获取方式,是通过迭代器遍历而来的,我们保存到数据中就模拟出IE
相似的风格。*/

/*三.XPath跨浏览器兼容
如果要做W3C和IE的跨浏览器兼容,我们要思考几个问题:1.如果传递一个节点的下
标,IE是从0开始计算,W3C从1开始计算,可以通过传递获取下标进行增1减1的操作
来进行。2.独有的功能放弃,为了保证跨浏览器。3.只获取单一节点和节点列表即可,基本
可以完成所有的操作。*/

//跨浏览器获取单一节点
function selectSingleNode(xmlDom,xpath){
var node=null;
    if(typeof xmlDom.evaluate!='undefined') {
        var patten=/[(d+)]/g;
        var flag=xpath.match(patten);
        var num=0;
        if(flag!==null){
        num=parseInt(RegExp.$1)+1;
        xpath=xpath.replace(patten,'[' +num+']');
        }
        var result=xmlDom.evaluate(xpath,xmlDom,null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,null);
        if(result!==null){
            node=result.singleNodeValue;
        }
        }else if(typeofxmlDom.selectSingleNode!='undefined') {
        node=xmlDom.selectSingleNode(xpath);
    }
    return node;
}


//跨浏览器获取节点集合
function selectNodes(xmlDom,xpath){
var nodes=[];
if(typeof xmlDom.evaluate!='undefined') {
    var patten=/[(d+)]/g;
    var flag=xpath.match(patten);
    var num=0;
    if(flag!==null){
    num=parseInt(RegExp.$1)+1;
    xpath=xpath.replace(patten,'[' +num+']');
    }
    var node=null;
    var result=xmlDom.evaluate('root/user',xmlDom,null,
    XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
    if(result!==null){
    while((node=result.iterateNext())!==null){
    nodes.push(node);
    }
    }
    }elseif(typeof xmlDom.selectNodes!='undefined') {
    nodes=xmlDom.selectNodes(xpath);
    }
    return nodes;
}

/*PS:在传递xpath路径时,没有做验证判断是否合法,有兴趣的同学可以自行完成。在
XML还有一个重要章节是XSLT和EX4,由于在使用频率的缘故,我们暂且搁置。*/
View Code

第30章 JSON

/*前两章我们探讨了XML的结构化数据,但开发人员还是觉得这种微型的数据结构还是
过于烦琐、冗长。为了解决这个问题,JSON的结构化数据出现了。JSON是JavaScript的一
个严格的子集,利用JavaScript中的一些模式来表示结构化数据。
一.JSON语法
JSON和XML类型,都是一种结构化的数据表示方式。所以,JSON并不是JavaScript
独有的数据格式,其他很多语言都可以对JSON进行解析和序列化。
JSON的语法可以表示三种类型的值:
1.简单值:可以在JSON中表示字符串、数值、布尔值和null。但JSON不支持JavaScript
中的特殊值undefined。
2.对象:顾名思义。
3.数组:顾名思义。
简单值
100、"Lee" 这两个量就是JSON的表示方法,一个是JSON数值,一个是JSON字符串。
布尔值和null也是有效的形式。但实际运用中要结合对象或数组。*/

//对象
//JavaScript对象字面量表示法:
var box={
    name:'Lee',
    age:100
};
//而JSON中的对象表示法需要加上双引号,并且不存在赋值运算和分号:
{
"name":"Lee", //使用双引号,否则转换会出错
"age":100
}
//数组
//JavaScript数组字面量表示法:
var box=[100,'Lee', true];
//而JSON中的数组表示法同样没有变量赋值和分号:
[100,"Lee",true]

//一般比较常用的一种复杂形式是数组结合对象的形式:

[
    {
        "title":"a",
        "num":1
    },
    {
        "title":"b",
        "num":2
    },
    {
        "title":"c",
        "num":3
    }
]

/*PS:一般情况下,我们可以把JSON结构数据保存到一个文本文件里,然后通过
XMLHttpRequest对象去加载它,得到这串结构数据字符串(XMLHttpRequest对象将在Aajx
章节中详细探讨)。所以,我们可以模拟这种过程。
模拟加载JSON文本文件的数据,并且赋值给变量。*/

var box='[{"name" :"a","age":1},{"name":"b","age":2}]';

/*PS;上面这短代码模拟了varbox=load('demo.json');赋值过程。因为通过load加载的文
本文件,不管内容是什么,都必须是字符串。所以两边要加上双引号。
其实JSON就是比普通数组多了两边的双引号,普通数组如下:*/

var box=[{name:'a', age:1},{name:'b', age:2}];

/*二.解析和序列化
如果是载入的JSON文件,我们需要对其进行使用,那么就必须对JSON字符串解析成
原生的JavaScript值。当然,如果是原生的JavaScript对象或数组,也可以转换成JSON字
符串。
对于讲JSON字符串解析为JavaScript原生值,早期采用的是eval()函数。但这种方法
既不安全,可能会执行一些恶意代码。*/

var box='[{"name" :"a","age":1},{"name":"b","age":2}]';
alert(box); //JSON字符串
var json=eval(box); //使用eval()函数解析
alert(json); //得到JavaScript原生值

/*ECMAScript5对解析JSON的行为进行规范,定义了全局对象JSON。支持这个对象的
浏览器有IE8+、Firefox3.5+、Safari4+、Chrome和Opera10.5+。不支持的浏览器也可以通过
一个开源库json.js来模拟执行。JSON对象提供了两个方法,一个是将原生JavaScript值转
换为JSON字符串:stringify();另一个是将JSON字符串转换为JavaScript原生值:parse()。*/

var box='[{"name" :"a","age":1},{"name":"b","age":2}]'; //特别注意,键要用双引号
    alert(box);
var json = JSON.parse(box); //不是双引号,会报错
alert(json);
var box=[{name:'a', age:1},{name:'b', age:2}]; //JavaScript原生值
var json=JSON.stringify(box); //转换成JSON字符串
alert(json); //自动双引号

/*在序列化JSON的过程中,stringify()方法还提供了第二个参数。第一个参数可以是一个
数组,也可以是一个函数,用于过滤结果。第二个参数则表示是否在JSON字符串中保留缩
进。*/

var box=[{name:'a', age:1,height:177},{name:'b', age:2,height:188}];
var json=JSON.stringify(box,['name','age'], 4);
alert(json);

/*PS:如果不需要保留缩进,则不填即可;如果不需要过滤结果,但又要保留缩进,则
讲过滤结果的参数设置为null。如果采用函数,可以进行复杂的过滤。*/

var box=[{name:'a', age:1,height:177},{name:'b', age:2,height:188}];
var json=JSON.stringify(box,function(key,value){
    switch(key){
    case'name' :
    return'Mr. ' +value;
    case'age' :
    returnvalue+'岁';
    default:
    return value;
}
},4);
alert(json);

/*PS:保留缩进除了是普通的数字,也可以是字符。
还有一种方法可以自定义过滤一些数据,使用toJSON()方法,可以将某一组对象里指
定返回某个值。*/


var box=[{name:'a', age:1,height:177,toJSON:function(){
    return this.name;
}},{name:'b',age :2,height:188,toJSON:function(){
    return this.name;
}}];

var json=JSON.stringify(box);
alert(json);


/*PS:由此可见序列化也有执行顺序,首先先执行toJSON()方法;如果应用了第二个过
滤参数,则执行这个方法;然后执行序列化过程,比如将键值对组成合法的JSON字符串,
比如加上双引号。如果提供了缩进,再执行缩进操作。
解析JSON字符串方法parse()也可以接受第二个参数,这样可以在还原出JavaScript值
的时候替换成自己想要的值。*/

var box='[{"name" :"a","age":1},{"name":"b","age":2}]';
var json=JSON.parse(box,function(key,value){
if(key=='name') {
    return 'Mr. ' +value;
}else{
    return value;
}
});
alert(json[0].name);
View Code

第31章 Ajax

/*2005年JesseJamesGarrett发表了一篇文章,标题为:“Ajax:AnewApproachtoWeb
Applications”。他在这篇文章里介绍了一种技术,用他的话说,就叫:Ajax,是Asynchronous
JavaScript+XML的简写。这种技术能够想服务器请求额外的数据而无须卸载页面(即刷新),
会带来更好的用户体验。一时间,席卷全球。*/

/*一.XMLHttpRequest
Ajax技术核心是XMLHttpRequest对象(简称XHR),这是由微软首先引入的一个特性,
其他浏览器提供商后来都提供了相同的实现。在XHR出现之前,Ajax式的通信必须借助一
些hack手段来实现,大多数是使用隐藏的框架或内嵌框架。
XHR的出现,提供了向服务器发送请求和解析服务器响应提供了流畅的接口。能够以
异步方式从服务器获取更多的信息,这就意味着,用户只要触发某一事件,在不刷新网页的
情况下,更新服务器最新的数据。
虽然Ajax中的x代表的是XML,但Ajax通信和数据格式无关,也就是说这种技术不
一定使用XML。
IE7+、Firefox、Opera、Chrome和Safari都支持原生的XHR对象,在这些浏览器中创
建XHR对象可以直接实例化XMLHttpRequest即可。*/

var xhr=newXMLHttpRequest();
alert(xhr); //XMLHttpRequest

/*如果是IE6及以下,那么我们必须还需要使用ActiveX对象通过MSXML库来实现。在
低版本IE浏览器可能会遇到三种不同版本的XHR对象,即MSXML2.XMLHttp、
MSXML2.XMLHttp.3.0、MSXML2.XMLHttp.6.0。我们可以编写一个函数。*/

function createXHR(){
    if(typeof XMLHttpRequest!='undefined') {
    return newXMLHttpRequest();
    }else if (typeofActiveXObject!='undefined') {
    var versions=[
        'MSXML2.XMLHttp.6.0',
        'MSXML2.XMLHttp.3.0',
        'MSXML2.XMLHttp'
];
for(var i=0;i<versions.length;i++){
try{
return new ActiveXObject(version[i]);
}catch(e){
//跳过
}
}
}else{
throw new Error('您的浏览器不支持XHR对象!');
}
}


var xhr = new createXHR();

/*在使用XHR对象时,先必须调用open()方法,它接受三个参数:要发送的请求类型(get、
post)、请求的URL和表示是否异步。*/

xhr.open('get','demo.php', false); //对于demo.php的get请求,false同步

//PS:demo.php的代码如下:
<?php echo Date('Y-m-dH:i:s')?> //一个时间

/*open()方法并不会真正发送请求,而只是启动一个请求以备发送。通过send()方法进行
发送请求,send()方法接受一个参数,作为请求主体发送的数据。如果不需要则,必须填null。
执行send()方法之后,请求就会发送到服务器上。*/

xhr.send(null); //发送请求

/*当请求发送到服务器端,收到响应后,响应的数据会自动填充XHR对象的属性。那么
一共有四个属性:*/

/*属性名                         说明
responseText             作为响应主体被返回的文本
responseXML                如果响应主体内容类型是"text/xml"或"application/xml",则返回包含响应数据的XMLDOM文档
status                     响应的HTTP状态
statusText                 HTTP状态的说明
*/
/*接受响应之后,第一步检查status属性,以确定响应已经成功返回。一般而已HTTP状
态代码为200作为成功的标志。除了成功的状态代码,还有一些别的:*/

/*
HTTP状态码                 状态字符串                         说明
200                         OK                     服务器成功返回了页面
400                         BadRequest             语法错误导致服务器不识别
401                         Unauthorized         请求需要用户认证
404                         Notfound             指定的URL在服务器上找不到
500                     InternalServerError     服务器遇到意外错误,无法完成请求
503                     ServiceUnavailable         由于服务器过载或维护导致无法完成请求
*/
/*
我们判断HTTP状态值即可,不建议使用HTTP状态说明,因为在跨浏览器的时候,可
能会不太一致。
*/
addEvent(document,'click', function(){
    var xhr=newcreateXHR();
    xhr.open('get','demo.php?rand=' +Math.random(),false); //设置了同步
    xhr.send(null);
    if(xhr.status==200){ //如果返回成功了
    alert(xhr.responseText); //调出服务器返回的数据
    }else{
    alert('数据返回失败!状态代码:' +xhr.status+'状态信息:' +xhr.statusText);
    }
});

/*以上的代码每次点击页面的时候,返回的时间都是时时的,不同的,说明都是通过服务
器及时加载回的数据。那么我们也可以测试一下在非Ajax情况下的情况,创建一个
demo2.php文件,使用非Ajax。*/

<script type="text/javascript"src="base.js"></script>
<script type="text/javascript">addEvent(document,'click', function(){
    alert("<?phpechoDate('Y-m-dH:i:s')?>");
});
</script>

/*
同步调用固然简单,但使用异步调用才是我们真正常用的手段。使用异步调用的时候,
需要触发readystatechange事件,然后检测readyState属性即可。这个属性有五个值:
*/

/*
值             状态                  说明
0             未初始化         尚未调用open()方法
1             启动             已经调用open()方法,但尚未调用send()方法
2             发送             已经调用send()方法,但尚未接受响应
3             接受             已经接受到部分响应数据
4             完成             已经接受到全部响应数据,而且可以使用
*/


addEvent(document,'click', function(){
var xhr=new createXHR();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
    if(xhr.status==200){
        alert(xhr.responseText);
    }else{
        alert('数据返回失败!状态代码:' +xhr.status+'状态信息:'
        +xhr.statusText);
    }
    }
};
xhr.open('get','demo.php?rand=' +Math.random(),true);
xhr.send(null);
});

/*PS:使用abort()方法可以取消异步请求,放在send()方法之前会报错。放在responseText
之前会得到一个空值。
二.GET与POST
在提供服务器请求的过程中,有两种方式,分别是:GET和POST。在Ajax使用的过
程中,GET的使用频率要比POST高。
在了解这两种请求方式前,我们先了解一下HTTP头部信息,包含服务器返回的响应头
信息和客户端发送出去的请求头信息。我们可以获取响应头信息或者设置请求头信息。我们
可以在Firefox浏览器的firebug查看这些信息。*/

//使用getResponseHeader()获取单个响应头信息
alert(xhr.getResponseHeader('Content-Type'));
//使用getAllResponseHeaders()获取整个响应头信息
alert(xhr.getAllResponseHeaders());
//使用setRequestHeader()设置单个请求头信息
xhr.setRequestHeader('MyHeader','Lee'); //放在open方法之后,send方法之前
/*PS:我们只可以获取服务器返回回来响应头信息,无法获取向服务器提交的请求头信
息,自然自定义的请求头,在JavaScript端是无法获取到的。
GET请求
GET请求是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查
询字符串参数追加到URL的末尾,以便提交给服务器。*/

xhr.open('get','demo.php?rand=' +Math.random()+'&name=Koo', true);

/*通过URL后的问号给服务器传递键值对数据,服务器接收到返回响应数据。特殊字符
传参产生的问题可以使用encodeURIComponent()进行编码处理,中文字符的返回及传参,
可以讲页面保存和设置为utf-8格式即可。
*/
//一个通用的URL提交函数
function addURLParam(url,name,value){
    url+=(url.indexOf('?')==-1?'?' :'&'); //判断的url是否有已有参数
    url+=encodeURIComponent(name)+'=' +encodeURIComponent(value);
    alert(url);
    return url;
}

/*PS:当没有encodeURIComponent()方法时,在一些特殊字符比如“&”,会出现错误导
致无法获取。
POST请求
POST请求可以包含非常多的数据,我们在使用表单提交的时候,很多就是使用的POST
传输方式。*/

xhr.open('post','demo.php', true);

//而发送POST请求的数据,不会跟在URL的尾巴上,而是通过send()方法向服务器提交数据。
xhr.send('name=Lee&age=100');

/*一般来说,向服务器发送POST请求由于解析机制的原因,需要进行特别的处理。因为
POST请求和Web表单提交是不同的,需要使用XHR来模仿表单提交。*/

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

/*PS:从性能上来讲POST请求比GET请求消耗更多一些,用相同数据比较,GET最多
比POST快两倍。
上一节课的JSON也可以使用Ajax来回调访问。*/

var url='demo.json?rand=' +Math.random();
var box=JSON.parse(xhr.responseText);

/*三.封装Ajax
因为Ajax使用起来比较麻烦,主要就是参数问题,比如到底使用GET还是POST;到
底是使用同步还是异步等等,我们需要封装一个Ajax函数,来方便我们调用。*/

function ajax(obj){
    var xhr=new createXHR();
    obj.url=obj.url+'?rand=' +Math.random();
    obj.data=params(obj.data);
    if(obj.method==='get') obj.url=obj.url.indexOf('?')==-1?
    obj.url+'?' +obj.data:obj.url+'&' +obj.data;
    if(obj.async===true){
    xhr.onreadystatechange=function(){
        if(xhr.readyState==4)callback();
};
}

xhr.open(obj.method,obj.url,obj.async);
    if(obj.method==='post') {
    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xhr.send(obj.data);
    }else{
    xhr.send(null);
    }
    if(obj.async===false){
    callback();
    }
    function callback(){
    if(xhr.status==200){
    obj.success(xhr.responseText); //回调
    }else{
    alert('数据返回失败!状态代码:' +xhr.status+',
    状态信息:' +xhr.statusText);
    }
}
}

//调用ajax
addEvent(document,'click', function(){ //IE6需要重写addEvent
    ajax({
        method:'get',
        url:'demo.php',
        data:{
        'name' :'Lee',
        'age' :100
    },
    success:function(text){
        alert(text);
    },
    async:true
    });
});
//名值对编码
function params(data){
    var arr=[];
    for(variindata){
    arr.push(encodeURIComponent(i)+'=' +encodeURIComponent(data[i]));
    }
    return arr.join('&');
}

//PS:封装Ajax并不是一开始就形成以上的形态,需要经过多次变化而成。
View Code
原文地址:https://www.cnblogs.com/suihui/p/3307036.html