×
广告

电子商务网站开发与建设

96
阿啊阿吖丁
2018.06.15 16:42* 字数 33193

概要

64学时 3.5学分

章节安排

  1. 电子商务网站概况
  2. HTML5+CSS3
  3. JavaScript
  4. Node

电子商务网站概述

电子商务网站定义

电子商务网站是指一个企业、机构或公司在互联网上建立的站点,其目的是为了宣传企业形象、发布产品信息、宣传经济法规、提供商业服务等。

电子商务网站功能

  • 企业形象宣传

  • 新闻发布、供求信息发布

  • 产品和服务项目展示

  • 商品和服务定购

电子商务网站的架构

电子商务网站的架构

电子商务网站的构成要素

  • 网站域名
  • 网站物理地点
  • 网站页面
  • 商品目录
  • 购物车
  • 付款台

JavaScript

  • 基础语法

  • 客户端JavaScript

基础语法

  • 词法结构

  • 类型、变量、运算符和表达式

  • 语句

  • 数组

  • 函数

  • 对象

  • 类和模块

  • 正则表达式的模式匹配

词法结构

JavaScript是区分大小写的语言。

JavaScript支持2种格式的注释:

//这里是单行注释

/*这里是一段注释(这里的注释可以连写多行)*/

JavaScript中,标识符用来对变量和函数进行命名。标识符必须以字母、下划线(_)或美元符($)开始。后续的字符可以是字母、数字、下划线或美元符。

JavaScript保留字不能作为标记符,例如:breakdeletefunctionreturntypeofcasedoifswitchvarcatch

JavaScript使用分号(;)将语句分隔开。

类型、变量、运算符和表达式

JavaScript的数据类型分为2类:

  • 原始类型:包括数字、字符串和布尔值。JavaScript中有两个特殊的原始值:null(空)和undefined(未定义)。
  • 对象类型:是属性的集合,每个属性都由“名/值对”构成。

数字、算术运算符

数字:包括整数、浮点数。

算术运算符:加(+)、减(-)、乘(*)、除(/)、求余数(%)、递增(++)、递减(--)、按位异或(^)。

  • 注意:++--运算符的返回值依赖于它相对于操作数的位置。当运算符在操作数之前,称为“前增量”运算符,它对操作数进行增量计算,并返回计算后的值。当运算符在操作数之后,称为“后增量”运算符,它对操作数进行增量计算,但返回未做增量计算的值。

    var i=1,j=++i;  //i和j的值都是2
    var i=1,j=i++;  //i是2,j是1
    
  • ^如果不是出现在正则表达式中,那么其代表按位异或运算符,也可以充当二进制算法。

  • 异或的算法为相同输出0,不同输出1

    a^b a的二进制 b的二进制 运算结果二进制 运算结果十进制
    6^8 0110(三位,不够前面加0) 1000 1110 14
    20^31 10100 11111 01011 11

数学常量

常量 说明
Math.E 常量e,自然对数的底数
Math.LN10 10的自然对数
Math.LN2 2的自然对数
Math.LOG10E e以10为底的对数
Math.LOG2E e以2为底的对数
Math.PI 常量π
Math.SQRT1_2 2的平方根的倒数
Math.SQRT2 2的平方根

数学函数

函数 说明 函数 说明
Math.abs(x) 返回x的绝对值 Math.acos(x) 返回x的反余弦值
Math.asin(x) 返回x的反正弦值 Math.atan(x) 返回x的反正切值
Math.atan2(y,x) 返回从X轴到指定点的角度,y为点的Y坐标,x为点的X坐标 Math.ceil(x) 返回大于或等于x的最接近的整数
Math.cos(x) 返回x的余弦值 Math.exp(x) 返回e的x次方
Math.floor(x) 返回小于或等于x的最接近的整数 Math.log(x) 返回x的自然对数
Math.max(args…) 返回参数中最大的值,参数中可以有多个值 Math.min(args…) 返回参数中最小的值,参数中可以有多个值
Math.pow(x,y) 返回xy次方 Math.random() 返回一个在[0.0,1)之间的随机数
Math.round(x) 返回最接近x的整数 Math.sin(x) 返回x的正弦值
Math.sqrt(x) 返回x的平方根 Math.tan(x) 返回x的正切值

数字相关方法

方法 说明
n.toExponential(digits) 返回以指数记数法表示的n的字符串格式,小数点前有一个数字,小数点后有digits个数字
n.toFixed(digits) 返回n的字符串格式,不使用指数记数法,在小数点后有指定的digits个数字
n.toLocaleString() n转换为本地格式的字符串
n.toPrecision(prec) 返回一个包含prec位有效数字的数字字符串,如果prec足够大,包括当前数字的所有整数部分,则返回值与toFixed方法一致。其他情况下,将使用指数记数法,小数点前有一个数字,小数点后有prec-1个数字
n.toString() n转换为字符串
Number(object) 把对象的值转换为数字。如果参数是Date对象,Number()返回从1970年1月1日至今的毫秒数。如果对象的值无法转换为数字,那么Number()函数返回NaN

字符串

由单引号或双引号括起来的字符序列。由单引号定界的字符串中可以包含双引号,由双引号定界的字符串中也可以包含单引号。

  • 注意:当使用单引号来定界字符串时,需注意英文中的缩写(can't)。因为撇号和单引号是同一个字符,所以必须使用转义字符(\)来转义,例如'can\'t'

JavaScript的内置功能之一就是字符串的连接。连接运算符为“+”。例如:

var msg="hello, "+"world";   //生成字符串“hello, world”

length属性可以确定一个字符串的长度,例如:msg.length

JavaScript中用“>”或“<”操作符比较字符串大小时,它们只会比较这些字符的Unicode编码,而不考虑本地的顺序。

字符串类型的大小判断是一个字符和一个字符的比较,只要有字符不同就停止继续判断并返回比较结果。例如:"aBc"<"ab";

localeCompare方法可以实现汉字按拼音排序。

字符集 范围 Unicode编码(16进制) Unicode编码(10进制)
数字 0~9 30~39 48~57
大写字母 A~Z 41~5A 65~90
小写字母 a~z 61~7A 97~122
基本汉字 ~ 4E00~9FA5 19968~40869

字符串相关方法

方法 说明 方法 说明
s.charAt(n) 返回字符串s的第n个字符,从0开始 s.concat(value,…) 返回由每个参数连接为s而组成的新的字符串。 s="hello"; s.concat("","world","!");
s.indexOf(s1 [,start]) 返回在sstart位置之后,s1第一次出现的位置,如果没有找到则返回-1 s.lastIndexOf(s1[,start]) 返回s1在字符串sstart位置之前最后一次出现的位置,如果没有找到则返回-1。其从s的结尾开始搜索到开头
s.trim() 去掉开头和结尾处的空白字符 s.match(s1) 在字符串内检索指定的值,若找到,则返回s1,若没有找到,则返回null
s.replace(s1,s2) 用于在s中用s2替换s1 s.search(s1) 返回第一个s1相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回 -1
s.slice(start,end) 返回从start位置开始,直到但不包含end位置的所有字符 s.split(delimiter) 通过delimiters切分成一个数组。
s.substr(start,length) 返回从start位置开始的length个字符 s.substring(start,end) 返回从start位置开始,直到但不包含end位置的所有字符
s.toLocaleLowerCase() 以本地化的方式将s转为小写 s.toLocaleUpperCase() 以本地化的方式将s转为大写
s.toLowerCase() s转为小写 s.toUpperCase() s转为大写
s.localeCompare(s1[,locale]) ss1小,返回一个小于0的数,若ss1大,返回一个大于0的数,若相同,返回0。可用于汉字按拼音排序的规则,例如"张三">"李四"。注意:Chrome浏览器在使用时需用: s.localeCompare(s1,"zh")locale包含一种或多种语言或区域设置标记的区域设置字符串数组。如果包含多个区域设置字符串,请以降序优先级对它们进行排列,确保首个条目为首选区域位置。如果省略此参数,则使用JavaScript运行时的默认区域设置。

布尔值、逻辑运算符、关系运算符、nullundefined

布尔值:这个类型只有两个值,保留字truefalse

逻辑运算符:&&(逻辑与)、||(逻辑或)、!(逻辑非)

关系运算符:==(等于)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于)、!=(不等于)

null是JavaScript语言的关键字,表示一个特殊值,常用来描述“空值”。

undefined是变量的一种取值,表明变量没有初始化。如果函数没有返回值,则返回undefined

变量

在JavaScript中,使用一个变量之前应先声明。变量是用关键字var来声明的,例如:

var i,j;        //通过一个var声明多个变量
var i=0,j=0;    //可以将变量的初始赋值和变量声明合写在一起

变量的作用域:

  • 全局变量:声明在函数外部的变量

  • 局部变量:声明在函数内部的变量。函数内声明的所有变量在函数体内始终是可见的。这意味着变量在声明之前甚至已经可见。JavaScript的这个特性被非正式地称为声明提前(hoisting),例如:

    var scope="global";
    function f() {
        console.log(scope);     //输出“undefined”,而不是“global”
        var scope="local";      //变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
        console.log(scope);     //输出“local”
        }
    

赋值

赋值表达式:JavaScript使用“=”运算符来给变量或者属性赋值。

带操作的赋值运算:

运算符 示例 等价于
+= a+=b a=a+b
-= a-=b a=a-b
*= a*=b a=a*b
/= a/=b a=a/b
%= a%=b a=a%b
^= a^=b a=a^b

语句

条件语句

通过判断指定表达式的值来决定执行还是跳过某些语句。JavaScript中基本的条件语句有2种:

  • if语句,其有两种形式:

    // 1
    if (条件)
      语句1;
    [else
     语句2;]
    
    //2
    if (条件){
        语句块1;
        }
    [else{
        语句块2;
        }]
    

    chap3-1.html

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>chap3-1</title>
      </head>
  <body>
  <form>
      <span>The Grade:</span><input type="text" id="Grade">
      </form>
  <script>
      document.getElementById("Grade").onkeydown=function (e) {
          if(e.keyCode==13){
              if(e.target.value>=60)
                  alert("成绩及格");
              else
                  alert("成绩不及格");
          }
      }
      </script>
  </body>
  </html>
  • switch语句,其形式为:

    switch (expression){
        case e1:    //如果expression==e1,则执行语句块1
            语句块1;
            break;  //停止执行switch语句
        case e2:    //如果expression==e2,则执行语句块2
            语句块2;
            break;
        case en:    //如果expression==en,则执行语句块n
            语句块n;
            break;
        default:    //如果所有的条件都不匹配,则执行语句块n+1
            语句块n+1;
            break;
            }
    

    chap3-2.html

    <body><form><span>The Grade:</span><input type="text" id="Grade"></form>
    <script>document.getElementById("Grade").onkeydown=function (e) {
            if(e.keyCode==13){
                var Grank=Math.floor(document.getElementById("Grade").value/10);
                switch (Grank){
                    case 10:
                        alert("优秀");
                        break;
                    case 9:
                        alert("优秀");
                        break;
                    case 8:
                        alert("良好");
                        break;
                    case 7:
                        alert("中等");
                        break;
                    case 6:
                        alert("及格");
                        break;
                    default:
                        alert("不及格");
                        break;
                }}} </script></body>
    

循环语句

可以让一部分代码重复执行。JavaScript中有4种循环语句:

  • while语句

    // 1
    while (条件)
        语句;
    // 2
    while (条件){
        语句块;
        }
    

    chap3-3.html

    <body>
    <form>
        <span>阶乘:</span><input type="text" id="jc">
        </form>
    <script>
        var o=document.getElementById("jc");
        o.onkeydown=function (e) {
            if(e.keyCode==13){
                var m=n=o.value;
                var result=1;
                while(n>0){
                    result*=n;
                    n=n-1;
                }
                alert(m+"!="+result);
            }
        }
        </script>
    </body>
    </html>
    
  • do/while语句

    // 1
    do
        语句;
        while(条件);
    // 2
    do{
        语句块;
        }while(条件);
    

    chap3-4.html

    <body>
    <form>
        <span>阶乘:</span><input type="text" id="jc">
        </form>
    <script>
        var o=document.getElementById("jc");
        o.onkeydown=function (e) {
            if(e.keyCode==13){
                var m=n=o.value;
                var result=1;
                do{
                    result*=n;
                    n--;
                }while(n>0);
                alert(m+"!="+result);
            }
        }
        </script>
    </body>
    
  • for语句

    for(initialize;test;increment){
        statements;
        }
    

    chap3-5.html

    <body>
    <form>
        <span>阶乘:</span><input type="text" id="jc">
        </form>
    <script>
        var o=document.getElementById("jc");
        o.onkeydown=function (e) {
            if(e.keyCode==13){
                var m=o.value;
                var result=1;
                for(var i=1;i<=m;i++){
                    result*=i;
                }
                alert(m+"!="+result);
            }
        }
        </script>
    </body>
    
  • for/in语句

    for(variable in object){
        statements;
        }
    

    chap3-6.html

    <script>
        var a="1,2,3,4,5";
        var arr=a.split(",");
        var sum=0;
        for(var i in arr){
            sum+=Number(arr[i]);
        }
        alert(a+"中值的和为:"+sum);
    </script>
    

跳转语句

可以使得JavaScript的执行从一个位置跳转到另一个位置。

  • break语句是跳转到循环或者其他语句的结束。
  • continue语句是终止本次循环的执行并开始下一次循环的执行。

chap3-7.html

<script>
    var sumb=sumc=0;
    for(var i=1;i<=100;i++){
        sumb+=i;
        if(i==50)
            break;
    }
    for(var i=1;i<=100;i++){
        if(i==50)
            continue;
        sumc+=i;
    }
    alert("break的结果:"+sumb+"\n"+"continue的结果:"+sumc);
</script>

标签语句

标签是由语句前的标识符和冒号组成:identifier:statement

通过给语句定义标签,就可以在程序的任何地方通过标签名引用这条语句。breakcontinue是JavaScript中唯一可以使用语句标签的语句。

break|continue identifier;

mainloop: while(j<=100){
    j++;
    continue mainloop;  //跳转到下一次循环
    }
alert("j的值为:"+j);
mainloop: while(j<=100){
    j++;
    break mainloop;
    }
alert("j的值为:"+j);   //break语句跳转至此处

注意:不管continue语句带不带标签,它只能在循环体内使用。

return语句

可以指定函数调用后的返回值。return expression;

function squre(x) {
    return x*x;
    }
document.writeln("2的平方等于:"+squre(2)+"<br>");

异常处理语句

所谓异常(exception)是当发生了某种异常情况或错误时产生的一个信号。抛出异常就是用信号通知发生了错误或异常情况。捕获异常是指处理这个信号,即采取必要的手段从异常中恢复。

throw语句可以抛出异常:throw expression;

try/catch/finally语句可以捕获异常:

try{
    //通常来讲,这里的代码会从头执行到尾而不会产生问题,但有时会抛出一个异常,要么是由throw语句直接抛出异常,要么是通过调用一个方法间接抛出异常
    }
catch(e){
    //当且仅当try语句块抛出了异常,才会执行这里的代码。这里可以通过局部变量e来获得对Error对象或者抛出的其它值的引用,这里的代码块可以基于某种原因处理这个异常,也可以忽略这个异常,还可以通过throw语句重新抛出异常。
    }
finally{
    /*不管try语句块是否抛出了异常,这里的逻辑总是会执行,终止try语句块的方式有:
    1)正常终止,执行完语句块的最后一条语句
    2)通过break、continue或return语句终止
    3)抛出一个异常,异常被catch从句捕获
    4)抛出一个异常,异常未被捕获,继续向上传播
    */
    }

chap3-8.html

<script>
    function jc(x) {
        if(x<0||x>10) throw new Error("x的值不能为负");
        for(var res=1;x>1;res*=x,x--);
        return res;
    }
    var grade=Number(prompt("请输入一个正整数:",""));
    try{
       alert(grade+"!="+jc(grade));
    }
    catch (e){
        alert(e);
        var grade=Number(prompt("请输入一个正整数:",""));
        if(grade>10) throw new Error("x的值不能超过10");
        alert(grade+"!="+jc(grade));
    }
    finally {
        alert("x的范围为1-10之间");
    }
</script>

数组

数组是值的有序集合。每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。

JavaScript数组是无类型的:数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型。

创建

创建数组有2种方式:

  • 数组直接量:

    var empty=[];   //没有元素的数组
    var primes=[1,2,3,4,5];     //有5个数值的数组
    var misc=[1.1,true,"a"];    //3个不同类型的元素
    var base=1024;
    var table=[base,base+1,base+2];     //值可以是表达式
    var b=[{x:1,y:2},primes];   //值还可以对象或其它数组
    

    如果省略数组直接量中的某个值,省略的元素将被赋予undefined值:

    var count=[1,,3];   //数组有3个元素,中间的那个元素值为undefined
    var undefs=[,,];    //数组有2个元素,都是undefined
    

    数组直接量的语法允许有可选的结尾的逗号,故[,,]只有两个元素,而非三个。

  • 调用构造函数Array(),其有2种形式:

    • 调用时没有参数:var a=new Array();

      该方法创建一个没有任何元素的空数组,等同于数组直接量[]

    • 调用时有一个数值参数,它指定长度:var a=new Array(10);

      当预先知道所需元素个数时,这种形式的Array()构造函数可以用来预分配一个数组空间。此时数组中没有存储值,甚至数组的索引属性“0”、“1”等还未定义。

      我们可以用new Array()显示指定两个或多个数组元素或者数组的一个非数值元素:

      var a=new Array(5,4,"testing");
      

读写

数组元素的读与写:使用[]操作符来访问数组中的一个元素。

var a=["hello"];    //从一个元素的数组开始
var value=a[0];     //读第0个元素
a[1]=3.14;          //写第1个元素
i=2;
a[i]=3;             //写第2个元素
document.write(a.length);

JavaScript中数组的特别之处在于,当使用小于232的非负整数作为索引时,数组会自动维护其length属性值,如上,创建仅有一个元素的数组,然后在索引12处分别进行赋值,则数组的长度变为3

注意:JavaScript中数组索引仅仅是对象属性名的一种特殊类型,这意味着数组没有“越界”错误的概念。当试图查询对象中不存在的属性时,不会报错,只会得到undefined值。

稀疏数组

就是包含从0开始的不连续索引的数组。稀疏数组length属性大于元素的个数。可以用Array()构造函数或简单地指定数组的索引值大于当前的数组长度来创建稀疏数组。

a=new Array(5);     //数组没有元素,但是a.length是5
a[1000]=0;      //赋值添加一个元素,但是设置length为1001

足够稀疏的数组通常在实现上比稠密的数组更慢,内存利用率更高,在这样的数组中查找元素的时间更长。

元素的添加

数组元素的添加有3种方式:

  • 为新索引赋值:

    var a=[];   //开始是一个空数组
    a[0]="zero";    //然后向其中添加元素
    a[1]="one";
    
  • 使用push()方法在数组末尾添加一个或多个元素:

    var a=[];   //开始是一个空数组
    a.push("zero");    //在末尾添加一个元素
    a.push("one","two");    //在末尾添加两个元素
    
  • 使用unshift()方法在数组头部添加一个或多个元素:

    var a=[];   //开始是一个空数组
    a.unshift("two");    //在头部添加一个元素
    a.unshift("zero","one");    //在头部添加两个元素
    

元素的删除

数组元素的删除有3种方式:

  • 使用delete运算符删除:

    对一个数组使用delete不会修改数组的length属性,也不会将元素从高索引处移下来填充已删除属性留下的空白。

    var a=[1,2,3];
    delete a[1];    //a在索引1的位置不再有元素
    
  • 使用pop()方法删除数组末尾的元素:

    该方法减少数组的长度。

    var a=[1,2,3];
    a.pop();    //删除a尾部的元素
    
  • 使用shift()方法在数组头部删除一个或多个元素:

    该方法减少数组的长度,并将所有随后的元素下移一个位置来填补数组头部的空缺。

    var a=[1,2,3];
    a.shift();    //删除a头部的元素
    

多维数组

JavaScript不支持真正的多维数组,但可以用数组的数组来近似。

chap3-9.html

<head><style>
span{display: inline-block;width: 80px;height: 60px;font-size: xx-large}</style>
</head>
<body>
<script>
    var table=new Array(10);    //表格有10行
    for(var i=0;i<table.length;i++)
        table[i]=new Array(10);     //每行有10列
    //初始化数组
    for(var row=0;row<table.length;row++){
        for(var col=0;col<table[row].length;col++){
            table[row][col]=row*col;
        }
    }
    //输出数组元素值
    for(var row=0;row<table.length;row++){
        for(var col=0;col<table[row].length;col++){
            document.write("<span>"+table[row][col]+"</span>");
        }
        document.write("<br>");
    }
    </script>
</body>

数组方法

join():将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认使用逗号。

var a=[1,2,3];  //创建一个包含三个元素的数组
var str=a.join();   //str的值为“1,2,3”
str=a.join(" ");    //str的值为“1 2 3”

reverse():将数组中的元素颠倒顺序,返回逆序的数组。

var a=[1,2,3];  //创建一个包含三个元素的数组
a.reverse();
for(var i=0;i<a.length;document.write(a[i]+"<br>"),i++);

sort():将数组中的元素排序并返回排序后的数组,语法为:arrayObject.sort([sortby])

  • 如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。

    var a=["banana","cherry","apple"];
    a.sort();
    for(var i=0;i<a.length;document.write(a[i]+"<br>"),i++);
    var b=[33,4,1111,222];
    b.sort();   //输出结果:1111 222 33 4
    for(var i=0;i<b.length;document.write(b[i]+"<br>"),i++);
    
  • 如果想按照其他标准进行排序,就需要提供比较函数(sortby),该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数ab,其返回值如下:

    • a小于b,在排序后的数组中a应该出现在b之前,则返回一个小于0的值。
    • a等于b,则返回0
    • a大于b,则返回一个大于0的值。

chap3-10.html

<script>
    var a=["banana","cherry","apple"];
    a.sort();
    for(var i=0;i<a.length;document.write(a[i]+"<br>"),i++);
    var b=[33,4,1111,222];
    b.sort();   //输出结果:1111 222 33 4
    for(var i=0;i<b.length;document.write(b[i]+"<br>"),i++);
    b.sort(function (m,n) {
        return m-n;     //按由小到大的顺序排列
    });
    for(var i=0;i<b.length;document.write(b[i]+"<br>"),i++);
</script>

concat():创建并返回一个新数组,它的元素包括调用concat()的原始数组的元素和concat()的每个参数。如果这些参数中的任何一个自身是数组,则连接的是数组的元素,而非数组本身。

注意:concat()不会递归扁平化数组的数组。concat()也不会修改调用的数组。

chap3-11.html

<head>
    <meta charset="UTF-8">
    <title>chap3-11</title>
    <style>
        span{display: inline-block;width: 80px;height: 60px;font-size: xx-large}
        span#s1{display: block}
    </style>
    </head>
<body>
<script>
    var a=[1,2,3];
    var b=a.concat(4,5);    //返回[1,2,3,4,5]
    b=a.concat([4,5],[6,7]);      //返回[1,2,3,4,5,6,7]
    b=a.concat([4,[5,[6,7]]]);      //返回[1,2,3,4,[5,[6,7]]]
    for(var i=0;i<b.length;i++){
        if(Array.isArray(b[i]))
            for(var j=0;j<b[i].length;document.write("<span>"+b[i][j]+"</span>"),j++);
        else
            document.write("<span id='s1'>"+b[i]+"</span>");
    }
    </script>
</body>

slice()方法返回一个新的数组,包含从start到end (不包括该元素)的arrayObject中的元素。其语法格式为:

arrayObject.slice(start,end)

  • start:必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1指最后一个元素,-2指倒数第二个元素,以此类推。
  • end:可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从start到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
var a=[1,2,3,4,5];
document.write(a.slice(0,3)+"<br>");    //返回[1,2,3]
document.write(a.slice(3)+"<br>");      //返回[4,5]
document.write(a.slice(1,-1)+"<br>");   //返回[2,3,4]

splice()方法向/从数组中添加/删除项目,然后返回被删除的项目。其语法格式为:

arrayObject.splice(index,howmany,item1,.....,itemX)

  • index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
  • howmany:必需。要删除的项目数量。如果设置为0,则不会删除项目。
  • item1,…,itemX:可选。向数组添加的新项目。
  • splice()方法可删除从index处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。
  • 如果从arrayObject中删除了元素,则返回的是含有被删除的元素的数组。

chap3-12.html

<script>
    var a=[1,2,3,4,5];
    document.write(a.slice(0,3)+"<br>");    //返回[1,2,3]
    document.write(a.slice(3)+"<br>");      //返回[4,5]
    document.write(a.slice(1,-1)+"<br>");   //返回[2,3,4]
    a.splice(2,0,6);
    document.write(a+"<br>");   //返回[1,2,6,3,4,5]
    a.splice(2,1);
    document.write(a+"<br>");   //返回[1,2,3,4,5]
    a.splice(2,1,6);
    document.write(a+"<br>");   //返回[1,2,6,4,5]
    a.splice(2,3,3);
    document.write(a+"<br>");   //返回[1,2,3]
    a.splice(3,0,4,5);
    document.write(a+"<br>");   //返回[1,2,3,4,5]
</script>

函数

函数是这样的一段JavaScript代码,它只定义一次,但可能被执行或调用任意次。

JavaScript函数是参数化的:函数的定义会包括一个称为形参(parameter)的标识符列表,这些参数在函数体中像局部变量一样工作。函数调用会为形参提供实参(argument)的值。函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。

除了实参之外,每次调用还会拥有另一个值,即本次调用的上下文(context),这就是this关键字的值。如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数的this的值。

JavaScript的函数可以嵌套在其他函数中定义,这样就构成了一个闭包(closure)。

定义

函数使用function关键字来定义,它可以用在:

  • 函数声明语句

    在函数声明语句中:

    • function关键字后的函数名称是函数声明语句必需的部分。
    • 一对圆括号,其中包含由0个或者多个用逗号隔开的形参组成的列表。
    • 一对花括号,其中包含0条或多条JavaScript语句。

    这些语句构成了函数体,一旦调用函数,就会执行这些语句。

    function jc(x) {
        var result=1;
        for(var i=1;i<=x;i++){
            result*=i;
        }
        return result;
        }
    var a=Number(prompt("请输入一个正整数:",""));
    document.write(jc(a)+"<br>");
    
  • 函数定义表达式

    对于函数定义表达式来说,函数名称是可选的,如果存在,该名字只存在于函数体中,并指代该函数对象本身。

    var square=function(x) {return x*x;};   //定义时函数名称省略
    document.write(square(a)+"<br>");       //调用时使用变量名称(实参)形式
    var f=function fact(x) {                //定义时可以包含函数名称
        if (x<=1)
            return 1;
        else
            return x*fact(x-1);
            }
    document.write(f(a)+"<br>");            //调用时使用变量名称(实参)形式
    

如同变量,函数声明语句“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码所调用。

但是,对于函数定义表达式而言,就另当别论了,为了调用一个函数,必须要能引用它,而要使用一个以表达式方式定义的函数之前,必须把它赋值给一个变量。变量的声明提前了,但给变量赋值是不会提前的,所以,以表达式方式定义的函数在定义之前无法调用。

chap3-13.html

<script>
    var a=Number(prompt("请输入一个正整数:",""));
    document.write(jc(a)+"<br>");
    function jc(x) {
        var result=1;
        for(var i=1;i<=x;i++){
            result*=i;
        }
        return result;
    }
    var square=function(x) {return x*x;};   //定义时函数名称省略
    document.write(square(a)+"<br>");       //调用时使用变量名称(实参)形式
    var f=function fact(x) {                //定义时可以包含函数名称
        if (x<=1)
            return 1;
        else
            return x*fact(x-1);
    }
    document.write(f(a)+"<br>");            //调用时使用变量名称(实参)形式
</script>

调用

有4种方式来调用JavaScript函数:

  • 作为函数调用:var a=jc(10);

  • 作为方法调用:var b=Math.floor(3.2);

    方法调用和函数调用有一个重要的区别,即:调用上下文。this关键字只能出现在函数中,当然在全局作用域中是个例外。

    全局作用域中this指向全局对象(全局对象在浏览器这个环境中指window)。

    如果this出现在函数中,其指向的依据就是函数的执行环境而不是声明环境。换句话说,this永远指向所在函数的所有者,当没有显示的所有者的时候,那么this指向全局对象。

    各种情况下的this的具体指向:

    • 在全局作用域中this指向为全局对象windowdocument.write(this+ "<br>");

    • 函数作为某个对象的成员方法调用时this指向该对象。

      chap3-14.js

      var name="zhangsan";
      var obj={
          name:"lizi",
          getName:function () {
              document.write(this.name + "<br>");
          }
          }
      obj.getName();      //输出lizi
      
    • 函数作为函数直接使用时this指向全局对象。

      chap3-14.js

      var name="zhangsan";
      var obj={
          name:"lizi",
          getName:function () {
              document.write(this.name + "<br>");
          }
          }
      var nameGet=obj.getName;
      nameGet();          //输出zhangsan
      
    • 函数作为构造函数调用时this指向用该构造函数构造出来的新对象。

      chap3-14.js

      var name="zhangsan";
      var obj1=function (x,y) {
          this.name=x+y;
          }
      obj1.prototype.getName=function () {
          document.write(this.name + "<br>");
          }
      var myObj=new obj1("wang","er");
      myObj.getName();      //输出wanger
      
    • call()apply()bind()方法可以改变函数执行时候的this指向。

      function Sister() {
          this.age=18;
          this.sayAge=function () {document.write("Age:"+this.age+ "<br>");}
          }
      function Brother() {
          this.age=25;
          this.sayAge=function () {document.write("Age:"+this.age+ "<br>");}
          }
      var sis=new Sister();
      var bro=new Brother();
      sis.sayAge.call(bro);   //输出"Age:25"
      
  • 作为构造函数调用

    如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。var myObj=new obj1("wang","er");

    构造函数调用创建和初始化一个新的对象myObj,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象。myObj对象继承自构造函数的prototype属性。

  • 间接调用

    call()apply()方法可以看做是某个对象的方法,通过调用方法的形式来间接调用函数。

    他们的用途相同,都是在特定的作用域中调用函数。

    接收参数方面不同,apply()接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。call()方法第一个参数与apply()方法相同,但传递给函数的参数必须列举出来。

    chap3-14.js

    window.firstName="San";
    window.lastName="Zhang";
    var myObject={firstName:"my",lastName:"Object"};
    function HelloName() {document.write(this.firstName+" "+this.lastName+ "<br>");}
    HelloName.call(window);     //输出"San Zhang"
    HelloName.call(this);      //输出"San Zhang"
    HelloName.call(myObject);   //输出"my Object"
    
    function sum(m,n) {
        return m+n;
        }
    document.write(sum.call(window,10,10)+ "<br>");     //输出20
    document.write(sum.apply(window,[10,20])+ "<br>");  ////输出30
    

实参和形参

JavaScript中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript函数调用甚至不检查传入形参的个数。

可选形参:当调用函数的时候传入的实参比形参个数少时,剩下的形参都将设置为undefined值。因此在调用函数时形参是否可选以及是否可以省略应当保持较好的适应性。为了做到这一点,应当给省略的参数赋一个合理的默认值。

  • 注意:当用这种可选形参来实现函数时,需要将可选形参放在形参列表的最后。

chap3-15.js

function int(x,type) {
    if(type===undefined) return Math.floor(x);
    if(type===1) return Math.floor(x);
    if(type===2) return Math.ceil(x);
    if(type===3) return Math.round(x);
    }
document.write("3.4默认去尾法取整:" +int(3.4)+"<br>");
document.write("3.4去尾法取整:" +int(3.4,1)+"<br>");
document.write("3.4进位法取整:" +int(3.4,2)+"<br>");
document.write("3.4四舍五入取整:" +int(3.4,3)+"<br>");

可变长的实参列表(实参对象):当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。参数对象解决了这个问题。

  • 实参对象有一个重要的用处就是:让函数可以操作任意数量的实参。
  • 假设定义了函数f,它的形参只有一个x。如果调用f时传入两个实参,第一个实参可以通过形参名x来获得,也可以通过arguments[0]来得到;第二个实参只能通过arguments[1]来得到。此外,和数组一样,arguments.length属性返回实参的个数。
  • 注意:arguments不是数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性。

chap3-15.js

function max() {
    var max=Number.NEGATIVE_INFINITY;   //NEGATIVE_INFINITY 表示负无穷大
    for(var i=0;i<arguments.length;i++){
        if(arguments[i]>max) max=arguments[i];
    }
    return max;
    }
var largest=max(1,10,100,2,3,1000,4,5,10000,6);
document.write("最大值为:"+largest+"<br>");

将对象属性用做实参:当一个函数包含超过三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序很难。最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。为了实现这种风格的方法调用,定义函数时,传入的实参都写入一个单独的对象中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。

chap3-15.js

function arraycopy(from,from_start,to,to_start,length) {
    for(var i=to_start;i<to_start+length;i++){
        to[i]=from[from_start+i-to_start];
    }
    }
function easycopy(args) {
    arraycopy(args.from,
        args.from_start||0,     //这里设置了默认值
        args.to,
        args.to_start||0,       //这里设置了默认值
        args.length
    );
    }
var a=[1,2,3,4],b=[5,6,7,8];
easycopy({from:a, to:b, to_start:2, length:4});
for(var i=0;i<b.length;i++){document.write(b[i]+"<br>");}

作为值的函数

在JavaScript中,函数不仅是一种语法,也是值,也就是说,可以将函数赋值给变量。

chap3-16.js

function squre(x) {return x*x;}
var s=squre;    //现在s和squre指代同一个函数
document.write(squre(4)+"<br>");
document.write(s(4)+"<br>");

除了可以将函数赋值给变量,同样可以将函数赋值给对象的属性。

chap3-16.js

var o={square:squre};
var x=o.square(16);
document.write(x+"<br>");

函数甚至不需要带名字,当把它们赋值给数组元素时:

chap3-16.js

var a=[function (x) {return x*x},20];
document.write(a[0](a[1])+"<br>");

作为命名空间的函数

JavaScript中变量的作用域有全局变量和局部变量2种。在JavaScript中是无法声明只在一个代码块内可见的变量的,基于这个原因,我们常常简单地定义一个函数用做临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间。

function mymodule() {
    //模块代码,这个模块所使用的所有变量都是局部变量,而不是污染全局命名空间
    }
mymodule();     //不要忘了还要调用这个函数

这段代码仅仅定义了一个单独的全局变量:名叫“mymodule”的函数。这样还是太麻烦,可以直接定义一个匿名函数,并在单个表达式中调用它:

(function () {
  //模块代码
}());   //结束函数定义并立即调用它

闭包

出于种种原因,我们有时候需要得到函数内的局部变量。闭包可以捕捉到局部变量(和参数),并一直保存下来。闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!

chap3-17.js

var scope="global scope";       //全局变量
function checkscope() {
    var scope="local scope";    //局部变量
    function f() {return scope;}    //在作用域中返回这个值
    return f();
    }
var a=checkscope();
document.write(a+"<br>")

对象

对象是一种复合值,它将很多值聚合在一起,可通过名字访问这些值。对象也可看作是属性的无序集合,每个属性都是一个名/值对。属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。

对象除了可以保持自有的属性外,还可以从一个称为“原型”的对象继承属性。

除了字符串、数字、truefalsenullundefined之外,JavaScript中的值都是对象。

除了包含属性之外,每个对象还拥有三个相关的对象特性:

对象的原型(prototype)指向另一个对象,本对象的属性继承自它的原型对象。

对象的类(class)是一个标识对象类型的字符串。

对象的扩展标记(extensible flag)指明了(在ECMAScript 5中)是否可以向该对象添加新属性。

JavaScript对象的类别

  • 内置对象:是由ECMAScript规范定义的对象或类。例如,数组,函数,日期和正则表达式。
  • 宿主对象:是由JavaScript解释器所嵌入的宿主环境(比如Web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象。
  • 自定义对象:是由运行中的JavaScript代码创建的对象。

创建对象

创建对象(3种方式):

  • 对象直接量:是由若干属性名/值

    var empty={};    //空对象,没有任何属性
    var point={x:0,y:0};    //两个属性
    var book={
        "main title":"JavaScript",      //属性名字里有空格,必须用字符串表示
        "sub-title":"The Definitive Guide",     //属性名字里有连字符,必须用字符串表示
        "for":"all audiences",      //"for"是保留字,因此必须用引号
        author:{            //这个属性的值是一个对象
            firstname:"Shulin",
            lastname:"Chen"
        }
    };
    
  • 通过new创建对象:new关键字创建并初始化一个新对象,new后跟随一个函数调用。这里的函数称作构造函数。例如:

    var author=new Object(); //创建一个空对象
    author.firstname="Shulin";
    author.lastname="Chen";
    
    var mycars=new Array();
    mycars[0]="Saab";
    mycars[1]="Volvo";
    
    var today = new Date();  //Date 对象自动使用当前的日期和时间作为其初始值。
    
  • Object.create(proto[, propertiesObject]) 是ECMAScript 5中提出的一种新的对象创建方式,第一个参数是要继承的原型,也可以传一个null,第二个参数是对象的属性描述符,这个参数是可选的。例如:

    var o1 = Object.create({x:1,y:2});  //o1继承了属性x和y
    var o2 = Object.create(Object.prototype);   //o2和{}以及new Object()一样,创建了一个普通的空对象
    
    • 如果proto参数不是 null 或一个对象,则抛出一个 TypeError 异常。

    • 在ECMAScript 3中可以用类似下面的代码来模拟原型继承:

      chap3-18.js

      //inherit()返回了一个继承自原型对象p的属性的新对象
      //这里使用ECMAScript 5中的Object.create()函数(如果存在的话)
      //如果不存在Object.create(),则退化使用其它方法
      function inherit(p) {
          if(p==null) throw TypeError();      //p是一个对象,但不能是null
          if(Object.create) return Object.create(p);      //如果Object.create()存在,直接使用它
          var t=typeof p;
          if(t!=="object" && t!=="function") throw TypeError();
          function f() {};    //定义一个空构造函数
          f.prototype=p;      //将其原型属性设置为p
          return new f();     //使用f()创建p的继承对象
          }
      //Inherit()函数的其中一个用途就是防止函数无意间(非恶意地)修改那些不受你控制的对象。
      // 不是将对象直接作为参数传入函数,而是将它的继承对象传入函数。
      //如果给继承对象的属性赋值,则这些属性只会影响这个继承对象自身,而不是原始对象。
      var o={x:"don't change this value"};
      changex(inherit(o));
      function changex(obj) {
          obj.x="hello world!";
          document.write(obj.x+"<br>");
          }
      document.write(o.x+"<br>");
      changex(o);
      document.write(o.x+"<br>");
      

属性的查询和设置

JavaScript为属性访问定义了两种语法:

对象名.属性名对象名[表达式]

其中,表达式指定要访问的属性的名称或者代表要访问数组元素的索引。

对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符(不能有空格、连字符等)。点运算符后的标识符不能是保留字,比如book.for是非法的,必须使用方括号的形式访问它们,比如book["for"]

对于方括号([])来说,方括号内必须是一个计算结果为字符串的表达式。其看起来更像数组,只是这个数组元素是通过字符串索引而不是数字索引。这种数组称为“关联数组”。

chap3-19.html

<script>
    var book={
        "main title":"JavaScript",
        "sub-title":"The Definitive Guide",
        "for":"all audiences",
        author:{
            firstname:"Shulin",
            lastname:"Chen"
        }
    };
    var a=[book,4,[5,6]];
    document.write(book.author.firstname+"<br>");   //获得book对象中author的“firstname”属性
    document.write(book["for"]+"<br>");
    document.write(a[0]["main title"]+"<br>");
    document.write(a[2][1]+"<br>");
    book["main title"]="ECMAScript 6";  //给“main title”属性赋值
</script>

JavaScript对象具有自有属性(实例属性),也有一些属性是从原型对象继承而来的(继承属性)。

假设要查询对象q的属性x,如果q中不存在x,则会继续在q的原型对象中查询属性x,如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型对象上执行查询,直到找到x或者查找到一个原型是null的对象为止。可以看到,对象的原型属性构成了一个“”,通过这个“”可以实现属性的继承。

chap3-20.html

<head>
    <meta charset="UTF-8">
    <title>chap3-20</title>
    <script src="js/chap3.js"></script>
    </head>
<body>
<script>
    var o={};   //o从Object.prototype继承对象的方法
    o.x=1;      //给o定义一个属性x
    var p=inherit(o);   //p继承o和Object.prototype
    p.y=2;      //给p定义一个属性y
    var q=inherit(p);   //q继承p、o和Object.prototype
    q.z=3;      //给q定义一个属性z
    document.write(q.x+q.y+q.z+"<br>");
    </script>
</body>

假设给对象o的属性x赋值,如果o中已经有属性x(这个属性不是继承来的),那么这个赋值操作只改变这个已有属性x的值。如果o中不存在属性x,那么赋值操作给o添加一个新的属性x。如果之前o继承自属性x,那么这个继承的属性就被新创建的同名属性覆盖了。

属性赋值操作首先检查原型链,以此判定是否允许赋值操作。如果o继承自一个只读属性x,那么赋值操作是不允许的。如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修改原型链。

chap3-20.js

var a={
    get r(){return 1;},
    x:1
    };
var b=inherit(a);   //b继承属性r
b.y=1;              //b定义了个属性
b.x=2;              //b覆盖继承来的属性x
b.r=3;              //r为只读属性,赋值语句无效
document.write(b.r+"<br>"); //输出1
document.write(b.x+"<br>"); //输出2
document.write(a.x+"<br>"); //原型对象没有修改

删除属性

delete运算符可以删除对象的属性。它的操作数是一个属性访问表达式:

delete只是断开属性和宿主对象的联系,而不会去操作对象中的属性。

delete运算符只能删除自有属性,不能删除继承属性,要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象。

chap3-21.js

var book={
    "main title":"JavaScript",
    "sub-title":"The Definitive Guide",
    "for":"all audiences",
    author:{
        firstname:"Shulin",
        lastname:"Chen"
    }
    };
delete book.author;     //book不再有属性author
delete book["main title"];  //book不再有属性"main title"
document.write(book.author+"<br>");
document.write(book["main title"]+"<br>");
var o=Object.create(book);  //o继承了book对象的属性
delete o["for"];    //不能删除继承属性
document.write(book["for"]+"<br>");

检测属性

判断某个属性是否存在于某个对象中可以有3种方式:

in运算符:如果对象的自有属性或继承属性中包含这个属性,则返回true

hasOwnProperty()方法:对象的自有属性返回true,对于继承属性返回false

propertyIsEnumerable()方法:只有检测到是自有属性且这个属性的可枚举性为true时,它才返回true。某些内置属性是不可枚举的。

var o={x:1};
var obj=Object.create(o);
obj.y=2;
"x" in obj;  //输出true
"y" in obj;  //输出true
obj.hasOwnProperty("x");  //输出false
obj.hasOwnProperty("y");  //输出true
obj.propertyIsEnumerable("x");   //输出false
obj.propertyIsEnumerable("y");  //输出true

枚举属性

在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。

JavaScript中基本包装类型的原型属性是不可枚举的,如Object, Array, Number等。

Object对象的propertyIsEnumerable()方法可以判断此对象是否包含某个属性,并且这个属性是否可枚举。

for/in循环可以遍历对象中所有可枚举的对象属性(包括对象自有属性和继承的属性)。

Object.keys()方法会返回一个由一个给定对象的自身可枚举属性组成的数组。

Object.getOwnPropertyNames()方法会返回一个由一个给定对象的自身属性组成的数组,包括可枚举和不可枚举的。

chap3-22.js

var po={px:1,py:2};
var o={x:3,y:4};
o.__proto__=po;     //设置o的原型为po
document.write("for/in方法输出结果:<br>");
for(property in o){
    document.write(property+":"+o[property]+"<br>");
}
var propertyArray=Object.keys(o);
document.write("定义枚举属性前Object.keys方法输出结果:<br>");
for(var i=0;i<propertyArray.length;i++){
    document.write(propertyArray[i]+"<br>");
}
Object.defineProperties(o,{
    x:{enumerable:true},
    y:{enumerable:false}
});
propertyArray=Object.keys(o);
document.write("定义枚举属性后Object.keys方法输出结果:<br>");
for(var i=0;i<propertyArray.length;i++){
    document.write(propertyArray[i]+"<br>");
}
propertyArray=Object.getOwnPropertyNames(o);
document.write("定义枚举属性后Object.getOwnPropertyNames方法输出结果:<br>");
for(var i=0;i<propertyArray.length;i++){
    document.write(propertyArray[i]+"<br>");
}

属性gettersetter

对象属性是由名字、值和一组特性(attribute)构成的。在ECMAScript 5中,属性值可以用一个或两个方法替代,这两个方法就是gettersetter。由gettersetter定义的属性称作“存取器属性”(accessorproperty),它不同于“数据属性”(data property)。

数据属性:包含属性的操作特性;如:设置值、是否可枚举等。

特性名称 描述 默认值
value 设置属性的值 undefined
writable 是否可修改属性的值;true:可修改属性的值;false:不可修改属性的值 false
enumerable 是否可枚举属性;true:可枚举,可通过for/in语句枚举属性;false:不可枚举 false
configurable 是否可修改属性的特性;true:可修改属性的特性(如把writablefalse改为true);false:不可修改属性的特性 false

存取器属性:包含属性的操作特性;如:设置值、是否可枚举等。

特性名称 描述 默认值
get 属性的返回值函数 undefined
set 属性的设置值函数;含有一个赋值参数 undefined
enumerable 是否可枚举属性;true:可枚举,可通过for/in语句枚举属性;false:不可枚举 false
configurable 是否可修改属性的特性;true:可修改属性的特性(如把writablefalse改为true);false:不可修改属性的特性 false

存取器也是可以继承的。

chap3-23.html

<script>
    var obj={};
    //添加一个属性,并设置为存取器属性
    Object.defineProperty(obj,"name",{
        get:function () {
            return this._name;  //get和set里的变量不要使用属性,如:属性为name,get和set用的是_name
        },
        set:function (x) {
            if(isNaN(x))    //isNaN() 函数用于检查其参数是否是非数字值。
                this._name=x;
            else
                this._name="name不能为纯数字";
        },
        enumerable:true,
        configurable:true
    });
    obj.name="12";
    document.write(obj.name+"<br>");
    var o=inherit(obj);     //存取器也是可以继承的
    o.name="a12";
    document.write(o.name+"<br>");
</script>

属性的特性

为了实现属性特性的查询和设置操作,ECMAScript 5中定义了一个名为“属性描述符”(property descriptor)的对象,这个对象代表数据属性特性和存取器属性特性。

在使用Object.definePropertyObject.definePropertiesObject.create函数的情况下添加数据属性,writableenumerableconfigurable默认值为false

使用对象直接量创建的属性,writableenumerableconfigurable特性默认为true

Object.getOwnPropertyDescriptor(object,propertyname)可用来获取描述属性特性的描述符对象。其中object为包含属性的对象,必需;propertyname为属性的名称,必需。

chap3-24.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-24</title>
    </head>
<body style="font-size: xx-large">
<script>
    var o1={name:"tom"};
    document.write("对象直接量,属性特性默认为true<br>");
    var desc=Object.getOwnPropertyDescriptor(o1,"name");
    for(var prop in desc)
        document.write(prop+":"+desc[prop]+"<br>");
    var o2=Object.create(null,{
        name:{value:"tom"}
    });
    document.write("通过Object.create创建,属性特性默认为false<br>")
    desc=Object.getOwnPropertyDescriptor(o2,"name");
    for(prop in desc)
        document.write(prop+":"+desc[prop]+"<br>");
        </script>
</body>
</html>

三个属性

原型属性

是用来继承属性的,指向另一个对象,本对象的属性继承自它的原型对象。

  • 通过对象直接量创建的对象使用Object.prototype作为它们的原型;

  • 通过new创建的对象使用构造函数的prototype属性来作为它们的原型;

  • 通过Object.create()来创建的对象使用第一个参数作为它们的原型。

在ECMAScript5中将对象作为参数传入Object.getPrototypeOf()可查询它的原型;

想要检测一个对象是否是另一个对象的原型(或处于原型链中),使用isPrototypeOf()

var p={x:1,y:2};
var o=Object.create(p);
document.write(p.isPrototypeOf(o)+"<br>");     //返回true
document.write(Object.prototype.isPrototypeOf(o));      //返回true
类属性

是一个字符串,用以表示对象的类型信息。只能通过toString()这种间接的方法查询对象的类信息。该方法返回如下格式的字符串:[object class]

很多对象继承的toString方法被重写过,因此必须间接的调用Function.call()方法:

chap3-25.html

<script>
    function classOf(o) {
        if(o===null) return "null";
        if(o===undefined) return "undefined";
        return Object.prototype.toString.call(o).slice(8,-1);//提取返回字符串的第8个到倒数第二个位置之间的字符
    }
    document.write("null值类属性:"+classOf(null)+"<br>");
    document.write("数组类属性:"+classOf(new Array())+"<br>");
    document.write("对象类属性:"+classOf({})+"<br>");
    document.write("字符串类属性:"+classOf("")+"<br>");
    document.write("数值类属性:"+classOf(1)+"<br>");
    document.write("布尔值类属性:"+classOf(true)+"<br>");
    document.write("日期类属性:"+classOf(new Date())+"<br>");
    document.write("正则表达式类属性:"+classOf(new RegExp())+"<br>");
    document.write("客户端宿主对象类属性:"+classOf(window)+"<br>");
    function f() {};
    document.write("函数类属性:"+classOf(f)+"<br>");
    document.write("函数对象类属性:"+classOf(new f())+"<br>");
</script>
可扩展属性

用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的。

Object.isExtensible()判断对象是否是可扩展的。

Object.preventExtensions()可将对象转换为不可扩展的,一旦对象转换成不可扩展的了就不能转回可扩展的了。

类和模块

在JavaScript中也可以定义对象的类,让每个对象都共享某些属性,这种"共享"的特性是非常有用的。类的成员或实例都包含一些属性,用以存放它们的状态,其中有些属性定义了它们的行为(通常称为方法)。这些行为通常是由类定义的,而且为所有实例所共享。

在JavaScript中,类的实现是基于其原型继承机制的。如果两个实例都从一个原型对象上继承了属性,我们说它们是同一个类的实例。

JavaScript中类的一个重要特征是"动态可继承"(dynamically extendable),定义类是模块开发和重用代码的有效方式之一。

类和原型

在JavaScript中,类的所有实例对象都从一个类型对象上继承属性。因此,原型对象是类的核心。

在chap3.18中定义了inherit()函数,这个函数返回一个新创建的对象,然后继承自某个原型对象。如果定义了一个原型对象,然后通过inherit()函数创建了一个继承自它的对象,这样就定义了一个JavaScript类。通常,类的实例还需要进一步的初始化,通常是通过定义一个函数来创建并初始化这个新对象。

chap3-26.html

<head><script src="js/chap3.js"></script></head>
<script>
    // range: 实现一个能表示值的范围的类
    function range(from,to){
        //使用inherit()函数来创建对象,这个对象继承自下面定义的原型对象
        //原型对象作为函数的一个属性存储,并定义所有“范围对象”所共享的方法(行为)
        var r = inherit(range.methods);
        //储存新的“范围对象”启始位置和结束位置(状态)
        //这两个属性是不可继承的,每个对象都拥有唯一的属性
        r.from = from;
        r.to = to;
        //返回这个新创建的对象
        return r;
    }
    //原型对象定义方法,这些方法为每个范围对象所继承
    range.methods = {
        //如果x在范围内,则返回true;否则返回false
        //如果这个方法可以比较数字范围。也可以比较字符串和日期范围
        includes:function(x){
            return this.from <= x && x <= this.to;},
        //对于范围内每个整数都调用一次f
        //这个方法只可用作数字范围
        foreach:function (f){
            for (var x = Math.ceil(this.from); x <= this.to ; x++) f(x);
        },
        //返回表示这个范围的字符串
        toString:function (){return "("+ this.from + "..." + this.to + ")";}
    };
    //这是使用范围对象的一些例子
    var r =range(1,3); //创建一个范围对象
    console.log(r.includes(2)); //true:2 在这个范围内
    r.foreach(console.log);
    console.log(r.toString());
</script>

这段代码定义了一个工厂方法range(),用来创建新的范围对象。我们注意到,这里给range()函数定义了一个属性range.methods,用以便捷地存放定义类的原型对象。

注意range()函数给每个范围对象定义了fromto属性,用以定义范围的起始位置和结束位置,这两个属性是非共享的,当然也是不可继承的。range.methods方法都用到了fromto属性,而且使用了this关键字,为了指代它们,二者使用this关键字来指代调用这个方法的对象。任何类的方法都可以通过this的这种基本用法来读取对象的属性。

类和构造函数

构造函数是用来初始化和创建对象的。使用new调用构造函数会创建一个新对象,因此,构造函数本身只需要初始化这个新对象的状态即可。调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型。这意味着通过同一个构造函数创建的对象都是继承自一个相同的对象,因此它们都是一个类的成员。

chap3-27.html

<script>
    //表示值的范围的类的另一种实现
    //这是一个构造函数,用以初始化新创建的“范围对象”
    //注意,这里并没有创建并返回一个对象,仅仅是初始化
    function Range(from, to) {
        //存储这个“范围对象”的起始位置和结束位置(状态)
        //这两个属性是不可继承的,每个对象都拥有唯一的属性
        this.from = from;
        this.to = to;
    }
    //所有的“范围对象”都继承自这个对象
    //属性的名字必须是"prototype"
    Range.prototype = {
        //如果x在范围内,则返回true;否则返回false
        //这个方法可以比较数字范围,也可以比较字符串和日期范围
        includes: function(x) {
            return this.from <= x && x <= this.to;
        },
        //对于这个范围内的每个整数都调用一次f
        //这个方法只可用于数字范围
        foreach: function(f) {
            for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
        },
        //返回表示这个范围的字符串
        toString: function() {
            return "(" + this.from + "..." + this.to + ")";
        }
    };
    //这里是使用“范围对象”的一些例子
    var r = new Range(1, 3); //创建一个范围对象
    console.log(r.includes(2));
    r.foreach(console.log);
    console.log(r.toString());
</script>

一个常见的编程约定:从某种意义上来讲,定义构造函数即是定义类,并且类首字母要大写,而普通的函数和方法首字母都是小写。

Range()函数就是通过new关键字来调用的,构造函数会自动创建对象,然后将构造函数作为这个对象的方法来调用一次,最后返回这个新对象。

上面两个例子有一个非常重要的区别:就是原型对象的命名。在第一段示例代码中的原型是range.methods。这种命名方式很方便同时具有很好的语义,但有过于随意。在第二段代码中的原型是Range.prototype,这是一个强制命名。对Range()构造函数的调用会自动使用Range.prototype作为新Range对象的原型。

构造函数和类的标识

原型对象是类的唯一标识:当且仅当两个对象继承自同一个原型对象时,它们才是属于同一个类的实例。而初始化对象的状态的构造函数则不能作为类的标识,两个构造函数的prototype属性可能指向同一个原型对象。那么这两个构造函数创建的实例是属于一个类的。

尽管构造函数不像原型那样基础,但构造函数是类的"外在表现"。很明显,构造函数的名字通常用做类名。比如,我们说Range()构造函数创建Range对象,然而,更根本地讲,当使用instanceof运算符来检测对象是否属于某个类时会用到构造函数。假设这里有一个对象r,我们想知道r是否是Range对象,我们来这样写:

r instanceof Range // 如果r继承自Rang.prototype,则返回true 

任何JavaScript函数都可以用做构造函数,并且调用构造函数是需要用到一个prototype属性,因此,每个JavaScript函数都自动拥有一个prototype属性。这个属性的值是一个对象,这个对象包含唯一一个不可枚举的属性constructorconstructor属性的值是一个函数对象:

var F = function() {}; //这是一个函数对象:
var p = F.prototype; //这是F相关联的原型对象
var c = p.constructor; //这是与原型相关的函数
c === F; //=>true  对于任意函数F.prototype.constructor == F

下图展示了构造函数和原型之间的关系,包括原型到构造函数的反向引用及构造函数创建的实例。

构造函数和原型之间的关系

注意:在上面的例子中,Range重新定义了prototype,所以创建对象的constructor属性将不再是Range(),而是直接使用Object.prototype.constructor,即Object()。我们可以通过补救措施来修正这个问题,显式的给原型添加一个构造函数:

Chap3-28.html

Range.prototype = {
    constructor:Range,  //显式的设置构造函数反向引用
    includes: function(x) {
        return this.from <= x && x <= this.to;
    },
    //对于这个范围内的每个整数都调用一次f
    //这个方法只可用于数字范围
    foreach: function(f) {
        for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
    },
    //返回表示这个范围的字符串
    toString: function() {
        return "(" + this.from + "..." + this.to + ")";
    }
};

另外一种常见的解决办法是使用预定义的原型对象,预定义的原型对象包含constructor属性,然后依次给原型对象添加方法:

Chap3-29.html

//扩展预定义的Range.prototype对象,而不重写之
//这样就自动创建Range.prototype.constructor属性
Range.prototype.includes=function(x){
    return this.from <= x && x <= this.to;
};
//对于这个范围内的每个整数都调用一次f
//这个方法只可用于数字范围
Range.prototype.foreach = function(f) {
    for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
};
//返回表示这个范围的字符串
Range.prototype.toString=function() {
    return "(" + this.from + "..." + this.to + ")";
};

在JavaScript中定义类的步奏可以缩减为一个分三步的算法。

  1. 先定义一个构造函数,并设置初始化新对象的实例属性。
  2. 给构造函数的prototype对象定义实例的方法。
  3. 给构造函数定义类字段和类属性。

Complex.js

/*这个文件定义了Complex类,用来描述复数。这个构造函数为它所创建的每个实例定义了实例字段r和i,分别保存复数的实部和虚部*/
function Complex(real, imaginary) {
    this.r = real;                       // The real part of the complex number.
    this.i = imaginary;                  // The imaginary part of the number.
}
/*类的实例方法定义为原型对象的函数值的属性
*这个库定义的方法可以被所有实例继承,并为它们提供共享的行为
*需要注意的是,javascript的实例方法必须使用关键字this才存取实例的字段*/
Complex.prototype.add = function(that) {
    return new Complex(this.r + that.r, this.i + that.i);
};
Complex.prototype.equals = function(that) {
    return that != null &&                      // must be defined and non-null
        that.constructor === Complex &&         // and an instance of Complex
        this.r === that.r && this.i === that.i; // and have the same values.
};
Complex.prototype.toString = function() {
    return "{" + this.r + "," + this.i + "}";
};
//类字段(比如常量)和类方法直接定义为构造函数的属性
//这里定义了一些对复数运算有帮助的类的字段,它们的命名全都是大写,用以表明它们是常量
Complex.ZERO = new Complex(0,0);
Complex.I = new Complex(0,1);

Chap3-30.htnl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-30</title>
    <script src="js/Complex.js"></script>
</head>
<body style="font-size: x-large">
<script>
    var c = new Complex(2, 3); //使用构造函数创建新对象
    var d = new Complex(c.i,c.r); //用到了c的实例属性
    document.write(c.add(d).toString()+"<br>"); // {5,5}:使用了实例的方法
    var e=new Complex(0,1);
    document.write(e.equals(Complex.I));
</script>
</body>
</html>

类的扩充

JavaScript中基于原型的继承机制是动态的:对象从其原型继承属性,如果创建对象之后原型的属性发生改变,也会影响到继承这个原型的所有实例对象。这意味着我们可以通过给原型对象添加新的方法来扩充JavaScript类。

Chap3-31.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-31</title>
    <script src="js/Complex.js"></script>
</head>
<body>
<script>
    if(!Complex.prototype.mag){
        //计算复数的模,复数的模的定义为原点(0,0)到复平面的距离
        Complex.prototype.mag = function() {
            return Math.sqrt(this.r*this.r + this.i*this.i);
        };
    }
    var c = new Complex(2,3);
    document.write(c.mag());
</script>
</body>
</html>

正则表达式的模式匹配

JavaScript中的正则表达式用RegExp对象表示,可以使用RegExp()构造函数来创建RegExp对象,不过RegExp对象更多是通过一种特殊的直接语法量来创建;就像通过引号包裹字符的方式来定义字符串常量一样,这种表达式直接量定义为包含在一堆斜杠(/)之间的字符。例如:

var pattern=/s$/;   //用来匹配所有以字母“s”结尾的字符串

也可以用构造函数RegExp()来定义,例如:

var pattern1=new RegExp("s$");

正则表达式的模式规则是一个字符序列组成的。包括所有字母和数字在内,大多数的字符都是按照直接量仅描述待匹配的字符的。例如/java/可以匹配任何包含"java"子串的字符串。

直接量字符

正则表达式中所有字符和数字都是按照字面含义进行匹配的。javascript正则表达式语法也支持非字母的字符匹配,这些字符需要通过反斜线(\)作为前缀进行转义。

javascript正则表达式中的直接量字符:

字符 匹配
字母和数字字符 自身
\o NUL字符(\u0000)
\t 制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\xnn 由十六进制数指定的拉丁字符,例如\x0A等价于\n
\uxxxx 由十六进制数xxxx指定的Unicode字符,例如\u0009等价于\t

字符类

将直接量字符单独放进方括号内就组成了字符类(character class)。一个字符类可以匹配它包含的任意字符。例如,/[abc]/就和字母“a”、“b”、“c”中的任意一个都匹配。

字符 匹配
[...] 方括号内的任意字符
[^...] 不在方括号内的任意字符
. 除换行符和其它Unicode行终止符之外的任意字符
\w 任何ASCII字符组成的单词,等价于[a-zA-Z0-9]
\W 任何不是ASCII字符组成的单词,等价于[^a-zA-Z0-9]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符
\d 任何ASCII数字,等价于[0-9]
\D 除了ASCII数字之外的任何字符,等价于[^0-9]

注意:在方括号之内也可以写成这些特殊转义字符。比如,由于\s匹配所有的空白字符,\d匹配的是所有数字,因此,/[\s\d]/就是匹配任意空白或数字。

Chap3-32.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-32</title>
    <style type="text/css">
        label{font-size: xx-large}
        p>:invalid{outline: medium solid red}
        p>:valid{outline: medium dotted green}
    </style>
</head>
<body>
<p>
    <label for="a">ASCII字符:</label>
    <input type="text" id="a" pattern="\w">
</p>
<p>
    <label for="b">ASCII数字:</label>
    <input type="text" id="b" pattern="\d">
</p>
</body>
</html>

重复

字符 含义
{n,m} 匹配前一项至少n次,但不能超过m次
{n,} 匹配前一项n次或者更多次
{n} 匹配前一项n次
? 匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1}
+ 匹配前一项1次或多次,等价于{1,}
* 匹配前一项0次或多次,等价于{0,}
/\d{2,4}/        //匹配2~4个数字 
/\w{3}\d?/       //精确匹配三个单词和一个可选的数字 
/\s+java\s+/    //匹配前后带一个或多个空格的字符串“java” 
/[^(]*/         //匹配1个或多个非左括号的字符 

在使用*?时要注意,由于这些字符可能匹配0个字符,因此它们允许什么都不匹配。例如正则表达式/a*/实际上与字符串“bbbb”匹配,因为这个字符串含有0个a。

Chap3-33.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-33</title>
    <style type="text/css">
        label{font-size: xx-large}
        p>:invalid{outline: medium solid red}
        p>:valid{outline: medium dotted green}
    </style>
</head>
<body>
<p>
    <label for="a">匹配6~8个ASCII字符:</label>
    <input type="text" id="a" pattern="\w{6,8}">
</p>
<p>
    <label for="b">匹配3个ASCII字符和1个可选的ASCII数字:</label>
    <input type="text" id="b" pattern="\w{3}\d?">
</p>
</body>
</html>

非贪婪的重复

上表中匹配重复字符是尽可能多地匹配,而且允许后继的正则表达式继续匹配。因此,我们称之为“贪婪的”匹配。

我们同样可以使用这种表达式进行“非贪婪”的匹配。只需在待匹配的字符后跟随一个问号即可:??+?*?{1,5}?。比如,正则表达式/a+/可以匹配一个或多个连续的字母a。当使用“aaa”作为匹配的字符串时,正则表达式会匹配它的三个字符。但是/a+?/也可以匹配一个或多个连续字母a,但它是尽可能少的匹配。我们同样将“aaa”作为匹配字符串,但最后一个模式只能匹配第一个a。

使用非贪婪的匹配模式所得到的结果可能和期望的并不一致。例如,/a+b/,当使用它来匹配“aaab”时,它会匹配了整个字符串。而/a+?b/,它匹配尽可能少的a和一个b,当它用来匹配“aaab”时,你期望它能匹配一个a和最后一个b。但实际上,这个模式却匹配了整个字符串。这是因为正则表达式的匹配模式总是会寻找字符串中第一个可能匹配的位置。由于该匹配是从字符串的第一个字符开始的,因此在这里不考虑它的子串中更短的匹配。

选择、分组和引用

正则表达式的语法还包括指定选择项、子表达式分组和引用前一子表达式的特殊字符。

字符 含义
` ` 选择,匹配的是该符号左边的子表达式或右边的子表达式
(...) 组合,将几个项组合成一个单元,这个单元可通过*+?和` `等符号加以修饰,而且可以记住和这个组合相匹配的字符串以供此后的引用使用
(?:...) 只组合,将项组合到一个单元,但不记忆与该组相匹配的字符
\n 和第n个分组第一次匹配的字符相匹配,组是圆括号中子表达式(也有可能是嵌套的),组索引是从左到右的左括号数,(?:形式的分组不编码

字符|用于分隔供选择的字符。例如:

/ab|cd|ef/      //可以匹配字符串"ab",也可以匹配字符串"cd",还可以匹配字符串"ef"
/\d{3}|[a-z]{4}/   //可以匹配三位数字或4个小写字母

注意:选择项的尝试匹配总是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项。即使它产生更好的匹配。因此,当正则表达式/a|ab/匹配字符串“ab”时,它只能匹配第一个字符。

正则表达式中的圆括号有多种作用。

一个作用是把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用|*+或者?等来对单元内的项进行处理。例如:

/java(script)?/     //可以匹配字符串"java",其后可以有"script"也可以没有
/(ab|cd)+|ef/       //可以匹配字符串"ef",也可以匹配字符串"ab"或"cd"的一次或多次重复

Chap3-34.html

<body>
<p>
    <label for="a">匹配"ab",或"cd",或"ef":</label>
    <input type="text" id="a" pattern="ab|cd|ef">
</p>
<p>
    <label for="b">匹配三位数字或4个小写字母:</label>
    <input type="text" id="b" pattern="\d{3}|[a-z]{4}">
</p>
<p>
    <label for="b">匹配字符串"java",或"javascript":</label>
    <input type="text" id="b" pattern="java(script)?">
</p>
<p>
    <label for="a">匹配"ef",或匹配字符串"ab"或"cd"的一次或多次重复:</label>
    <input type="text" id="a" pattern="(ab|cd)+|ef">
</p>
</body>

另一个作用是允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符\后加一位数字来实现的。这个数字指定了带圆括号的子表达式在正则表达式中的位置。例如:

/([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/       //嵌套的子表达式([Ss]cript)可以用\2来指代

注意:因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。

/['"][^'"]*['"]/       //匹配的就是位于单引号或双引号之内的0个或多个字符。
/(['"])[^'"]*\1/        //与上面的正则表达式等价

在正则表达式中不用创建带数字编码的引用,也可以对子表达式进行分组。它是以(?:...)来进行分组。例如:

//这种改进的圆括号并不生成引用。所以在这个表达式中,\2引用了与(fun\W*)匹配的文本。
/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/

指定匹配位置

正则表达式中的锚字符:

字符 含义
^ 匹配字符串的开头,在多行检索中,匹配一行的开头
$ 匹配字符串的结尾,在多行检索中,匹配一行的结尾
\b 匹配一个单词的边界,简而言之,就是位于字符\w\W之间的位置,或位于字符\w和字符串开头或者结尾之间的位置。(但需要注意,[\b]匹配的是退格符)
\B 匹配非单词边界的位置
(?=p) 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符
(?!p) 零宽负向先行断言,要求接下来的字符不与p匹配

最常用的锚元素是^,它用来匹配字符串的开始,锚元素$用来匹配字符串的结束。例如:

/^JavaScript$/       //要匹配单词“JavaScript”

如果想匹配“Java”这个单词本身,可以使用/\sJava\s/,可以匹配前后都有空格的单词"Java"。但是这样做有两个问题,第一。如果“Java”出现在字符串的开始或者结尾,就匹配不成功,除非开始和结尾处各有一个空格。第二个问题是,当找到了与之匹配的字符串时,它返回的匹配字符串的前端和后端都有空格。因此,我们使用单词的边界\b来代替真正的空格符\s进行匹配(或定位)。这样的正则表达式就写成了/\bJava\b/

元素\B把匹配的锚点定位在不是单词的边界之处。因此,正则表达式/\B[Ss]cript/与“JavaScript”和“postscript”匹配,但不与“script”和“Scripting”匹配。

任意正则表达式都可以作为锚点条件。如果在符号(?=)之间加入一个表达式,它就是一个先行断言,用以说明圆括号内的表达式必须正确匹配,但不是真正意义上的匹配,可以使用/[Jj]ava([Ss]cript)?(?=\:)/。这个正则表达式可以匹配“Jvascript: The Definitive Guide”中的“javascript”,但不能匹配“Java in NutShell”中的“Java”,因为它后面没有冒号。

带有(?!的断言是负向先行断言,用以指定接下来的字符都不必匹配。例如:/Java(?!Script)([A-Z]\w*)/可以匹配“Java”后跟随一个大写字母和任意多个ASCII单词,但“Java”后面不能跟随“Script”。它可以匹配“JavaBeans”,但不能匹配“Javabeans”。

Chap3-35.html

<head><style type="text/css">
        label{font-size: xx-large}
        p>:invalid{outline: medium solid red}
        p>:valid{outline: medium dotted green}
</style></head>
<body>
<p>
    <label for="a">匹配位于单引号之内的0个或多个字符:</label>
    <input type="text" id="a" pattern="(['])[^']*\1">
</p>
<p>
    <label for="a">要匹配单词“JavaScript”:</label>
    <input type="text" id="a" pattern="^JavaScript$">
</p>
<p>
    <label for="a">要匹配前后都有空格的单词“Java”:</label>
    <input type="text" id="a" pattern="\sJava\s">
</p>
<p>
    <label for="a">要匹配单词“Java”:</label>
    <input type="text" id="a" pattern="\bJava\b">
</p>
</body>

修饰符

和之前讨论的正则表达式语法不同,修饰符是放在/符号之外的。Javascript支持三个修饰符:

字符 含义
i 执行不区分大小写的匹配
g 执行一个全局匹配,简言之,即找到所有的匹配,而不是在找到第一个之后就停止
m 多行匹配模式,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束
/java$/im   //可以匹配java,也可以匹配"Java\nis Fun".

用于模式匹配的String方法

String支持4种使用正则表达式的方法:

  • search():它的参数是一个正则表达式,返回第一个与之匹配的子串的位置,如果找不到匹配的子串,它将返回-1

    document.write("javascript".search(/script/i));     //返回4
    
  • replace():用于执行检索与替换操作。其中第一个参数是正则表达式,第二个参数是要进行替换的字符串。

    var text="javascript and Javascript and javascript";
    document.write(text.replace(/javascript/gi,"JavaScript"));
    
  • match():它唯一的参数就是一个正则表达式,返回的是一个由匹配结果组成的数组。如果该正则表达式设置了修饰符g,则该方法返回的数组包含字符串中所有匹配的结果。

    text="1 plus 2 equals 3";
    var arr=text.match(/\d+/g);
    
  • split():将调用它的字符串拆分为一个子串组成的数组,使用的分割符是split()的参数。

    text="1, 2, 3, 4, 5";
    var arr=text.split(/\s*,\S*/);  //允许两边可以留任意多的空白符
    

RegExp对象

正则表达式是通过RegExp对象来表示的。 RegExp()构造函数带有两个字符串参数,其中第一个参数包含正则表达式的主体部分,需要注意的是,不论是字符串直接量还是正则表达式,都是用\字符作为转义字符的前缀。第二个参数是可选的,它指定正则表达式的修饰符。

//全局匹配字符串中的6个数字,注意这里使用了"\\",而不是“\”
var zipcode=new RegExp("\\d{6}","g");
RegExp对象的属性
  • source属性:是一个只读的字符串,包含正则表达式的文本;

  • global属性:是一个只读的布尔值,用以说明这个正则表达式是否带有修饰符g

  • ignoreCase属性:是一个只读的布尔值,用以说明正则表达式是否带有修饰符i

  • multiline属性:是一个只读的布尔值,用以说明正则表达式是否带有修饰符m

  • lastIndex属性:是一个可读/写的整数,如果匹配模式带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec()test()方法用到。

Chap3-36.html

<script>
    document.write("javascript".search(/script/i));     //返回4
    var text="javascript and Javascript and javascript";
    document.write("<br>");
    document.write(text.replace(/javascript/gi,"JavaScript"));
    document.write("<br>");
    text="1 plus 2 equals 3";
    var arr=text.match(/\d+/g);
    for(var i in arr)
        document.write(arr[i]+"<br>");
    text="1, 2, 3, 4, 5";
    var arr=text.split(/\s*,\S*/);  //允许两边可以留任意多的空白符
    for(var i in arr)
        document.write(arr[i]);
    //全局匹配字符串中的6个数字,注意这里使用了"\\",而不是“\”
    var zipcode=new RegExp("\\d{6}","g");
    document.write("<br>");
    document.write(zipcode.source+"<br>");
    document.write(zipcode.global+"<br>");
</script>
RegExp对象的方法

exec()方法:用于检索字符串中的正则表达式的匹配。语法:

RegExpObject.exec(string)
  • 如果exec()找到了匹配的文本,则返回一个结果数组。否则,返回null。此数组的第0个元素是与正则表达式相匹配的文本,第1个元素是与RegExpObject 的第1个子表达式相匹配的文本(如果有的话),第2个元素是与RegExpObject 的第2个子表达式相匹配的文本(如果有的话),以此类推。

  • 在调用非全局RegExp对象的exec()方法时,返回的数组与调用方法String.match()返回的数组是相同的。

  • RegExpObject是一个全局正则表达式时,它会在RegExpObjectlastIndex属性指定的字符处开始检索字符串string。当exec()找到了与表达式相匹配的文本时,在匹配后,它将把RegExpObjectlastIndex属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用exec()方法来遍历字符串中的所有匹配文本。当exec()再也找不到匹配的文本时,它将返回null

    注意:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把lastIndex属性重置为0。

test()方法:用于检测一个字符串是否匹配某个模式。语法:

RegExpObject.test(string) 
  • 如果字符串string中含有与RegExpObject匹配的文本,则返回true,否则返回false

Chap3-37.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-37</title>
</head>
<body>
<script>
    var str="Visit W3School";
    var patt=new RegExp("W3School","g");
    var result;
    while ((result=patt.exec(str))!=null){
        document.write(result+"<br>");
        document.write(patt.lastIndex+"<br>");
    }
    var patt1=new RegExp("W3School");
    var result=patt1.test(str);
    document.write("Result: "+result+"<br>");
</script>
</body>
</html>

客户端JavaScript

  • document对象

  • Window对象

  • 使用DOM元素

  • 脚本化CSS

  • 脚本化HTTP

  • jQuery类库

  • 客户端存储

  • JSON

document对象

文档对象模型DOM把JavaScript和HTML文档的内容联系起来。通过使用DOM,可以添加、移除和操作各种元素,还可以使用事件(event)来响应用户的交互操作,以及完全控制CSS。

document对象是通往DOM功能的入口,它向你提供了当前文档的信息,以及一组可供探索、导航、搜索或操作结构与内容的功能。

问题 解决方案
获取文档信息 使用document元数据属性
获取文档位置信息 使用document.location属性
导航至某个新文档 修改Location对象的某个属性值
读取和写入cookie 使用document.cookie属性
判断浏览器处理文档的进展情况 使用document.readystate属性
获取浏览器已实现的DOM功能详情 使用document.implementation属性
获取代表特定元素类型的对象 使用document属性,如imageslinks
在文档中搜索元素 使用以document.getElement开头的各种方法
用CSS选择器在文档中搜索元素 使用document.querySelectordocument.querySelectorAll方法
合并进行链式搜索以寻找元素 对之前搜索的结果调用搜索方法
在DOM树中导航 使用文档/元素的方法与属性,如hasChildNodes()

使用document元数据

元数据属性 说明
characterSet 返回文档的字符集编码。只读
charset 获取或设置文档的字符集编码
compatMode 获取文档的兼容性模式
cookie 获取或设置当前文档的cookie
defaultCharset 获取浏览器所使用的默认字符编码
dir 获取或设置文档的文本方向
domain 获取或设置当前文档的域名
implementation 提供可用DOM功能的信息
lastModified 返回文档的最后修改时间(如果修改时间不可用则返回当前时间)
location 提供当前文档的URL信息
readyState 返回当前文档的状态。只读
referrer 返回链接到当前文档的文档URL
title 获取或设置当前文档的标题

Chap3-38.html

<head>
    <meta charset="UTF-8">
    <title>chap3-38</title>
    <style type="text/css">
        pre{font-size: xx-large}
    </style>
</head>
<body>
<script>
    document.dir="rtl";
    document.writeln("<pre>");
    document.writeln("characterSet:"+document.characterSet);
    document.writeln("charset:"+document.charset);
    document.writeln("compactMode:"+document.compatMode);
    document.writeln("defaultCharset:"+document.defaultCharset);
    document.writeln("dir:"+document.dir);
    document.writeln("domain:"+document.domain);
    document.writeln("lastModified:"+document.lastModified);
    document.writeln("referrer:"+document.referrer);
    document.writeln("title:"+document.title);
    document.writeln("</pre>");
</script>

compatMode属性返回值有两个:

  • CSS1Compat:代表此文档遵循某个有效的HTML规范(但不必是HTML5,有效的HTML4文档也会返回这个值)

  • BackCompat:代表此文档含有非标准的功能,已触发怪异模式

使用Location对象

Location对象的属性和方法 说明
protocol 获取或设置文档URL的协议部分
host 获取或设置文档URL的主机和端口部分
href 获取或设置当前文档的地址
hostname 获取或设置文档URL的主机名部分
port 获取或设置文档URL的端口部分
pathname 获取或设置文档URL的路径部分
search 获取或设置文档URL的查询(问号串)部分
hash 获取或设置文档URL的锚(井号串)部分
assign(URL) 导航到指定的URL上
replace(URL) 清除当前文档并导航到URL所指定的那个文档
reload() 重新载入当前的文档
resolveURL(URL) 将指定的相对URL解析成绝对URL

Chap3-39.html

<body>
<button id="pressme">Press Me</button>
<script>
    document.writeln("<pre>");
    document.writeln("protocol:"+document.location.protocol);
    document.writeln("host:"+document.location.host);
    document.writeln("hostname:"+document.location.hostname);
    document.writeln("href:"+document.location.href);
    document.writeln("port:"+document.location.port);
    document.writeln("pathname:"+document.location.pathname);
    document.writeln("search:"+document.location.search);
    document.writeln("hash:"+document.location.hash);
    document.writeln("</pre>");
    document.getElementById("pressme").onclick=function () {
        document.location.assign("chap3-38.html");
    }
</script>

</body>

读取和写入cookie

cookie属性让你可以读取、添加和更新文档所关联的cookie。cookie是形式为name=value的名称/值对。如果存在多个cookie,那么cookie属性会把它们一起返回,之间以分号间隔。

在创建cookie时,可以添加额外字段来改变cookie的处理方式:

额外项 说明
path=path 设置cookie关联的路径,如果没有指定则默认使用当前文档的路径
domain=domain 设置cookie关联的域名,如果没有指定则默认使用当前文档的域名
max-age=seconds 设置cookie的有效期,以秒的形式从它创建之时起开始计算
expires=date 设置cookie的有效期,用的是GMT格式的日期,如果没有指定则cookie会在对话结束时过期
secure 只有在安全(HTTPS)连接时才会发送cookie

Chap3-40.html

<body><p id="cookiedata"></p><button id="write">Add Cookie</button>
<button id="update">Update Cookie</button><button id="read">Read Cookie</button>
<button id="maxage">max-age</button>
<script>
    var cookieCount=0;
    document.getElementById("update").onclick=function () {
        document.cookie="Cookie_"+cookieCount+"=Updated_"+cookieCount;
        readCookies();
    }
    document.getElementById("write").onclick=function () {
        cookieCount++;
        document.cookie="Cookie_"+cookieCount+"=Value_"+cookieCount;
        readCookies();
    }
    document.getElementById("read").onclick=function () {
        readCookies();
    }
    document.getElementById("maxage").onclick=function () {
        document.cookie="Cookie_"+cookieCount+"=Updated_"+cookieCount+";max-age=5";
    }
    function readCookies() {
        document.getElementById("cookiedata").innerHTML=document.cookie;
    }
</script></body>

readyState属性

document.readyState属性提供了加载和解析HTML文档过程中当前处于哪个阶段的信息。在默认情况下,浏览器会在遇到文档里的script元素时立即开始执行脚本,但你可以使用defer属性推迟脚本的执行。

readyState 说明
loading 浏览器正在加载和处理此文档
interactive 文档已被解析,但浏览器还在加载其中链接的资源(图像和媒体文件等)
complete 文档已被解析,所有的资源也已加载完毕

Chap3-41.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chap3-41</title>
</head>
<body>
<script>
    document.onreadystatechange=function () {
        if(document.readyState=="interactive"){
            document.getElementById("pressme").onclick=function () {
                document.getElementById("results").innerHTML="Button Pressed";
            }
        }
    }
</script>
<button id="pressme">Press Me</button>
<p id="results"></p>
</body>
</html>

使用属性获取HTML元素对象

属性 说明
activeElement 返回一个代表当前带有键盘焦点元素的对象
body 返回一个代表body元素的对象
Embeds plugins 返回所有代表embed元素的对象
forms 返回所有代表form元素的对象
head 返回一个代表head元素的对象
images 返回所有代表img元素的对象
links 返回所有代表文档里具备href属性的aarea元素的对象
scripts 返回所有代表script元素的对象

Chap3-42.html

namedItem方法会返回集合里带有指定idname属性值的项目。

<head>
<style>
        div{border:medium black double;}
        p{font-size: x-large;}
    </style>
</head>
<body>
<div id="results"></div>
<img id="apple" src="pic/apple.jpg" height="100">
<p>I like apples</p>
<img id="sun" src="pic/sun.jpg" height="100">
<p>I love sun</p>
<script>
    var resultsElement=document.getElementById("results");
    var elems=document.images;
    for(var i=0;i<elems.length;i++){
        resultsElement.innerHTML+="Image Elements: "+elems[i].id+"<br>";
    }
    var srcValue=elems.namedItem("apple").src;
    resultsElement.innerHTML+="Src for apple element is: "+srcValue+"<br>";
</script>
</body>

搜索元素

属性 说明
getElementById(id) 返回带有指定id值的元素
getElementsByClassName(class) 返回带有指定class值的元素
getElementsByName(name) 返回带有指定name值的元素
getElementsByTagName(tag) 返回指定类型的元素
querySelector(selector) 返回匹配指定CSS选择器的第一个元素
querySelectorAll(selector) 返回匹配指定CSS选择器的所有元素

Chap3-43.html

这个例子中的选择器会匹配所有的p元素和id值为appleimg元素。

<head><style>p{font-size: x-large;color: white;background-color: grey}
        div{font-size: x-large;border: medium double black}    </style></head>
<body>
<div id="results"></div>
<img id="apple" class="pictures" name="myapple" src="pic/apple.jpg" height="100">
<p>I like apples</p>
<img id="sun" class="pictures" src="pic/sun.jpg" height="100">
<p>I love sun</p>
<script>
    var resultsElement=document.getElementById("results");
    var pElems=document.getElementsByTagName("p");
    resultsElement.innerHTML+="There are "+pElems.length+" p elements. <br>";
    var imgElems=document.getElementsByClassName("pictures");
    resultsElement.innerHTML+="There are "+imgElems.length+" elements in the pictures class. <br>";
    var nameElems=document.getElementsByName("myapple");
    resultsElement.innerHTML+="There are "+nameElems.length+" elements with the name 'apple'. <br>";
    var cssElems=document.querySelectorAll("p,img#apple");
    resultsElement.innerHTML+="The selector matched "+cssElems.length+" elements. <br>";
</script>
</body>

合并进行链式搜索

DOM的一个实用功能是几乎所有Document对象实现的搜索方法同时也能被HTMLElement对象实现,从而可以合并进行链式搜索。唯一的例外是getElementById方法,只有Document对象才能使用它。

Chap3-44.html

<head><style>p{font-size: x-large;color: white;background-color: grey}
        div{font-size: x-large;border: medium double black}
        span{font-size: x-large;display: block;height: 100px}
        .s1{display: inline-block}   </style></head>
<body>
<div id="results"></div>
<p id="tblock">
    <span><img id="apple" src="pic/apple.jpg" height="100"></span>
    <span>I like <span class="s1" style="color: red">apples</span> and <span class="s1" style="color: yellow">bananas</span> </span>
    <span><img id="sun" src="pic/sun.jpg" height="100"></span>
    <span>I love sun</span>
</p>
<script>
    var resultsElement=document.getElementById("results");
    var spanelems=document.getElementById("tblock").getElementsByTagName("span");
    resultsElement.innerHTML+="There are "+spanelems.length+" span elements. <br>";
    var imgElems=document.getElementById("tblock").querySelectorAll("img");
    resultsElement.innerHTML+="There are "+imgElems.length+" img elements. <br>";
    var s1elems=document.getElementById("tblock").querySelectorAll("#tblock>span");
    resultsElement.innerHTML+="There are "+s1elems.length+" span (not s1) elements. <br>";
</script>
</body>

在DOM树里导航

另一种搜索元素的方法是将DOM视为一棵树,然后在它的层级结构里导航。

navigation属性 说明
childNodes 返回子元素组
firstChild 返回第一个子元素
hasChildNodes() 如果当前元素有子元素就返回true
lastChild 返回倒数第一个子元素
nextSibling 返回定义在当前元素之后的兄弟元素
parentNode 返回父元素
previousSibling 返回定义在当前元素之前的兄弟元素

Chap3-45.html

<head>
    <meta charset="UTF-8">
    <title>chap3-45</title>
    <style>
        p{font-size: x-large;}
        div{font-size: x-large;border: medium double black}
    </style>
</head>
<body id="mybody">
<div id="results"></div>
<p id="pApple"><img id="apple" src="pic/apple.jpg" height="100"></p>
<p id="pFruits">I like apples and bananas</p>
<p id="pSun"><img id="sun" src="pic/sun.jpg" height="100"></p>
<p id="pSun">I love sun</p>
<p id="pButton">
    <button id="parent">Parent</button>
    <button id="child">First Child</button>
    <button id="prev">Prev Sibling</button>
    <button id="next">Next Sibling</button>
</p>
<script>
    var resultsElement=document.getElementById("results");
    var element=document.body;
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=handleButtonClick;
    }
    processNewElement(element);
    function handleButtonClick(e) {
        if(element.style){
            element.style.backgroundColor="white";
        }
        if(e.target.id=="parent" && element!=document.body){
            element=element.parentNode;
        }else if(e.target.id=="child" && element.hasChildNodes()){
            element=element.firstChild;
        }else if(e.target.id=="prev" && element.previousSibling){
            element=element.previousSibling;
        }else if(e.target.id=="next" && element.nextSibling){
            element=element.nextSibling;
        }
        processNewElement(element);
        if(element.style){
            element.style.backgroundColor="lightgrey";
        }
    }
    function processNewElement(elem) {
        resultsElement.innerHTML="Element type: "+elem+"<br>";
        resultsElement.innerHTML+="Element id: "+elem.id+"<br>";
        resultsElement.innerHTML+="Has child nodes: "+elem.hasChildNodes()+"<br>";
        if(elem.previousSibling){
            resultsElement.innerHTML+="Prev sibling is: "+elem.previousSibling+"<br>";
        }else {
            resultsElement.innerHTML+="No prev sibling<br>";
        }
        if(elem.nextSibling){
            resultsElement.innerHTML+="Next sibling is: "+elem.nextSibling+"<br>";
        }else {
            resultsElement.innerHTML+="No next sibling<br>";
        }
    }
</script>
</body>

Window对象

问题 解决方案
获取一个Window对象 使用document.defaultView或全局变量window
获取某个窗口的信息 使用window的信息性属性
与窗口进行交互 使用Window对象定义的方法
用模式对话框窗口提示用户 在某个Window对象上使用alertconfimpromptshowModalDialog方法
对浏览器历史执行简单的操作 Window.history属性返回的History对象使用backforwardgo方法
操控浏览器历史 Window.history属性返回的History对象使用pushStatereplaceState方法
向运行在另一个文档中的脚本发送消息 使用跨文档消息传递功能
设置一次性或重复性计时器 在Window对象上使用setIntervalsetTimeoutclearIntervalclearTimeout方法

获取Window对象

可以用两种方式获得Window对象:

  • document对象的defaultView属性

  • 全局变量window

Chap3-46.html

<head>
    <meta charset="UTF-8">
    <title>chap3-46</title>
    <style>
        td{font-size: x-large;border: solid black thin}
        th{font-size: x-large;border: solid black thin}
        table{border: solid black thin}
    </style>
</head>
<body>
<table>
    <tr><th>outerWidth:</th><td id="owidth"></td> </tr>
    <tr><th>outerHeight:</th><td id="oheight"></td> </tr>
</table>
<script>
    document.getElementById("owidth").innerHTML=window.outerWidth;
    document.getElementById("oheight").innerHTML=document.defaultView.outerHeight;
</script>
</body>

获取窗口信息

窗口属性 说明
innerHeight 获取窗口内容区域的高度
innerWidth 获取窗口内容区域的宽度
outerHeight 获取窗口的高度,包括边框和菜单栏等
outerWidth 获取窗口的宽度,包括边框和菜单栏等
pageXOffset 获取窗口从左上角算起水平滚动过的像素数
pageYOffset 获取窗口从左上角算起垂直滚动过的像素数
screen 返回一个描述屏幕的Screen对象
screenLeft 获取从窗口左边缘到屏幕左边缘的像素数(不是所有浏览器都同时实现了这两个属性,或是以同样的方法计算这个值)
screenTop 获取从窗口上边缘到屏幕上边缘的像素数(不是所有浏览器都同时实现了这两个属性,或是以同样的方法计算这个值)

Chap3-46.html

<body>
<table>
    <tr><th>outerWidth:</th><td id="owidth"></td> </tr>
    <tr><th>outerHeight:</th><td id="oheight"></td> </tr>
    <tr><th>innerWidth:</th><td id="iwidth"></td> </tr>
    <tr><th>innerHeight:</th><td id="iheight"></td> </tr>
    <tr><th>screenWidth:</th><td id="swidth"></td> </tr>
    <tr><th>screenHeight:</th><td id="sheight"></td> </tr>
    <tr><th>screenLeft:</th><td id="sleft"></td> </tr>
    <tr><th>screenTop:</th><td id="stop"></td> </tr>
</table>
<script>
    document.getElementById("owidth").innerHTML=window.outerWidth;
    document.getElementById("oheight").innerHTML=document.defaultView.outerHeight;
    document.getElementById("iwidth").innerHTML=window.innerWidth;
    document.getElementById("iheight").innerHTML=window.innerHeight;
    document.getElementById("swidth").innerHTML=window.screen.width;
    document.getElementById("sheight").innerHTML=window.screen.height;
    document.getElementById("sleft").innerHTML=window.screenLeft;
    document.getElementById("stop").innerHTML=window.screenTop;
</script>
</body>

上表中使用了screen属性来获取一个Screen对象,这个对象提供了显示此窗口的屏幕信息,其属性有:

screen属性 说明
availHeight 屏幕上可供显示窗口部分的高度(排除工具栏和菜单栏之类)
availWidth 屏幕上可供显示窗口部分的宽度(排除工具栏和菜单栏之类)
colorDepth 屏幕的颜色深度
height 屏幕的高度
width 屏幕的宽度

与窗口进行交互

Window对象提供了一组方法,可以用它们与包含文档的窗口进行交互。

方法名称 说明
blur() 让窗口失去键盘焦点
close() 关闭窗口(不是所有浏览器都允许某个脚本关闭窗口)
focus() 让窗口获得键盘焦点
print() 提示用户打印页面
scrollBy(x,y) 让文档相对于当前位置进行滚动
scrollTo(x,y) 滚动到指定的位置
stop() 停止载入文档

Chap3-47.html

<head><style>p{width: 1000px;font-size: large}  img{display: block} </style></head>
<body>
<p><button id=“scroll”>Scroll</button><button id=“print”>Print</button>
    <button id=“close”>Close</button>
</p>
<p> In ecology, primary compounds as …    <img src=“pic/globalGPP.jpg”>
    Gross primary production (GPP) is….    <img src=“pic/globalNPP.jpg”>
</p>
<script>
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="print"){
                window.print();
            }else if(e.target.id=="close"){
                window.close();
            }else{
                window.scrollTo(0,400);
            }
        }
    }
</script>
</body>

对用户进行提示

Window对象包含一组方法,能以不同的方式对用户进行提示。

方法名称 说明
alert(msg) 向用户显示一个对话框窗口并等候其被关闭
confirm(msg) 显示一个带有确认和取消提示的对话框窗口
prompt(msg,val) 显示对话框提示用户输入一个值
showModalDialog(url) 弹出一个窗口,显示指定的URL

showModalDialog方法已经被广告商们严重滥用了,很多浏览器对这个功能加以限制,仅允许用户事先许可的网站使用。

Chap3-48.html

<body>
<button id="alert">Alert</button>
<button id="confirm">Confirm</button>
<button id="prompt">Prompt</button>
<button id="modal">Modal Dialog</button>
<script>
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="alert"){
                window.alert("This is an alert");
            }else if(e.target.id=="confirm"){
                var confirmed=window.confirm("This is a confirm - do you want to proceed?");
                alert("Confirmed? "+confirmed);
            }else if(e.target.id=="prompt"){
                var response=window.prompt("Enter a world","hello");
                alert("The word was "+response);
            }else if(e.target.id=="modal"){
                window.showModalDialog("http://www.njfu.edu.cn");
            }
        }
    }
</script>
</body>

使用浏览器历史

Window.history属性返回一个History对象,可以对浏览器历史进行一些基本的操作。

名称 说明
back() 在浏览器历史中后退一步
forward() 在浏览器历史中前进一步
go(index) 转到相对于当前文档的某个浏览历史位置。正值是前进,负值是后退
length 返回浏览历史中项目数量

Chap3-49.html

<body>
<button id="back">Back</button>
<button id="forward">Forward</button>
<button id="go">Go -1</button>
<script>
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="back"){
                window.history.back();
            }else if(e.target.id=="forward"){
                window.history.forward();
            }else if(e.target.id=="go"){
                window.history.go(-1);
            }
        }
    }
    document.writeln("<a href=\"chap3-47.html\">浏览历史中项目数:"+window.history.length+"</a>");
</script>
</body>

使用计时器

Window对象提供的计时方法有:

方法名称 说明
clearInterval(id) 撤销某个时间间隔计时器
clearTimeout(id) 撤销某个超时计时器
setInterval(function,time) 创建一个计时器,每隔time毫秒调用指定的函数
setTimeout(function,time) 创建一个计时器,等待time毫秒后调用指定的函数

setTimeout方法创建的计时器只执行一次指定函数,而setInterval方法会重复执行某个函数。这两个方法返回一个唯一的标识符,可以用clearIntervalclearTimeout方法来撤销计时器。

Chap3-50.html

<body>
<p id="msg"></p>
<button id="settime">Set Time</button>
<button id="cleartime">Clear Time</button>
<button id="setinterval">Set Interval</button>
<button id="clearinterval">Clear Interval</button>
<script>
    var timeID,intervalID,count=0,buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="settime"){
                timeID=window.setTimeout(function () {
                    displayMsg("Timeout Expired");
                },5000);
                displayMsg("Timeout Set");
            }else if(e.target.id=="cleartime"){
                window.clearTimeout(timeID);
                displayMsg("Timeout Cleared");
            }else if(e.target.id=="setinterval"){
                intervalID=window.setInterval(function () {
                    displayMsg("Interval expired. Counter: "+count++);
                },1000);
                displayMsg("Interval Set");
}else if(e.target.id=="clearinterval"){
                window.clearInterval(intervalID);
                displayMsg("Interval Cleared");
            }
        }
    }
    function displayMsg(msg) {
        document.getElementById("msg").innerHTML=msg;
    }
</script>
</body>

使用DOM元素

问题 解决方案
获取元素的信息 使用HTMLElement的元数据属性
获取或设置包含某个元素所属全部类的单个字符串 使用className属性
检查或修改元素的各个类 使用classList属性
获取或设置元素的属性 使用attributegetAttributesetAttributeremoveAttributehasAttribute方法
获取或设置元素的自定义属性 使用dataset属性
操作元素的文本内容 使用Text对象
创建或删除元素 使用以document.create开头的方法以及用于管理子元素的HTMLElement方法
复制元素 使用cloneNode方法
移动元素 使用appendChild方法
比较两个对象是否相同 使用isSameNode方法
比较两个元素是否相同 使用isEqualNode方法
直接操作HTML片段 使用innerHTMLouterHTML属性以及insertAdjacentHTML方法
在文本块里插入元素 使用splitTextappendChild方法

使用元素对象

HTMLElement对象提供了一组属性,可以用来读取和修改被代表元素的数据。

元素数据属性 说明 元素数据属性 说明
checked 获取或设置checked属性是否存在 id 获取或设置id属性的值
classList 获取或设置元素所属的类列表 lang 获取或设置lang属性的值
className 获取或设置元素所属的类列表 spellcheck 获取或设置spellcheck属性是否存在
dir 获取或设置dir属性的值 tabIndex 获取或设置tabIndex属性的值
disabled 获取或设置disabled属性是否存在 tagName 返回标签名(标识元素类型)
hidden 获取或设置hidden属性是否存在 title 获取或设置title属性的值

Char3-57.html

<style>.p1{font-size: x-large;border: medium double black}
</style>
<body>
<p id="block1" lang="en" dir="ltr" class="p1" title="favFruits">
    I like <span id="apple">apples</span> and <span id="banana">banana</span>.
</p>
<pre id="results"></pre>
<script>
    var results=document.getElementById("results");
    var elem=document.getElementById("block1");
    results.innerHTML+="tag:"+elem.tagName+"\n";
    results.innerHTML+="id:"+elem.id+"\n";
    results.innerHTML+="dir:"+elem.dir+"\n";
    results.innerHTML+="lang:"+elem.lang+"\n";
    results.innerHTML+="hidden:"+elem.hidden+"\n";
    results.innerHTML+="disabled:"+elem.disabled+"\n";
    results.innerHTML+="classList:"+elem.classList+"\n";
    results.innerHTML+="title:"+elem.title+"\n";
</script>
</body>

可以用两种方法处理某个元素所属的类:

  • className属性:它返回一个类的列表,通过改变这个字符串的值,可以添加或移除类。
  • classList属性:它返回一个DOMTokenList对象。
DOMTokenList对象成员 说明
add(class) 给元素添加指定的类
contains(class) 如果元素属于指定的类就返回true
length 返回元素所属类的数量
remove(class) 从元素上移除指定的类
toggle(class) 如果类不存在就添加它,如果存在就移除它

Char3-57.html

<head>
<style>
  .fontSize{font-size: x-large}
  .border{border: medium double black}
  .big{height: 100px;width: 600px;}
  .small{height: 50px;width: 300px;}
  .green{background-color: green;}
  .blue{background-color: blue;}
  .white{color: white}
  .red{color: red}
  pre{font-size: x-large;border: thin black solid}
</style>
</head>
<body>
<p id="block1" lang="en" dir="ltr" class="fontSize big green" title="favFruits">
    I like <span id="apple">apples</span> and <span id="banana">banana</span>.
</p>
<pre id="results"></pre>
<button id="btn1">添加边框</button>
<button id="btn2">大小变化</button>
<button id="btn3">背景色变化</button>
<button id="btn4">前景色变化</button>
<script>
    var results=document.getElementById("results");
    var elem=document.getElementById("block1");
    var buttons=document.getElementsByTagName("button");
    showElementProperties();
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="btn1"){
                elem.className+=" border";
            }else if(e.target.id=="btn2"){
                elem.classList.toggle("small");
            }else if(e.target.id=="btn3"){
                elem.classList.toggle("blue");
            }else if(e.target.id=="btn4"){
                if(elem.classList.contains("white")){
                    elem.classList.remove("white");
                    elem.classList.add("red");
                }else{
                    elem.classList.add("white");
                    elem.classList.remove("red");
                }
            }
            showElementProperties();
        }
    }
    function showElementProperties() {
        results.innerHTML="tag:"+elem.tagName+"\n";
        results.innerHTML+="id:"+elem.id+"\n";
        results.innerHTML+="dir:"+elem.dir+"\n";
        results.innerHTML+="lang:"+elem.lang+"\n";
        results.innerHTML+="hidden:"+elem.hidden+"\n";
        results.innerHTML+="disabled:"+elem.disabled+"\n";
        results.innerHTML+="classList:"+elem.classList+"\n";
        results.innerHTML+="title:"+elem.title+"\n";
    }
</script>
</body>

使用元素属性

HTMLElement对象成员 说明
attributes 返回应用到元素上的属性
dataset 返回以data-开头的属性
getAttribute(name) 返回指定属性的值
hasAttribute(name) 如果元素带有指定的属性则返回true
removeAttribute(name) 从元素上移除指定属性
setAttribute(name,value) 应用一个指定名称和值的属性

Chap3-58.html

<head><style>.p1{font-size: x-large;border: medium double black}
        pre{font-size: x-large;border: thin black solid}
</style></head>
<body>
<p id="block1" class="p1" lang="en-UK">
    I like <span id="apple">apples</span> and <span id="banana">banana</span>.
</p>
<pre id="results"></pre>
<script>
    var results=document.getElementById("results");
    var elem=document.getElementById("block1");
    results.innerHTML="Element has data-fruit attribute: "+ elem.hasAttribute("data-fruit")+"\n"
    results.innerHTML+="Adding data-fruit attribute\n";
    elem.setAttribute("data-fruit","apples");
    results.innerHTML+="data-fruit value is: "+elem.getAttribute("data-fruit")+"\n";
    var attrs=elem.attributes;
    for(var i=0;i<attrs.length;i++){
        results.innerHTML+="Name: "+attrs[i].name+" value: "+attrs[i].value+"\n";
    }
</script>
</body>

使用Text对象

元素的文本内容是由Text对象代表的,它在文档模型里表现为元素的子对象。当浏览器在文档模型里生成p元素时,元素自身会有一个HTMLElement对象,内容则会有一个Text对象。

<p id="block1">
    I like <b>apples</b> and banana.
</p>
Text对象
Text对象成员 说明
appendData(string) 把指定字符串附加到文本块末尾
data 获取或设置文本
deleteData(offset,count) 从文本中移除字符串。第一个数字是偏移量,第二个是要移除的字符数量
insertData(offset,string) 在指定偏移量处插入指定字符串
length 返回字符的数量
replaceData(offset,count,string) 用指定字符串替换一段文本
replaceWholeText(string) 替换全部文本
splitText(number) 将现有的Text元素在指定偏移量处一分为二
substringData(offset,count) 返回文本的子串
wholeText 获取文本

Chap3-59.html

<body>
<p id="block1" style="font-size: x-large;border: medium double black">
    I like <b id="b1">apples</b> and banana.
</p>
<pre id="results"></pre>
<button id="pressme">Press Me</button>
<script>
    var results=document.getElementById("results");
    var elem=document.getElementById("block1");
    document.getElementById("pressme").onclick=function () {
        var textElem=elem.firstChild;
        results.innerHTML="The element has "+textElem.length+" chars\n";
        results.innerHTML+="The element is "+textElem.wholeText+"\n";
        textElem.replaceData(5,1,"Do you");
        var elemB=document.getElementById("b1");
        var textB=elemB.firstChild;
        textB.data="oranges";
    }
</script>
</body>

修改DOM

DOM操纵成员 说明
appendChild(HTMLElement) 将指定元素添加为当前元素的子元素
cloneNode(boolean) 复制一个元素
compareDocumentPosition(HTMLElement) 判断一个元素的相对位置
innerHTML 获取或设置元素的内容
insertAdjacentHTML(pos,text) 相对于元素插入HTML
insertBefore(newElem,childElem) 在第二个子元素之前插入第一个元素
isEqualNode(HTMLElement) 判断指定元素是否与当前元素相同
isSameNode(HTMLElement) 判断指定元素是否就是当前元素
outerHTML 获取或设置某个元素的HTML和内容
removeChild(HTMLElement) 从当前元素上移除指定的子元素
replaceChild(HTMLElement, HTMLElement) 替换当前元素的某个子元素
createElement(tag) 创建一个属于指定标签类型的新HTMLElement对象
createTextNode(text) 创建一个带有指定内容的新Text对象

Chap3-60.html

<head>
    <meta charset="UTF-8">
    <title>chap3-60</title>
    <style>
        table{border: solid thin black;margin: 10px}
        td{border:solid thin black;padding: 4px 5px}
    </style>
</head>
<body>
<table>
    <thead><tr><th>Name</th><th>Color</th></tr></thead>
    <tbody id="fruitsBody">
        <tr><td class="fruitName">Banana</td><td class="fruitColor">Yellow</td></tr>
        <tr><td>Apple</td><td>Red</td></tr>
    </tbody>
</table>
<button id="add">Add Element</button>
<button id="remove">Remove Element</button>
<button id="clone">Clone Element</button>
<script>
    var tableBody=document.getElementById("fruitsBody");
    document.getElementById("add").onclick=function () {
        var row=tableBody.appendChild(document.createElement("tr"));
        row.setAttribute("id","newrow");
        var col=row.appendChild(document.createElement("td"));
        col.appendChild(document.createTextNode("Plum"));
        row.appendChild(document.createElement("td"))
            .appendChild(document.createTextNode("Purple"));
    }
    document.getElementById("remove").onclick=function () {
        var row=document.getElementById("newrow");
        row.parentNode.removeChild(row);
    }
    document.getElementById("clone").onclick=function () {
        var newElem=tableBody.getElementsByTagName("tr")[0].cloneNode(true);
        newElem.getElementsByClassName("fruitName")[0].firstChild.data="pear";
        newElem.getElementsByClassName("fruitColor")[0].firstChild.data="Yellow";
        tableBody.appendChild(newElem);
    }
</script>
</body>

脚本化CSS

行间样式

脚本化CSS就是使用javascript来操作CSS。引入CSS有3种方式:行间样式、外部样式和内部样式。

行间样式又叫内联样式,使用HTML的style属性进行设置:

<div id="test" style="height: 40px;width: 40px;background-color: blue;"></div>

element元素节点提供style属性,用来操作CSS行间样式,style属性指向cssStyleDeclaration对象。例如:

test.style.height = '30px'; 

如果一个CSS属性名包含一个或多个连字符,CSSStyleDeclaration属性名的格式应该是移除连字符,将每个连字符后面紧接着的字母大写:

<div id="test" style="height: 40px;width: 40px;background-color: blue;"></div>
console.log(test.style.backgroundColor)
  • cssText属性:通过cssText属性能够访问到style特性中的CSS代码。在读模式下,cssText返回浏览器对style特性中CSS代码的内部表示;在写模式中,赋给cssText的值会重写整个style特性的值。

    <div id="test" style="float: left">hello world</div>
    <button id="pressme">Press Me</button>
    <div id="results"></div>
    <p>
    <script>
        var divTest=document.getElementById("test");
        document.getElementById("results").innerHTML=divTest.style.cssText;
        document.getElementById("pressme").onclick=function () {
            divTest.style.cssText="font-size:x-large;float:right;background-color: blue;color: white";
        }
    </script>
    
  • length属性:返回内联样式中的样式个数。

    var divResults=document.getElementById("results");
    divResults.innerHTML=divTest.style.length;
    
  • item()方法:返回给定位置的CSS属性的名称。

    var resFontsize=divTest.style.item(0);
    
  • getPropertyValue()方法:返回给定属性的字符串值。

    divResults.innerHTML=resFontsize+":"+divTest.style.getPropertyValue(resFontsize);
    
  • getPropertyPriority()方法:如果给定的属性使用了!important设置,则返回important;否则返回空字符串。

    <div id="results" style="height: 40px!important;width: 200px;background-color: lightgray"></div>
    divResults.innerHTML=divResults.style.getPropertyPriority("height");
    
  • setProperty()方法:将给定属性设置为相应的值,并加上优先级标志(important或一个空字符串),该方法无返回值。语法为:setProperty(propertyName,value,priority)

    divResults.style.setProperty('height','20px','important');
    
  • removeProperty()方法:从样式中删除给定属性,并返回被删除属性的属性值。

    divResults.style.removeProperty('background-color');
    

Chap3-51.html

<body>
<div id="test" style="float: left">hello world</div>
<p>
    <button id="cssText">cssText</button>
    <button id="length">Length</button>
    <button id="PropertyValue">getPropertyValue</button>
    <button id="PropertyPriority">getPropertyPriority</button>
    <button id="setProperty">setProperty</button>
    <button id="removeProperty">removeProperty</button>
</p>
<div id="results" style="height: 40px!important;width: 200px;background-color: lightgray"></div>
<script>
    var divTest=document.getElementById("test");
    var divResults=document.getElementById("results");
    divResults.innerHTML=divTest.style.cssText;
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            if(e.target.id=="cssText"){
                divTest.style.cssText="font-size:x-large;float:right;background-color: blue;color: white";
            }else if(e.target.id=="length"){
                divResults.innerHTML="length:"+divTest.style.length;
            }else if(e.target.id=="PropertyValue"){
                var resFontsize=divTest.style.item(0);
                divResults.innerHTML=resFontsize+":"+divTest.style.getPropertyValue(resFontsize);
            }else if(e.target.id=="PropertyPriority"){
                divResults.innerHTML=divResults.style.getPropertyPriority("height");
            }
            else if(e.target.id=="setProperty"){
                divResults.style.setProperty('height','20px','important');
            }else if(e.target.id=="removeProperty"){
                divResults.style.removeProperty('background-color');
            }
        }
    }
</script>
</body>

查询计算样式

元素的渲染结果是多个CSS样式博弈后的最终结果,浏览器会从多个来源汇聚样式以计算出该用哪个值来显示某一元素。浏览器用于显示某个元素的CSS属性值集合被称为计算样式。

元素的计算样式(computedStyle)是一组在显示元素时实际使用的属性值,也是用一个 CSSStyleDeclaration对象来表示的,但计算样式是只读的,主要通过getComputedStyle()方法实现。语法为:

document.defaultView.getComputedStyle(element[,pseudo-element]) 
//或
window.getComputedStyle(element[,pseudo-element])
//或
getComputedStyle(element[,pseudo-element])

getComputedStyle()方法接收两个参数:要取得计算样式的元素和一个伪元素字符串。如果不需要伪元素信息,第二个参数可以是nullgetComputedStyle()方法返回一个CSSStyleDeclaration对象,其中包含当前元素的所有计算的样式。

Chap3-52.html

<style>
        #test:before{
            content: "getComputedStyle";
            width: 250px;
            display: inline-block;
        color: blue;
        }
</style>
<div id="test" style="font-size:x-large;width: 500px;border: double medium black">hello world</div>
<script>
    var divTest=document.getElementById("test");
    document.writeln("缺省伪元素:");
    document.writeln(document.defaultView.getComputedStyle(divTest).width);
    document.writeln(window.getComputedStyle(divTest).width);
    document.writeln(getComputedStyle(divTest).width);
    document.writeln("伪元素:");
    document.writeln(getComputedStyle(divTest,":before").width);
</script>

在使用getComputedStyle()方法的过程中,有如下注意事项:

  • 对于fontbackgroundborder等复合样式,各浏览器处理不一样。chrome会返回整个复合样式,而IE9+、firefox和safari则输出空字符串''

    document.writeln(getComputedStyle(divTest).font);
    
  • 不论以什么格式设置颜色,浏览器都以rgb()rgba()的形式输出

    document.writeln(getComputedStyle(divTest,":before").color);
    
  • 在计算样式中,类似百分比等相对单位会转换为绝对值

    #test:after{content: "!"; width: 1%; display: inline-block; color: blue;}
    
    document.writeln(getComputedStyle(divTest,":after").width);
    

getComputedStyleelement.style的相同点就是二者返回的都是CSSStyleDeclaration对象,取相应属性值得时候都是采用的 CSS 驼峰式写法。

而不同点就是:

  • element.style读取的只是元素的“内联样式”,即 写在元素的style属性上的样式;而getComputedStyle读取的样式是最终样式,包括了“内联样式”、“嵌入样式”和“外部样式”。
  • element.style既支持读也支持写,我们通过element.style即可改写元素的样式。而getComputedStyle仅支持读并不支持写入。

我们可以通过使用getComputedStyle读取样式,通过element.style修改样式

脚本化CSS

在实际工作中,我们使用javascript操作CSS样式时,如果要改变大量样式,会使用脚本化CSS类的技术:

  • style:我们在改变元素的少部分样式时,一般会直接改变其行间样式;
  • cssText:改变元素的较多样式时,可以使用cssText
  • css类:更常用的是使用css类,将更改前和更改后的样式提前设置为类名。只要更改其类名即可(参考3.2.3,Chap3-57);
  • classList:如果要改变多个类名,使用classList更为方便(参考3.2.3,chap3-57)
<style>
.big{height: 100px;width: 100px;background-color: blue;}
.small{height: 50px;width: 50px;background-color: green;}
</style>

<div id="test" class="big" style="border: medium double black;font-size: 1em;color: white">hello world!</div>
var divTest=document.getElementById("test");
divTest.onclick=function () {
        if(divTest.className=="big"){
            divTest.className="small";
        }else if(divTest.className=="small"){
            divTest.className="big";
        }
}

脚本化样式表

CSSStyleSheet类型表示的是样式表。我们知道,引入CSS一共有3种方式,包括行间样式、内部样式和外部样式。其中,内部样式和外部样式分别通过<style><link>标签以样式表的形式引入,属于CSSStyleSheet类型。

样式表CSSStyleSheet是通过document.styleSheets集合来表示的,它会返回一组对象集合,这些对象代表了与文档关联的各个样式表。

CSSStyleSheet对象成员 说明
cssRules 返回样式表的规则集合
deleteRule(pos) 从样式表中移除一条规则
disabled 获取或设置样式表的禁用状态
href 返回链接样式表的href
insertRule(rule,pos) 插入一条新规则到样式表中
media 返回应用到样式表上的媒介限制集合
ownerNode 返回样式所定义的元素
title 返回title属性的值
type 返回type属性的值

获得样式表的基本信息

Chap3-54.html

<head>
    <meta charset="UTF-8">
    <title>chap3-54</title>
    <style title="S1">
        p{border: double medium black;background-color: lightgray}
        #block1{color: white}
        table{border: thin solid black;border-collapse: collapse;margin: 5px;float: left}
        td{padding: 2px}
    </style>
    <link rel="stylesheet" title="L1" type="text/css" href="css/styles.css">
    <style title="S2" media="screen AND (min-width:500px)" type="text/css">
        #block2{color: yellow;font-style: italic}
    </style>
</head>
<body>
<p id="block1">There are lots of different kinds of fruit</p>
<p id="block2">One of the most interesting aspects of fruit is the variety available in each country.</p>
<div id="placeholder"></div>
<script>
    var placeholder=document.getElementById("placeholder");
    var sheets=document.styleSheets;
    for(var i=0;i<sheets.length;i++){
        var newElem=document.createElement("table");
        newElem.setAttribute("border","1");
        addRow(newElem,"Index",i);
        addRow(newElem,"href",sheets[i].href);
        addRow(newElem,"title",sheets[i].title);
        addRow(newElem,"type",sheets[i].type);
        addRow(newElem,"ownerNode",sheets[i].ownerNode.tagName);
        placeholder.appendChild(newElem);
    }
    function addRow(elem,header,value) {
        elem.innerHTML+="<tr><td>"+header+":</td><td>"+value+"</td></tr>";
    }
</script>
</body>

styles.css

a{
    background-color: grey;
    color: white;
}
span{
    border:thin black solid;
    padding: 10px;
}

禁用样式表

CSSStyleSheet.disabled属性可用来一次性启用和禁用某个样式表里的所有样式。

Chap3-55.html

<head> <style>p{border: medium double black;background-color: lightgray;height: 40px}</style>
<style media="screen AND (min-width:500px)" type="text/css">
        #block1{color: yellow;border: thin solid black;background-color: lightgreen} </style>
</head>
<body>
<p id="block1">There are lots of different kinds of fruit</p>
<div><button id="pressme">Press Me</button> </div>
<script>
    document.getElementById("pressme").onclick=function () {
        document.styleSheets[0].disabled=!document.styleSheets[0].disabled;
    }
</script>
</body>

CSSRuleList对象

CSSStyleSheet.cssRules属性会返回一个CSSRuleList对象,它允许你访问样式表里的各种样式。样式表里的每一种CSS样式都有一个CSSStyleRule对象代表。

CSSRuleList对象成员 说明
item(pos) 返回指定索引的CSS样式
length 返回样式表里的样式数量
CSSStyleRule对象成员 说明
cssText 获取或设置样式的文本(包括选择器)
parentStyleSheet 获取此样式所属的样式表
selectorText 获取或设置样式的选择器文本
style 获取一个代表具体样式属性的对象

Chap3-56.html

<head> <style> p{border: double medium black;background-color: lightgray}
        #block1{color: white;border: thick solid black;background-color: gray}
        table{border: thin solid black;border-collapse: collapse;margin: 5px;float: left}
        td{padding: 2px} </style>
</head>
<body>
<p id="block1">There are lots of different kinds of fruit</p>
<p id="block2">One of the most interesting aspects of fruit is the variety available in each country.</p>
<div><button id="pressme">Press Me</button> </div>
<div id="placeholder"></div>
<script>
    var placeholder=document.getElementById("placeholder");
    processStyleSheet();
    document.getElementById("pressme").onclick=function () {
        document.styleSheets[0].cssRules.item(1).selectorText="#block2";
        if(placeholder.hasChildNodes()){
            var childCount=placeholder.childNodes.length;
            for(var i=0;i<childCount;i++){
                placeholder.removeChild(placeholder.firstChild);
            }
        }
        processStyleSheet();
    }
function processStyleSheet() {
        var rulesList=document.styleSheets[0].cssRules;
        for(var i=0;i<rulesList.length;i++){
            var rule=rulesList.item(i);
            var newElem=document.createElement("table");
            newElem.setAttribute("border","1");
            addRow(newElem,"parentStyleSheet",rule.parentStyleSheet.title);
            addRow(newElem,"selectorText",rule.selectorText);
            addRow(newElem,"cssText",rule.cssText);
            placeholder.appendChild(newElem);
        }
    }
    function addRow(elem,header,value) {
        elem.innerHTML+="<tr><td>"+header+":</td><td>"+value+"</td></tr>";
    }
</script>
</body>

脚本化HTTP

超文本传输协议(HyperText Transfer Protocol, HTTP)规定Web浏览器如何从Web服务器获取文档和向Web服务器提交表单内容,以及Web服务器如何响应这些请求和提交。

通常,HTTP并不在脚本的控制下,只是当用户单击链接、提交表单和输入URL时才发生。但是,用JavaScript操作HTTP是可行的,当用window.location属性或调用表单对象的submit()方法时,都会初始化HTTP请求。在这两种情况下,浏览器会加载新页面。

Ajax描述了一种主要使用脚本操纵HTTP的Web应用架构。Ajax应用的主要特点是使用脚本操纵HTTP和Web服务器进行数据交换,不会导致页面重载。

Web应用可以使用Ajax技术把用户的交互数据记录到服务器中;也可以开始只显示简单页面,之后按需加载额外的数据和页面组件来提升应用的启动时间。

Ajax起步

Ajax的关键在于XMLHttpRequest对象。

第一步是创建一个新的XMLHttpRequest对象:

var httpRequest=new XMLHttpRequest();

第二步是给XMLHttpRequest对象的readystatechange事件设置一个事件处理器。这个事件会在请求过程中被多次触发,向你提供事情的进展情况。可以读取XMLHttpRequest.readyState属性的值来确定当前处理的是哪一个。

数值 说明
UNSENT 0 已创建XMLHttpRequest对象
OPENED 1 已调用open方法
HEADERS_RECEIVED 2 已收到服务器响应的标头
LOADING 3 已收到服务器响应
DONE 4 响应完成或已失败,如请求成功,HTTP状态码为200

第三步是告诉XMLHTTPRequest对象你想要做什么,比如调用open方法请求打开文档:

httpRequest.open(“GET”,“chap3-58.html”);

第四步是调用send方法,向服务器发送请求:

httpRequest.send();

XMLHttpRequest.responseText属性可以用来获得服务器发送的数据,例如:

document.getElementById("results").innerHTML=httpRequest.responseText;

responseText属性会返回一个字符串,代表从服务器上取回的数据。

Chap3-61.html

<body>
<div>
    <button>chap3-58.html</button>
    <button>chap3-59.html</button>
    <button>chap3-60.html</button>
</div>
<div id="results" style="border: dotted medium black">Press a button</div>
<script>
    var buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){
        buttons[i].onclick=function (e) {
            var httpRequest=new XMLHttpRequest();
            httpRequest.onreadystatechange=function (e) {
                if(e.target.readyState==XMLHttpRequest.DONE && e.target.status==200){
                    document.getElementById("results").innerHTML=e.target.responseText;
                }
            };
            httpRequest.open("GET",e.target.innerHTML);
            httpRequest.send();
        }
    }
</script>
</body>

使用Ajax事件

事件名称 说明
abort 在请求被中止时触发
error 在请求失败时触发
timeout 如果请求超时则触发
readystatechange 在请求生命周期的不同阶段触发
loadstart 在请求开始时触发
progress 触发以提示请求的进度
load 在请求成功完成时触发
loadend 在请求已完成时触发,无论成功还是失败

除了readystatechange之外,其他事件都定义于XMLHttpRequest规范的第二级。调度这些事件时,浏览器会对readystatechange事件使用常规的Event对象,对其他事件则使用ProgressEvent对象。

名称 说明
lengthComputable 如果能够计算数据流的总长度则返回true
loaded 返回当前已载入的数据量
total 返回可用的数据总量

Chap3-62.html

<div><button>chap3-57.html</button><button>chap3-58.html</button><button>chap3-59.html</button></div>
<table id="events" border="1"></table><div id="target">Press a button</div>
<script>
    var tableElem=document.getElementById("events"),buttons=document.getElementsByTagName("button");
    for(var i=0;i<buttons.length;i++){buttons[i].onclick=handleButtonPress;}
    var httpRequest;
    function handleButtonPress(e) {
        clearEventDetails();
        httpRequest=new XMLHttpRequest();
        httpRequest.onreadystatechange=handleResponse;
        httpRequest.onerror=handleError;
        httpRequest.onload=handleLoad;
        httpRequest.onloadend=handleLoadEnd;
        httpRequest.onloadstart=handleLoadStart;
        httpRequest.onprogress=handleProgress;
        httpRequest.open("GET",e.target.innerHTML);
        httpRequest.send();
    }
    function handleResponse(e) {
        displayEventDetails("readystate("+httpRequest.readyState+")");
        if(httpRequest.readyState==4&&httpRequest.status==200){
            document.getElementById("target").innerHTML=httpRequest.responseText;
        }
    }
    function handleError(e) {displayEventDetails("error",e);}
    function handleLoad(e) {displayEventDetails("load",e);}
    function handleLoadEnd(e) {displayEventDetails("loadend",e);}
    function handleLoadStart(e) {displayEventDetails("loadstart",e);}
    function handleProgress(e) {displayEventDetails("progress",e);}
    function clearEventDetails() {        tableElem.innerHTML="<tr><th>Event</th><th>lengthComputable</th><th>loaded</th><th>total</th></tr>"
    }
    function displayEventDetails(eventName,e) {
        if(e){            tableElem.innerHTML+="<tr><td>"+eventName+"</td><td>"+e.lengthComputable+"</td><td>"+e.loaded+"</td><td>"+e.total+"</td></tr>";
        }else {
            tableElem.innerHTML+="<tr><td>"+eventName+"</td><td>NA</td><td>NA</td><td>NA</td></tr>"
        }
    }
</script>

jQuery

jQuery是一个JavaScript库,jQuery极大地简化了 JavaScript编程,jQuery很容易学习。jQuery 库包含以下特性:

  • HTML 元素选取和操作
  • CSS 操作
  • HTML 事件函数
  • JavaScript 特效和动画
  • HTML DOM 遍历和修改
  • AJAX

共有两个版本的jQuery可供下载:一份是精简过的,另一份是未压缩的(供调试或阅读)。这两个版本都可从jQuery.com下载。

如需使用 jQuery,需要把它包含在希望使用的网页中。可以使用 HTML 的 <script> 标签引用它:

<script src="jquery.js"></script>

jQuery 语法是为 HTML 元素的选取编制的,可以对元素执行某些操作。基础语法是:

$(selector).action()
  • 美元符号定义 jQuery
  • 选择符(selector)"查询"和"查找" HTML 元素
  • jQuery 的 action() 执行对元素的操作
$(this).hide()        //隐藏当前元素
$("p").hide()         //隐藏所有段落
$(".test").hide()     //隐藏所有 class="test" 的所有元素
$("#test").hide()     //隐藏所有 id="test" 的元素

CSS选择符

jQuery中有3种基本的选择符:

选择符 CSS jQuery 说明
标签名 P $('p') 取得文档中所有的段落
ID #some-id $('#some-id') 取得文档中ID为some-id的一个元素
.some-class $('.some-class') 取得文档中类为some-class的所有元素

当在jQuery中使用$(document).ready()时,位于其中的所有代码都会在DOM加载后立即执行。

Chap3-63.html

<head><script src="js/jquery-3.3.1.js"></script>
  <style>.horizontal{float: left;list-style: none;margin: 10px} 
         .subLevel{background-color: lightgray}
         ul{font-size: x-large}
  </style></head>
<body><p><ul id="selected">
    <li>Fruits<ul><li>apple</li><li>banana</li><li>orange</li><li>pear</li></ul></li>
    <li>Vegetables<ul><li>cabbage</li><li>tomato</li><li>potato</li><li>lettuce</li></ul></li>
    <li>Meat<ul><li>pork</li><li>beef</li><li>chicken</li><li>lamb</li></ul></li>
</ul></p>
<p><button id="horizontal-level">Horizontal CSS</button><button id="sub-level">Sub-level CSS</button></p>
<script>
    $(document).ready(function () {
        $("#horizontal-level").click(function () {
            $('#selected>li').addClass('horizontal');
        });
        $("#sub-level").click(function () {
            $("#selected li:not(.horizontal)").addClass("subLevel");
        });
    })
</script>
</body>

属性选择符

jQuery也支持属性选择符,CSS属性选择符详细内容参照第2章CSS。

例如:要选择带有alt属性的所有图像元素,可以使用以下代码:$("img[alt]")

要选择带有href属性且以mailto开头的锚元素,可以使用:$("a[href^=mailto:]")

要选择所有PDF文件的链接,可以使用:$("a[href$=.pdf]")

自定义选择符

除了各种CSS选择符之外,jQuery还添加了独有的完全不同的自定义选择符:

选择器 实例 说明
:first $("p:first") 第一个<p>元素
:last $("p:last") 最后一个<p>元素
:even $("tr:even") 所有偶数<tr>元素
:odd $("tr:odd") 所有奇数<tr>元素
:eq(index) $("ul li:eq(3)") 列表中的第四个元素(index从0开始)
:gt(no) $("ul li:gt(3)") 列出index大于3的元素
:lt(no) $("ul li:lt(3)") 列出index小于3的元素
:not(selector) $("input:not(:empty)") 所有不为空的input元素
:header $(":header") 所有标题元素<h1>-<h6>
:contains(text) $(":contains('W3School')") 包含指定字符串的所有元素
:empty $(":empty") 无子(元素)节点的所有元素
:hidden $("p:hidden") 所有隐藏的<p>元素
:visible $("table:visible") 所有可见的表格

Chap3-64.html

<head><script src="js/jquery-3.3.1.js"></script>
<style> tr{background-color: lightgray}     .even{background-color: lightgreen} 
        .first{font-weight: bolder}         .last{font-style: italic}
        .eq0{color: red}                    .gt4{color: red}          .contain{text-align: center}
</style></head>
<body>
<table width="300px">
    <tr><td>Fruits</td><td>Color</td></tr><tr><td>apple</td><td>red</td></tr>
    <tr><td>banana</td><td>yellow</td></tr><tr><td>pear</td><td>yellow</td></tr>
    <tr><td>plum</td><td>purple</td></tr><tr><td colspan="2">My favorite fruits</td></tr>
</table>
<script>
    $(document).ready(function () {
       $("tr:even").addClass("even");
       $("tr:first").addClass("first");
       $("tr:last").addClass("last");
       $("tr:eq(0)").addClass("eq0");
       $("tr:gt(4)").addClass("gt4");
       $("tr:contains('yellow')").addClass("contain");
    });
</script>
</body>

在操作表单时,jQuery的自定义选择符也可以简化选择元素的任务:

选择器 实例 说明
:input $(":input") 所有<input>元素
:text $(":text") 所有type="text"<input>元素
:password $(":password") 所有type="password"<input>元素
:radio $(":radio") 所有type="radio"<input>元素
:checkbox $(":checkbox") 所有type="checkbox"<input>元素
:submit $(":submit") 所有type="submit"<input>元素
:reset $(":reset") 所有type="reset"<input>元素
:button $(":button") 所有type="button"<input>元素
:image $(":image") 所有type="image"<input>元素
:file $(":file") 所有type="file"<input>元素
:enabled $(":enabled") 所有激活的input元素
:disabled $(":disabled") 所有禁用的input元素
:selected $(":selected") 所有被选取的input元素
:checked $(":checked") 所有被选中的input元素

Chap3-65-html

<head><script src=“js/jquery-3.3.1.js”></script>
<style>  .s1{display: inline-block;width: 80px;height:40px;font-size: x-large}
         .s2{display: inline-block;width: 200px;height:40px;font-size: x-large}
</style></head>
<body>
<pre>
    <span class=“s1”>用户名:</span><span class=“s2”><input type=“text”></span>
    <span class=“s1”>密码:</span><span class=“s2”><input type=“password”></span>
    <span class=“s1”>性别:</span><span class=“s2”><input type=“radio” name=“sex” checked>男 <input type=“radio” name=“sex”>女</span>
    <button>加边框</button>
</pre>
<script>
    $(document).ready(function () {
       $(":text").css("background-color","lightgray");
       $(":password").css("color","red");
       $(":button").click(function () {
           $(":checked").css("outline","dotted medium green");
       });
    });
</script>
</body>

事件

事件方法 描述
$(document).ready(function) 将函数绑定到文档的就绪事件(当文档完成加载时)
$(selector).click(function) 触发或将函数绑定到被选元素的点击事件
$(selector).dblclick(function) 触发或将函数绑定到被选元素的双击事件
$(selector).focus(function) 触发或将函数绑定到被选元素的获得焦点事件
$(selector).mouseover(function) 触发或将函数绑定到被选元素的鼠标悬停事件
$(selector).mouseleave(function) 触发或将函数绑定到被选元素的mouse leave事件
$(selector).change(function) 触发或将函数绑定到被选元素的change事件
$(selector).keydown(function) 触发或将函数绑定到被选元素的key down事件
$(selector).keyup(function) 触发或将函数绑定到被选元素的key up事件

Chap3-66.html

<head><script src="js/jquery-3.3.1.js"></script>
<style>.s1{display: inline-block;width: 80px;height:40px;font-size: x-large}
       .s2{display: inline-block;width: 200px;height:40px;font-size: x-large}
       .outline{outline: medium dotted green}</style></head>
<body>
<pre>
    <span class="s1">用户名:</span><span class="s2"><input type="text"></span>
    <span class="s1">密码:</span><span class="s2"><input type="password"></span>
    <span class="s1">性别:</span><span class="s2"><input type="radio" name="sex" checked>男 <input type="radio" name="sex">女</span>
</pre>
<script>
    $(document).ready(function () {
        $(":text").keydown(function () {$(this).css("background-color","lightgreen");});
        $(":text").keyup(function () {$(this).css("background-color","lightgray");});
        $(":password").dblclick(function () {$(this).hide();});
        $(":radio").mousemove(function () {$(this).addClass("outline");});
        $(":radio").mouseleave(function () {$(this).removeClass("outline");});
    });
</script>

效果

方法 描述
animate() 对被选元素应用“自定义”的动画
clearQueue() 对被选元素移除所有排队的函数(仍未运行的)
delay() 对被选元素的所有排队函数(仍未运行)设置延迟
dequeue() 运行被选元素的下一个排队函数
fadeIn() 逐渐改变被选元素的不透明度,从隐藏到可见
fadeOut() 逐渐改变被选元素的不透明度,从可见到隐藏
fadeTo() 把被选元素逐渐改变至给定的不透明度
hide() 隐藏被选的元素
queue() 显示被选元素的排队函数
show() 显示被选的元素
slideDown() 通过调整高度来滑动显示被选元素
slideToggle() 对被选元素进行滑动隐藏和滑动显示的切换
slideUp() 通过调整高度来滑动隐藏被选元素
stop() 停止在被选元素上运行动画
toggle() 对被选元素进行隐藏和显示的切换
  • animate()方法执行CSS属性集的自定义动画。该方法通过CSS样式将元素从一个状态改变为另一个状态。CSS属性值是逐渐改变的,这样就可以创建动画效果。

    语法:$(selector).animate(styles,speed,easing,callback)

    参数 说明
    styles 必需。规定产生动画效果的 CSS 样式和值。 可能的 CSS 样式值:backgroundPositionborderWidthborderBottomWidthborderLeftWidthorderRightWidthborderTopWidthborderSpacingmarginmarginBottommarginLeftmarginRightmarginTopoutlineWidthpaddingpaddingBottompaddingLeftpaddingRightpaddingTopheightwidthmaxHeightmaxWidthminHeightminWidthfontfontSizebottomleftrighttopletterSpacingwordSpacinglineHeighttextIndent
    speed 可选。规定动画的速度。默认是normal。可能的值:毫秒(比如 1500)、slownormalfast
    easing 可选。规定在不同的动画点中设置动画速度的easing函数。内置的 easing函数:swinglinear
    callback 可选。animate函数执行完之后,要执行的函数。

    注意:只有数字值可创建动画(比如margin:30px)。字符串值无法创建动画(比如background-color:red)。

  • delay()方法对被选元素的所有排队函数(仍未运行)设置延迟。如果当前动画还没执行完,并不会立即停止,而是等当前动画执行完,然后延迟,再执行下一个动画。它常用在队列中的两个jQuery效果函数之间,从而在上一个动画效果执行后,延迟指定时间,然后再执行下一个动画。

    语法:$(selector).delay(duration,[queueName])

    参数 说明
    duration 延时时间,单位:毫秒
    queueName 队列名词,默认是Fx,动画队列。
  • queue()方法显示或操作在匹配元素上执行的函数队列。队列是一个或多个等待运行的函数。queue()方法通常与dequeue()方法一起使用。

    语法:$(selector).queue(queueName)

  • dequeue()方法为匹配元素执行序列中的下一个函数。当调用.dequeue()时,会从序列中删除下一个函数,然后执行它。该函数反过来会(直接或间接地)引发对.dequeue()的调用,这样序列才能继续下去。

    语法:$(selector).dequeue(queueName)

    注意:当通过.queue()添加函数时,我们应当确保最终调用了.dequeue(),这样下一个排队的函数才能执行。

    Chap3-68.html

    <body>
    <button id="start">开始动画</button><button id="stop">停止动画</button>
    <div style="background-color: green;height: 100px;width: 100px;position: absolute"></div>
    <script>
      $(document).ready(function () {
          var div=$("div");
          $("#start").click(function () {
              div.animate({height:"300px",opacity:"0.4"},"slow");
              div.animate({width:"300px",opacity:"0.8"},"slow");
              div.queue(function () {
                  div.css("background-color","red");
                  div.dequeue();
              })
              div.animate({height:"100px",opacity:"0.4"},"slow");
              div.animate({width:"100px",opacity:"0.8"},"slow");
              div.delay(1000).animate({width:"500px",opacity:"0.4"});
          });
          $("#stop").click(function () {
              div.clearQueue();
          });
      });
    </script>
    </body>
    
  • fadeIn()方法使用淡入效果来显示被选元素,假如该元素是隐藏的。

    语法:$(selector).fadeIn(speed,callback)

    参数 说明
    speed 可选。规定元素从隐藏到可见的速度。默认为normal。可能的值:毫秒 (比如1500)、slownormalfast。在设置速度的情况下,元素从隐藏到可见的过程中,会逐渐地改变其透明度(这样会创造淡入效果)。
    callback 可选。fadeIn函数执行完之后,要执行的函数。除非设置了speed参数,否则不能设置该参数。
  • fadeOut()方法使用淡出效果来隐藏被选元素,假如该元素是显示的。

    语法:$(selector).fadeOut(speed,callback)

  • fadeTo()方法将被选元素的不透明度逐渐地改变为指定的值。

    语法:$(selector).fadeTo(speed,opacity,callback)

    参数 说明
    opacity 必需。规定要淡入或淡出的透明度。必须是介于0.001.00之间的数字。

    Chap3-68.html

    <button id="fadeout">淡出</button><button id="fadein">淡入</button>
    <button id="fadeto">改变透明度</button>
    <div style="background-color: green;height: 100px;width: 100px;position: absolute"></div>
    <script>
      $(document).ready(function () {
          var div=$("div");
          $("#fadein").click(function () {
              div.fadeIn(1000,function () {
                  alert("fadeIn()方法已完成!");
              });
          });
          $("#fadeout").click(function () {
              div.fadeOut(1000,function () {
                  alert("fadeOut()方法已完成!");
              });
          });
          $("#fadeto").click(function () {
              div.fadeTo(2000,0.4,function () {
                  alert("fadeTo()方法已完成!");
              });
          });
    
  • hide()方法如果被选的元素已被显示,则隐藏该元素。

    语法:$(selector).hide(speed,callback)

  • show()方法如果被选元素已被隐藏,则显示这些元素。

    语法:$(selector).show(speed,callback)

  • slideDown()方法通过使用滑动效果,显示隐藏的被选元素。

    语法:$(selector).slideDown(speed,callback)

  • slideUp()方法通过使用滑动效果,隐藏被选元素,如果元素已显示出来的话。

    语法:

    $(selector).slideUp(speed,callback)

  • slideToggle()方法通过使用滑动效果(高度变化)来切换元素的可见状态。如果被选元素是可见的,则隐藏这些元素,如果被选元素是隐藏的,则显示这些元素。

    语法:$(selector).slideToggle(speed,callback)

    Chap3-68.html

    <button id="hide">隐藏</button><button id="show">显示</button><button id="slidedown">Slide Down</button> <button id="slideup">Slide Up</button><button id="slidetoggle">Slide Toggle</button>
    <div style="background-color: green;height: 100px;width: 100px;position: absolute"></div>
    
    $(document).ready(function () {
          var div=$("div");
          $("#hide").click(function () {
              div.hide(1000);
          });
          $("#show").click(function () {
              div.show(1000,function () {
                  div.css("background-color","blue");
              });
          });
          $("#slidedown").click(function () {
              div.slideDown(1000);
          });
          $("#slideup").click(function () {
              div.slideUp(1000);
          });
    $("#slidetoggle").click(function () {
              div.slideToggle(1000);
          });
      });
    

文档操作

方法 描述
addClass() 向匹配的元素添加指定的类名。
after() 在匹配的元素之后插入内容。
append() 向匹配元素集合中的每个元素结尾插入由参数指定的内容。
appendTo() 向目标结尾插入匹配元素集合中的每个元素。
attr() 设置或返回匹配元素的属性和值。
before() 在每个匹配的元素之前插入内容。
clone() 创建匹配元素集合的副本。
detach() 从DOM中移除匹配元素集合。
empty() 删除匹配的元素集合中所有的子节点。
hasClass() 检查匹配的元素是否拥有指定的类。
html() 设置或返回匹配的元素集合中的HTML内容。
insertAfter() 把匹配的元素插入到另一个指定的元素集合的后面。
insertBefore() 把匹配的元素插入到另一个指定的元素集合的前面。
prepend() 向匹配元素集合中的每个元素开头插入由参数指定的内容。
prependTo() 向目标开头插入匹配元素集合中的每个元素。
remove() 移除所有匹配的元素。
removeAttr() 从所有匹配的元素中移除指定的属性。
removeClass() 从所有匹配的元素中删除全部或者指定的类。
replaceAll() 用匹配的元素替换所有匹配到的元素。
replaceWith() 用新内容替换匹配的元素。
text() 设置或返回匹配元素的内容。
toggleClass() 从匹配的元素中添加或删除一个类。
unwrap() 移除并替换指定元素的父元素。
val() 设置或返回匹配元素的值。
wrap() 把匹配的元素用指定的内容或元素包裹起来。
wrapAll() 把所有匹配的元素用指定的内容或元素包裹起来。
wrapinner() 将每一个匹配的元素的子内容用指定的内容或元素包裹起来。
  • addClass()方法向被选元素添加一个或多个类。该方法不会移除已存在的class属性,仅仅添加一个或多个class属性。

    语法:$(selector).addClass(class)

  • hasClass()方法检查被选元素是否包含指定的 class。

    语法:$(selector).hasClass(class)

  • removeClass()方法从被选元素移除一个或多个类。如果没有规定参数,则该方法将从被选元素中删除所有类。

    语法:$(selector).removeClass(class)

  • toggleClass()对设置或移除被选元素的一个或多个类进行切换。

    语法:$(selector).toggleClass(class,switch)

    参数 说明
    switch 可选。布尔值。规定是否添加或移除class。

    Chap3-69.html

    <head><script src="js/jquery-3.3.1.js"></script>�  <style>�    .intro{font-size: x-large;color: blue}    .note{background-color: yellow}    .main{color:red}�  </style>�</head>�<body>�<h1>This is a heading</h1><p>This is a paragraph.</p><p>This is another paragraph.</p>�<button id="btn1">向第一个 p 元素添加两个类</button><button id="btn2">改变第一个段落的类</button>�<button id="btn3">切换段落的 "main" 类</button>�<script>�    $(document).ready(function(){�        $("#btn1").click(function(){�            $("p:first").addClass("intro note");�        });�        $("#btn2").click(function(){�            $("p:first").removeClass("intro").addClass("main");�        });�        $("#btn3").click(function(){�            $("p:first ").toggleClass("main");�        });�    });�</script>
    
    
  • after()方法在被选元素后插入指定的内容。

    语法:

  • append()方法在被选元素的结尾(仍然在内部)插入指定内容。

    语法:$(selector).append(content)

  • appendTo()方法在被选元素的结尾(仍然在内部)插入指定内容,规定要插入的内容必须包含 HTML 标签。

    语法:$(content).appendTo(selector)

  • before()方法在被选元素前插入指定的内容。

    语法:$(selector).before(content)

  • prepend()方法在被选元素的开头(仍位于内部)插入指定内容。

    语法:$(selector).prepend(content)

  • prependTo()方法在被选元素的开头(仍位于内部)插入指定内容,规定要插入的内容必须包含HTML标签。

    语法:$(content).prependTo(selector)

  • insertBefore()方法在被选元素之前插入HTML标记或已有的元素,规定要插入的内容必须包含HTML标签。

    语法:$(content).insertBefore(selector)

  • insertAfter()方法在被选元素之后插入HTML标记或已有的元素,规定要插入的内容必须包含HTML标签。

    语法:$(content).insertAfter(selector)

    **Chap3-69.html **

    <button id="btn4">after插入内容</button>
    <button id="btn5">append插入内容</button>
    <button id="btn6">appendTo插入内容</button>
    <button id="btn7">before插入内容</button>
    <button id="btn8">prepend插入内容</button>
    <button id="btn9">prependTo插入内容</button>
    <button id="btn10">insertBefore插入内容</button>
    <button id="btn11">insertAfter插入内容</button>
    <script>
        $(document).ready(function(){
            $("#btn4").click(function(){$("p:first").after("Hello World!"); });
            $("#btn5").click(function(){$("p:eq(1)").append("Hello World!"); });
            $("#btn6").click(function(){$("<b> Hello World!</b>").appendTo("p:eq(1)");});
            $("#btn7").click(function(){$("p:first").before("Hello World!");});
            $("#btn8").click(function(){$("p:eq(1)").prepend("Hello World!");});
            $("#btn9").click(function(){$("<b> Hello World!</b>").prependTo("p:eq(1)");});
            $("#btn10").click(function(){$("<b> 你好!</b>").insertBefore("p:eq(1)");});
            $("#btn11").click(function(){$("<b> 你好!</b>").insertAfter("p:eq(1)");});
        });
    
  • attr()方法设置或返回被选元素的属性和值。当该方法用于返回属性值,则返回第一个匹配元素的值。当该方法用于设置属性值,则为匹配元素设置一个或多个属性/值对。

    • 返回属性的值:$(selector).attr(attribute)
    • 设置属性和值:$(selector).attr(attribute,value)
    • 使用函数设置属性和值:$(selector).attr(attribute,function(index,currentvalue))
    • 设置多个属性和值:$(selector).attr({attribute:value, attribute:value,...})
    参数 描述
    attribute 规定属性的名称。
    value 规定属性的值。
    function(index,currentvalue) 规定要返回属性值到集合的函数 : index接受集合中元素的index位置;currentvalue接受被选元素的当前属性值。

    Chap3-69.html

    <p><img src="pic/apple.jpg" alt="red apple" width="284" height="213"></p>
    <button id="btn12">返回图片的宽度</button>
    <button id="btn13">图像宽度减少50 px</button>
    <button id="btn14">给图片设置宽度和高度属性</button>
    <script>
        $(document).ready(function(){
            
            $("#btn12").click(function(){alert("图片宽度: " + $("img").attr("width"));});
            $("#btn13").click(function(){
                $("img").attr("width",function (n,v) {
                    return v-50;
                })
            });
            $("#btn14").click(function(){$("img").attr({width:"150",height:"100"});});
        });
    
  • removeAttr()方法从被选元素移除一个或多个属性。

    语法:$(selector).removeAttr(attribute)

  • html()方法设置或返回被选元素的内容(innerHTML)。当该方法用于返回内容时,则返回第一个匹配元素的内容。当该方法用于设置内容时,则重写所有匹配元素的内容。

    • 返回内容:$(selector).html()
    • 设置内容:$(selector).html(content)
    • 使用函数设置内容:$(selector).html(function(index,currentcontent))
  • text()方法设置或返回被选元素的文本内容。当该方法用于返回内容时,则返回所有匹配元素的文本内容(会删除HTML标记)。当该方法用于设置内容时,则重写所有匹配元素的内容。

    • 返回文本内容:$(selector).text()
    • 设置文本内容:$(selector).text(content)
    • 使用函数设置文本内容:$(selector).text(function(index,currentcontent))

    Chap3-69.html

    <p class="main note">这是<b>另一个</b>段落。</p>
    <button id="btn15">删除所有 p 元素的 style 属性</button>
    <button id="btn16">html()修改第四个段落的内容</button>
    <button id="btn17">text()修改第四个段落的内容</button>
    <script>
        $(document).ready(function(){
            $("#btn15").click(function(){$("p").removeAttr("class");});
            $("#btn16").click(function(){
                $("p").html(function(n){
                    if(n===3){return $(this).html()+"这个P元素的索引位置为: " + n;}
                });
            });
            $("#btn17").click(function(){
                $("p:eq(3)").text(function(n){return $(this).text();});
            });
        });
    
  • val()方法返回或设置被选元素的value属性。当用于返回值时:该方法返回第一个匹配元素的 value 属性的值。当用于设置值时:该方法设置所有匹配元素的value属性的值。

    • 返回value属性:$(selector).val()
    • 设置value属性:$(selector).val(value)
    • 通过函数设置value属性:$(selector).val(function(index,currentvalue))
  • replaceWith() 和replaceAll()方法用指定的 HTML 内容或元素替换被选元素。差异在于语法:内容和选择器的位置,以及 replaceWith() 能够使用函数进行替换。

    语法:

    $(selector).replaceWith(content)
    $(content).replaceAll(selector)
    
  • wrap()方法把每个被选元素放置在指定的 HTML 内容或元素中。

    语法:$(selector).wrap(wrappingElement,function(index))

    参数 描述
    wrappingElement 必需。规定包裹每个被选元素的HTML元素。可能的值: HTML元素、jQuery对象、DOM 元素
    function(index) 可选。规定返回包裹元素的函数。index:返回集合中元素的index位置。
  • unwrap()方法移除被选元素的父元素。

    语法:$(selector).unwrap()

    Chap3-69.html

    <style> div{border: medium dotted black;width: 800px} </style>
    <p>姓名: <input type="text" name="fname" value="Peter"></p>
    <button id="btn18">返回第一个输入字段的值</button>
    <button id="btn19">设置姓名的值</button>
    <button id="btn20">替换每个P元素</button>
    <button id="btn21">创建一个新的div来环绕每个p元素</button>
    <button id="btn22">去掉 div 元素</button>
    
        $(document).ready(function(){
            $("#btn18").click(function(){$("p:eq(3)").text($("input:text").val());});
            $("#btn19").click(function(){
                $("input:text").val(function (n,c) {
                    return c+" Chen";
                });
            });
            $("#btn20").click(function(){
                $("p").replaceWith(function(n){
                    return "<h3>这个元素的下标是 " + n + ".</h3>";
                });
            });
            $("#btn21").click(function(){$("p").wrap(document.createElement("div"));});
            $("#btn22").click(function(){$("p").unwrap();});
    

CSS操作

css()方法为被选元素设置或返回一个或多个样式属性。

当用于返回属性:

  • 该方法返回第一个匹配元素的指定CSS属性值。然而,简写的CSS属性(比如backgroundborder)不被完全支持,且当用于返回属性值时,在不同的浏览器中有不同的结果。
  • 语法:$(selector).css(property)

当用于设置属性:

  • 该方法为所有匹配元素设置指定CSS属性。
  • 设置CSS属性和值:$(selector).css(property,value)
  • 使用函数设置CSS属性和值:$(selector).css(property,function(index,currentvalue))
  • 设置多个属性和值:$(selector).css({属性:value, 属性:value, ...})

Chap3-70.html

<body><p style="font-size: 30px;color: red">这是一个段落。</p>
<button id=“btn1”>返回P元素的CSS样式颜色值</button><button id=“btn2”>设置P元素的字体大小</button>
<button id="btn3">设置P元素的多个css属性</button>
<script>
    $(document).ready(function(){
        $("#btn1").click(function(){$("p").text($("p").css("color"));});
        $("#btn2").click(function(){
            $("p").css("font-size",function(i,v){
                return Number(v.substr(0,v.length-2))+10;
            });
        });
        $("#btn3").click(function(){
            $("p").css({
                "color":"white",
                "background-color":"lightgray",
                "font-family":"Arial",
                "font-size":"50px",
                "padding":"5px"
            });
        });
    });
</script>
</body>

客户端存储

客户端存储web应用允许使用浏览器提供的API实现将数据存储在用户电脑上。

客户端存储遵循"同源策略",因此不同站点的页面是无法读取对于存储的数据。而同一站点的不同的页面之间是可以互相共享存储数据的。

web应用可以选择他们存储数据的有效期:有临时存储的,可以让数据保存至当前窗口关闭或浏览器退出;也有永久存储,可以将数据永久地存储在硬盘上,数年或者数月不失效。

客户端存储有以下几种形式:

  • web存储

    先是HTML5的一个API,后来成了标准了。目前包含IE8在内的主流浏览器都实现了。包含localStorage和sessionStorage对象,这两个对象实际上是持久化的关联数组,是名值对的映射表,“名”和“值”都是字符串。web存储易于使用,支持大容量(但非无限量)数据存储,同时兼容当前所有主流浏览器。

  • cookie

    cookie是一种早期的客户端存储机制,起初是针对服务器端脚本设计使用的。尽管在客户端提供了非常繁琐的javascript API来操作cookie,但它们难用至极,而且只适合少量的文本存储。不仅如此,任何以cookie形式存储的数据,不论服务器端是否需要,每一次HTTP请求都要把这些数据传输到服务器端。cookie目前仍然被大量的客户端程序员使用的一个重要的原因是:所有新旧浏览器都支持它。但是,随着Web Storage的普及,cookie将最终回归到最初的形态:作为一种被服务端脚本使用的客户存储机制。

Web存储

实现了“web存储”草案标准的浏览器在window对象上定义了两个属性,localStorage和sessionStorage。在控制台输入window.localStorage可以获得页面所存储的数据。

这两个属性代表同一个Storage对象(一个持久化关联数组),数组使用字符串来索引,存储的值也是字符串形式的,Storage对象在使用上和一般的Javascript没有什么两样,设置对象的属性为字符串值,随后浏览器会将值存储起来。lcoalStorage和sessionStorage两者的区别在于存储的有效期和作用域不同:即数据可以存储多长时间以及谁拥有数据的访问权。

通过localStorage存储的数据是永久性的,除非web应用立刻删除存储的数据。或者用户通过设置浏览器配置来删除,否则数据将一直保留在用户电脑上,永不过期

localStorage的作用域是限定在文档源(document origin)级别的。文档源是通过协议、主机名及端口三者来确定的。因此,每个url都有不同的文档源。

sessionStorage的有效期与存储数据的脚本所在最顶层的窗口或者是浏览器标签页是一样的。一旦窗口或者标签页被永久关闭了,那么所有通过sessionStorage存储的数据也被删除了。

sessionStorage的作用域也是限定在文档源中,因此,非同源的文档之间是无法贡献sessionStorage的。不仅如此,sessionStorage的作用域还被限定在窗口中。如果同源的文档渲染在不同的浏览器标签页中,那么他们互相之间拥有的是各自的sessionStorage数据,无法共享。就是说,同个浏览器下两个相同的标签页也不能共享数据。

Chap3-71.html

<p>中文姓名:<input type="text" id="txtC"> </p><p>英文姓名:<input type="text" id="txtE"> </p>
<button id="btn1">Web存储-localStorage</button><button id="btn2">Web存储-sessionStorage</button>
 $(document).ready(function(){
        var nameC=localStorage.uNameC;    //等同于nameC=localStorage["unameC"]
        if(!nameC){
            $("#txtC").val("请输入用户名");
        }else{
            $("#txtC").val(localStorage.uNameC);
        }
        var nameE=sessionStorage.uNameE;
        if(!nameE){
            $("#txtE").val("Please input your name");
        }else{
            $("#txtE").val(sessionStorage.uNameE);
        }
        $("#btn1").click(function(){
            localStorage.uNameC=$("#txtC").val();
        });
        $("#btn2").click(function(){
            sessionStorage.uNameE=$("#txtE").val();
        });
    });

localStorage和sessionStorage通常是被当做普通的javascript对象使用。通过设置属性来存储字符串值,查询该属性来读取该值。这两个对象还提供了一些属性和方法:

属性或方法 语法 说明
length Storage.length 返回存储在Storage对象里的数据项数量。
setItem() storage.setItem(keyName, keyValue); 接受一个键名和值作为参数,将会把键名添加到存储中,如果键名已存在,则更新其对应的值。
getItem() storage.getItem(keyName); 接受一个键名作为参数,并返回对应键名的值。
removeItem() storage.removeItem(keyName); 接受一个键名作为参数,会把该键名从存储中移除。
clear() storage.clear(); 调用它可以清空存储对象里所有的键值。
key() storage.key(key); 接受一个数值n作为参数,返回存储对象第n个数据项的键名。

Chap3-71.html

<div id="results" style="font-size: x-large"></div>
$(document).ready(function(){
    localStorage.setItem('bgcolor', 'red');
    localStorage.setItem('font', 'Helvetica');
    localStorage.setItem('image', 'myCat.png');
    $("#results").html("删除前Storage中数据项:<br>");
    for(var i=0;i<localStorage.length;i++){
        $("#results").html(function (n,v) {
            return v+localStorage.key(i)+": "+localStorage.getItem(localStorage.key(i))+"<br>";
        });
    }
    localStorage.removeItem("image");
    $("#results").html(function (n,v) {
        return v+"删除后Storage中数据项:<br>";
    });
    for(var i=0;i<localStorage.length;i++){
        $("#results").html(function (n,v) {
            return v+localStorage.key(i)+": "+localStorage.getItem(localStorage.key(i))+"<br>";
        });
    }
  });
  $(document).ready(function(){
    localStorage.setItem('bgcolor', 'red');
    localStorage.setItem('font', 'Helvetica');
    localStorage.setItem('image', 'myCat.png');
    $("#results").html("删除前Storage中数据项:<br>");
    for(var i=0;i<localStorage.length;i++){
        $("#results").html(function (n,v) {
            return v+localStorage.key(i)+": "+localStorage.getItem(localStorage.key(i))+"<br>";
        });
    }
    localStorage.removeItem("image");
    $("#results").html(function (n,v) {
        return v+"删除后Storage中数据项:<br>";
    });
    for(var i=0;i<localStorage.length;i++){
        $("#results").html(function (n,v) {
            return v+localStorage.key(i)+": "+localStorage.getItem(localStorage.key(i))+"<br>";
        });
    }
  });

cookie

cookie是指web浏览器存储的少量数据,同时它是与具体的web页面或者站点相关的。cookie最早是设计为被服务端所用的,cookie数据会自动在web浏览器和web服务器之间传输,因此服务端脚本就可以读、写客户端的cookie值。

可以通过document.cookie来获取cookie值。详见3.2.1.3。

JSON

什么是JSON?

JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)。

JSON 是轻量级的文本数据交换格式。

JSON 使用JavaScript语法来描述数据对象,但是JSON仍然独立于语言和平台。JSON解析器和JSON库支持许多不同的编程语言。

JSON 具有自我描述性,更易理解。

JSON文件

JSON文件的文件类型是.json

JSON文本的 MIME 类型是application/json

JSON 语法规则

JSON 语法是 JavaScript 对象表示法语法的子集。

  • 数据在名称/值对中。
  • 数据由逗号分隔
  • 花括号保存对象。
  • 方括号保存数组。
{ 
  "employees": [ 
    { "firstName":"Bill" , "lastName":"Gates" }, 
    { "firstName":"George" , "lastName":"Bush" },
    { "firstName":"Thomas" , "lastName":"Carter" } 
  ]
} 

可以像这样访问 JavaScript 对象数组中的第一项:employees[0].lastName;

可以像这样修改数据:employees[0].lastName = "Jobs";

JSON 值可以是

  • 数字(整数或浮点数)
  • 字符串(在双引号中)
  • 逻辑值(true 或 false)
  • 数组(在方括号中)
  • 对象(在花括号中)
  • null

Chap3-72.html

<h2>在 JavaScript 中创建 JSON 对象</h2>
<p style="font-size: x-large">
    Name: <span id="jname"></span><br />
    Age: <span id="jage"></span><br />
    Address: <span id="jstreet"></span><br />
    Phone: <span id="jphone"></span><br />
</p>

<script type="text/javascript">
    var JSONObject= {
        "name":"Bill Gates",
        "street":"Fifth Avenue New York 666",
        "age":56,
        "phone":"555 1234567"};
    document.getElementById("jname").innerHTML=JSONObject.name;
    document.getElementById("jage").innerHTML=JSONObject.age;
    document.getElementById("jstreet").innerHTML=JSONObject.street;
    document.getElementById("jphone").innerHTML=JSONObject.phone;
</script>

JSON的用法

JSON 最常见的用法之一,是从web服务器上读取JSON数据(作为文件或作为 HttpRequest),将JSON数据转换为JavaScript对象,然后在网页中使用该数据。

JavaScript 函数eval()可用于将JSON文本转换为JavaScript对象。必须把文本包围在括号中,这样才能避免语法错误:

var obj = eval ("(" + txt + ")");

提示eval()函数可编译并执行任何JavaScript代码。这隐藏了一个潜在的安全问题。

使用JSON解析器将JSON转换为JavaScript对象是更安全的做法。JSON解析器只能识别JSON文本,而不会编译脚本。

JSON对象和JSON字符串的转换

  • JSON.parse(jsonString):在一个字符串中解析出JSON对象
  • jQuery.parseJSON(jsonString):将格式完好的JSON字符串转为与之对应的JavaScript对象
  • JSON.stringify(obj):将一个JSON对象转换成字符串

JSON.parse()jQuery.parseJSON()的区别:有的浏览器不支持JSON.parse()方法,使用jQuery.parseJSON()方法时,在浏览器支持时会返回执行JSON.parse()方法的结果,否则会返回类似执行eval()方法的结果。

Chap3-73.html

<h2>通过 JSON 字符串来创建对象</h3>
    <p style="font-size: x-large">
        First Name: <span id="fname1"></span><br />
        Last Name: <span id="lname1"></span><br />
        First Name: <span id="fname2"></span><br />
        Last Name: <span id="lname2"></span><br />
    </p>
    <script type="text/javascript">
        var txt = '{"employees":[' +
            '{"firstName":"Bill","lastName":"Gates" },' +
            '{"firstName":"George","lastName":"Bush" },' +
            '{"firstName":"Thomas","lastName":"Carter" }]}';
        var obj = eval ("(" + txt + ")");
        document.getElementById("fname1").innerHTML=obj.employees[1].firstName;
        document.getElementById("lname1").innerHTML=obj.employees[1].lastName;
        var obj1 = JSON.parse(txt);
        document.getElementById("fname2").innerHTML=obj1.employees[2].firstName;
        document.getElementById("lname2").innerHTML=obj1.employees[2].lastName;
    </script>

Node

  • Node概述
  • Node核心模块
  • Express框架

Node概述

Node,或者Node.js,是一个可以让JavaScript运行在服务器端的平台。因此,可以用Node.js轻松地进行服务器端应用开发,在服务端堪与PHP、Python、Perl、Ruby平起平坐。

使用 Node.js,你可以轻松地开发:

  • 具有复杂逻辑的网站;
  • 基于社交网络的大规模Web应用;
  • Web Socket服务器;
  • TCP/UDP套接字应用程序;
  • 命令行工具;
  • 交互式终端程序;
  • 带有图形用户界面的本地应用程序;
  • 单元测试工具;
  • 客户端JavaScript编译器。

当你使用Node.js时,不用额外搭建一个 HTTP 服务器,因为Node.js本身就内建了一个。这个服务器不仅可以用来调试代码,而且它本身就可以部署到产品环境,它的性能足以满足要求。

Node.js最大的特点就是采用异步式I/O与事件驱动的架构设计。Node.js在执行的过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式I/O请求完成后会被推送到事件队列,等待程序进程进行处理。

例如,对于简单而常见的数据库查询操作,按照传统方式实现的代码如下:

res = db.query('SELECT * from some_table');
res.output();

以上代码在执行到第一行的时候,线程会阻塞,等待数据库返回查询结果,然后再继续处理。

对于高并发的访问,一方面线程长期阻塞等待,另一方面为了应付新请求而不断增加线程,因此会浪费大量系统资源,同时线程的增多也会占用大量的CPU时间来处理内存上下文切换,而且还容易遭受低速连接攻击。

看看Node.js是如何解决这个问题的:

db.query('SELECT * from some_table', function(res) {
res.output();
});
//后面的语句

这段代码中db.query的第二个参数是一个函数,我们称为回调函数。进程在执行到db.query的时候,不会等待结果返回,而是直接继续执行后面的语句,直到进入事件循环。当数据库查询结果返回时,会将事件发送到事件队列,等到线程进入事件循环以后,才会调用之前的回调函数继续执行后面的逻辑。

Node.js的异步机制是基于事件的,所有的磁盘I/O、网络通信、数据库查询都以非阻塞的方式请求,返回的结果由事件循环来处理。

Node.js进程在同一时刻只会处理一个事件,完成后立即进入事件循环检查并处理后面的事件。这样做的好处是,CPU和内存在同一时间集中处理一件事,同时尽可能让耗时的I/O操作并行执行。对于低速连接攻击,Node.js只是在事件队列中增加请求,等待操作系统的回应,因而不会有任何多线程开销,很大程度上可以提高Web应用的健壮性,防止恶意攻击。

Node事件

Chap4-1-1.js(异步读取文件)

var fs=require('fs');   //内置文件系统模块fs
console.log("异步读取文件");
fs.readFile('files/content.txt','utf-8',function (err,data) {
    if(err){
        console.error(err);
    }else {
        console.log(data);
    }
});
console.log('end.');

Chap4-1-2.js(同步读取文件)

var fs=require('fs');   //内置文件系统模块fs
console.log("同步读取文件");
var data=fs.readFileSync('chap3.js','utf-8');
console.log(data);
console.log("end");

安装

https://nodejs.org/en/网站上下载Node 8.11.1 LTS,按步骤安装即可。

为了测试是否已经安装成功,我们在运行中输入cmd,打开命令提示符,然后输入node,将会进入Node.js的交互模式。

Node安装

通过这种方式安装的 Node.js 还自动附带了npm,我们可以在命令提示符中直接输入npm来使用它。

Node包管理器(npm)是一个由Node.js官方提供的第三方包管理工具,通过Node.js执行,能解决Node.js代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

可以通过输入npm -v来测试是否成功安装。

npm

建立HTTP服务器

Node.js是为网络而诞生的平台,不同于“浏览器-HTTP 服务器-PHP 解释器”的组织方式,Node.js将“HTTP服务器”这一层抽离,直接面向浏览器用户。

Node与PHP的架构

Chap4-2.js

var http=require('http');   //http模块可以创建服务器应用实例,也能发送http请求
http.createServer(function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html"});
    res.write("<h1>Node.js</h1>");
    res.end("<p style='font-size: x-large'>Hello World</p>");
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

模块与包

模块(Module)和包(Package)是Node.js最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。

Node.js提供了require函数来调用其他模块,而且模块都是基于文件的,机制十分简单。

我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布和维护。

创建模块

在 Node.js中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。

Node.js 提供了exportsrequire两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。

module.js

通过exports对象把setNamesayHello作为模块的访问接口。

var name;
exports.setName=function (theName) {
    name=theName;
};
exports.sayHello=function () {
    return "hello "+name;
};

Chap4-3.js

在chap4-3.js中通过require('./module')加载这个模块,然后就可以直接访问module.js中exports对象的成员函数了。

var http=require('http');   //http模块可以创建服务器应用实例,也能发送http请求
var mymodule=require("./module");
mymodule.setName("Zhang San");
http.createServer(function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html"});
    res.write("<h1>Node.js</h1>");
    res.end("<p style='font-size: x-large'>"+mymodule.sayHello()+"</p>");
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

npm提供的上万个模块都是通过这种简单的方式搭建起来的。

创建包

包是在模块基础上更深一步的抽象,它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js根据CommonJS规范实现了包机制,开发了npm来解决包的发布和获取需求。

Node.js的包是一个目录,其中包含一个 JSON 格式的包说明文件package.json。严格符合 CommonJS规范的包应该具备以下特征:

  • package.json必须在包的顶层目录下;
  • 二进制文件应该在bin目录下;
  • JavaScript代码应该在lib目录下;
  • 文档应该在doc目录下;
  • 单元测试应该在test目录下。

Node.js对包的要求并没有这么严格,只要顶层目录下有package.json,并符合一些规范即可。

作为文件夹的模块

模块与文件是一一对应的。文件不仅可以是 JavaScript 代码或二进制代码,还可以是一个文件夹。最简单的包,就是一个作为文件夹的模块。

例如:建立一个叫做somepackage的文件夹,在其中创建index.js:

exports.hello=function () {console.log("hello.");};

然后在somepackage之外建立Chap4-4.js:

Chap4-4.js

var somePackage=require("./somepackage");
somePackage.hello();

我们使用这种方法可以把文件夹封装为一个模块,即所谓的。包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。

package.json

通过定制package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。在前面例子中的somepackage文件夹下,我们创建一个叫做package.json的文件,内容如下所示:

{"main":"./lib/interface.js"}

然后将index.js重命名为interface.js并放入lib子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。

Node.js在调用某个包时,会首先检查包中package.json文件的main字段,将其作为包的接口模块,如果package.json或main字段不存在,会尝试寻找index.js或index.node作为包的接口。

package.json是CommonJS规定的用来描述包的文件,完全符合规范的package.json文件应该含有以下字段:

  • name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
  • description:包的简要说明。
  • version:符合语义化版本识别规范的版本字符串。
  • keywords:关键字数组,通常用于搜索。
  • maintainers:维护者数组,每个元素要包含 nameemail (可选)、web (可选)字段。
  • contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一个元素。
  • bugs:提交bug的地址,可以是网址或者电子邮件地址。
  • licenses:许可证数组,每个元素要包含 type (许可证的名称)和 url (链接到许可证文本的地址)字段。
  • main:入口文件,如果不存在这个字段,会自动按下面顺序查找:index.jsindex.nodeindex.json
  • dependencies:当前包所需的依赖包,一个关联数组,由包名称和版本号组成。
  • repositories:源代码的托管仓库位置,每个元素要包含 type (仓库的类型,如 git )、url (仓库的地址)和 path (相对于仓库的路径,可选)字段。

Node.js包管理器

npm是 Node.js 官方提供的包管理工具,它已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

获取一个包

使用 npm 安装包的命令格式为:npm [install/i] [package_name]

npm在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。

本地模式和全局模式

在使用npm安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用npm install命令就是采用本地模式,即把包安装到当前目录的node_modules子目录下。Node.js的require在加载模块时会尝试搜寻node_modules子目录,因此使用 npm 本地模式安装的包可以直接被引用。

npm 还有另一种不同的安装模式被成为全局模式,使用方法为:npm [install/i] -g [package_name]

当使用全局模式安装时,npm会将包安装到系统目录,譬如:C:\Users\admin\node_modules。

注意:使用全局模式安装的包并不能直接在JavaScript文件中用require获得,因为require不会搜索 /User/admin/node_modules。

总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装。

Node核心模块

  • 全局对象
  • 常用工具
  • 事件机制
  • 文件系统访问
  • URL &querystring模块
  • HTTP服务器与客户端

全局对象

Node.js 中的全局对象是global,所有全局变量(除了global本身以外)都是global对象的属性,例如consoleexportsprocess等。

process对象

process对象是一个全局变量,它提供当前Node.js进程的有关信息,以及控制当前Node.js进程。 因为是全局变量,所以无需使用require()

  • process.argv属性返回一个数组,这个数组包含了启动Node.js进程时的命令行参数。第一个元素为node,第二个元素为当前执行的JavaScript文件路径。剩余的元素为其他命令行参数。

    Chap4-5.js

    process.argv.forEach(function(val,index){
       console.log(index+":"+val);
    });
    

    运行以下命令,启动进程:

    node chap4-5.js one two=three four
    
  • process.stdout是标准输出流,通常我们使用的console.log()向标准输出打印字符,而process.stdout.write()函数提供了更底层的接口。

  • process.stdin是标准输入流。

    Chap4-5.js

    var num1, num2;
    process.stdout.write('请输入num1的值:');//向屏幕输出,提示信息,要求输入num1
    /*监听用户的输入*/
    process.stdin.on('data', function (chunk) {
        if (!num1) {
            num1 = Number(chunk);
            process.stdout.write('请输入num2的值');//向屏幕输出,提示信息,要求输入num2
        } else {
            num2 = Number(chunk);
            process.stdout.write('结果是:' + (num1 + num2));
        }
    });
    

console对象

用于向标准输出流(stdout)或标准错误流(stderr)输出字符。

  • console.log():向标准输出流打印字符并以换行符结束,其可以接受若干个参数。

    const count = 5;
    console.log('count: %d', count);
    
  • console.error():与console.log()用法相同,只是向标准错误流输出。

    console.error('error #%d', count);
    

__dirname属性

返回当前模块的文件夹名称。等同于__filenamepath.dirname()的值。

console.log(__dirname);  //返回D:\教学\讲义\网络技术与应用\programming\myNode\chap4

__filename属性

返回当前模块的文件名称。

console.log(__filename);    //返回D:\教学\讲义\网络技术与应用\programming\myNode\chap4\chap4-6.js

require()方法

引入模块:

  • 引入同目录下的包(根据文件夹名称)

    注意:包名称前面需要加./

    Chap4-3.js

    var http=require('http');   //http模块可以创建服务器应用实例,也能发送http请求
    var mymodule=require("./module");
    mymodule.setName("Zhang San");
    http.createServer(function (req,res) {
        res.writeHead(200,{"Content-Type":"text/html"});
        res.write("<h1>Node.js</h1>");
        res.end("<p style='font-size: x-large'>"+mymodule.sayHello()+"</p>");
    }).listen(3000);
    console.log("HTTP server is listening at port 3000.");
    
  • 引入同目录下node_modules目录下的包

    包的路径:\myNode\node\_modules\somepackage\index.js

    Chap4-7.js的路径:\myNode\chap4-7.js

    Chap4-7.js

    var somePackage=require("somepackage");
    somePackage.hello();
    

常用工具util

util模块,提供常用函数的集合,用于弥补核心JavaScript的功能过于精简的不足。 它可以通过以下方式使用:var util = require('util');

util.inherits(constructor, superConstructor)

从一个构造函数中继承原型方法到另一个。constructor的原型会被设置到一个从superConstructor创建的新对象上。

Chap4-8.js

var util=require("util");
function Base() {
    this.name="base";
    this.base=1991;
    this.sayHello=function () {
        console.log("hello "+this.name);
    };
}
Base.prototype.showName=function () {
    console.log(this.name);
};
function Sub() {
    this.name="sub";
}
util.inherits(Sub,Base);
var objBase=new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
var objSub=new Sub();
objSub.showName();
console.log(objSub);

输出结果:

base
hello base
Base { name: 'base', base: 1991, sayHello: [Function] }
sub
Sub { name: 'sub' }

util.inspect(object[, options])

返回object的字符串表示,主要用于调试。

options可用于改变格式化字符串的某些方面:

  • showHidden,如果为true,则object的不可枚举的符号与属性也会被包括在格式化后的结果中。默认为false
  • depth表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多少。如果不指定depth,默认会递归2层,指定为null表示将不限递归层数完整遍历对象。
  • color值为true,输出格式将会以ANSI颜色编码,通常用于在终端显示更漂亮的效果。

Chap4-9.js

var util=require("util");
function Person() {
    this.name="byvoid";
    this.toString=function () {
        return this.name;
    };
}
var obj=new Person();
console.log(util.inspect(obj));
console.log(util.inspect(obj,true));

输出结果:

Person { name: 'byvoid', toString: [Function] }

Person {
  name: 'byvoid',
  toString: 
   { [Function]
     [length]: 0,
     [name]: '',
     [arguments]: null,
     [caller]: null,
     [prototype]: { [constructor]: [Circular] } } }

事件机制

events是Node.js最重要的模块,没有“之一”,原因是Node.js本身架构就是事件式的,而它提供了唯一的接口,所以堪称Node.js事件编程的基石。events模块不仅用于用户代码与Node.js下层事件循环的交互,还几乎被所有的模块依赖。

events模块只提供了一个对象: events.EventEmitterEventEmitter的核心就是事件发射与事件监听器功能的封装。

EventEmitter的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter支持若干个事件监听器。当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

Chap4-10.js

var events=require("events");
var emitter=new events.EventEmitter();
emitter.on("someEvent",function (arg1,arg2) {
    console.log("Listener1",arg1,arg2);
});
emitter.on("someEvent",function (arg1,arg2) {
    console.log("Listener2",arg1,arg2);
});
emitter.emit("someEvent","byvoid",1991);

输出结果:

Listener1 byvoid 1991
Listener2 byvoid 1991
EventEmitter对象的方法 说明
addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部
on(event, listener) 为指定事件注册一个监听器,接受一个字符串event和一个回调函数
once(event, listener) 为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器
removeListener(event, listener) 移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。它接受两个参数,第一个是事件名称,第二个是回调函数名称
removeAllListeners([event]) 移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器
setMaxListeners(n) 默认情况下, EventEmitters如果你添加的监听器超过 10 个就会输出警告信息。setMaxListeners函数用于提高监听器的默认限制的数量
listeners(event) 返回指定事件的监听器数组
emit(event, [arg1], [arg2], [...]) 按参数的顺序执行每个监听器,如果事件有注册监听返回true,否则返回false
EventEmitter类方法 说明
listenerCount(emitter, event) 返回指定事件的监听器数量。

Chap4-10.js

var events = require('events');
var eventEmitter = new events.EventEmitter();
var listener1 = function listener1() {console.log('监听器 listener1 执行。');} // 监听器 #1
var listener2 = function listener2() {console.log('监听器 listener2 执行。');} // 监听器 #2
var listener3 = function listener3() {console.log('监听器 listener3 执行。');} // 监听器 #3
// 绑定 connection 事件,处理函数为 listener1
eventEmitter.addListener('connection', listener1 );
// 绑定 connection 事件,处理函数为 listener2
eventEmitter.on('connection', listener2 );
// 绑定 connection 事件,处理函数为 listener3
eventEmitter.once('connection', listener3 );
var eventListeners = require('events').EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + " 个监听器监听连接事件。");
// 处理 connection 事件
eventEmitter.emit('connection');
// 移除监绑定的 listener1 函数
eventEmitter.removeListener('connection', listener1 );
console.log("listener1 不再受监听。");
// 触发连接事件
eventEmitter.emit('connection');
eventListeners = require('events').EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + " 个监听器监听连接事件。");
console.log("程序执行完毕。");

文件系统访问

fs模块是文件操作的封装,它提供了文件的读取、写入、更名、删除、遍历目录、链接等文件系统操作。

Node导入文件系统模块(fs)语法如下所示:var fs = require("fs")

fs.readFile(filename[, encoding][, callback(err,data)])

filename,表示要读取的文件名;

encoding,是可选的,表示文件的字符编码;

callback,是回调函数,用于接收文件的内容。回调函数提供两个参数errdataerr表示有没有错误发生,data是文件内容。如果指定了encodingdata是一个解析后的字符串,否则data将会是以Buffer形式表示的二进制数据。

Chap4-11.js

var fs=require('fs');   //内置文件系统模块fs
fs.readFile('files/content.txt','utf-8',function (err,data) {
    if(err){
        console.error(err);
    }else {
        console.log("utf-8");
        console.log(data);
    }
});
fs.readFile('files/content.txt',function (err,data) {
    if(err){
        console.error(err);
    }else {
        console.log("缺省encoding");
        console.log(data);
    }
});

输出结果:

utf-8
Text 文本文件实例
缺省encoding
<Buffer 54 65 78 74 20 e6 96 87 e6 9c ac e6 96 87 e4 bb b6 e5 ae 9e e4 be 8b>

fs.readFileSync(filename, [encoding])

fs.readFile同步的版本。

它接受的参数和fs.readFile相同,而读取到的文件内容会以函数返回值的形式返回。如果有错误发生,fs将会抛出异常,你需要使用trycatch捕捉并处理异常。

Chap4-1-2.js

var fs=require('fs');   //内置文件系统模块fs
console.log("同步读取文件");
var data=fs.readFileSync('chap3.js','utf-8');
console.log(data);
console.log("end");

fs.open(path, flags, [mode], [callback(err, fd)])

异步地打开文件。

path为文件的路径;

flags可以是以下值:

  • r:以读取模式打开文件。
  • r+:以读写模式打开文件。
  • w:以写入模式打开文件,如果文件不存在则创建。
  • w+:以读写模式打开文件,如果文件不存在则创建。
  • a:以追加模式打开文件,如果文件不存在则创建。
  • a+:以读取追加模式打开文件,如果文件不存在则创建。

mode参数用于创建文件时给文件指定权限。但只有当文件被创建时才有效。默认为0666,可读写。

fd为一个整数,表示打开文件返回的文件描述符。

fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead, buffer)])

功能是从指定的文件描述符fd中读取数据并写入buffer指向的缓冲区对象。

offsetbuffer的写入偏移量;

length是要从文件中读取的字节数;

position是文件读取的起始位置,如果position的值为null,则会从当前文件指针的位置读取;

回调函数传递bytesRead和`buffer,分别表示读取的字节数和缓冲区对象。

fs.close(fd, [callback(err)])

为异步模式下关闭文件。

Chap4-12.js

一般来说,除非必要,否则不要使用这种方式读取文件,因为它要求你手动管理缓冲区和文件指针,尤其是在你不知道文件大小的时候,这将会是一件很麻烦的事情。

var fs=require("fs");
fs.open("files/content.txt","r",function (err,fd) {
    if(err){
        console.error(err);
        return;
    }
    var buf=new Buffer(8);
    fs.read(fd,buf,0,8,null,function (err,bytesRead,buffer) {
        if(err){
            console.error(err);
            return;
        }
        console.log("bytesRead:"+bytesRead);
        console.log(buffer);
    })
    fs.close(fd,function (err) {
        if(err){
            return console.error(err);
        }
    });
});

输出结果:

bytesRead:8
<Buffer 54 65 78 74 20 e6 96 87>

fs.writeFile(file, data[, encoding][, callback(err)])

功能是异步地写入数据到文件,如果文件已经存在,则替代文件;

file,文件名或文件描述符;

data可以是一个字符串或一个buffer

Chap4-13.js

var fs=require("fs");
console.log("准备写入文件");
fs.writeFile("files/input.txt","我是通过fs.writeFile写入文件的内容","utf8",function (err) {
    if(err){
        return console.error(err);
    }
    console.log("数据写入成功!");
})

fs.write(fd,buffer,offset,length,position,[callback(err, bytesWritten, buffer)])

功能是写入bufferfd指定的文件;

offsetbuffer中被写入偏移量;

length指定要写入的字节数;

position指向从文件开始写入数据的位置,如果position的值为null,则会从当前文件指针的位置写入;

回调函数传递bytesWrittenbuffer,分别表示写入的字节数和缓冲区对象。

在Node.js中,定义了一个Buffer类,该类用来创建一个专门存放二进制数据的缓存区。

Chap4-14.js

var fs=require("fs");
fs.open("files/index2.txt","w",function (err,fd) {
    if(err){
        return console.error(err);
    }
    var buffer=new Buffer("Hello World!");
    fs.write(fd,buffer,0,12,null,function (err,bytesWritten,buffer) {
        if(err){
            return console.error(err);
        }
        console.log(bytesWritten,buffer.slice(0,bytesWritten).toString());
        fs.close(fd);
    })
});

url & querystring模块

url模块

url模块提供了一些实用函数,用于 url处理与解析,url字符串是一个结构化的字符串,包含多个有意义的组成部分,被解析时,会返回一个URL对象,包含每个组成部分的属性。

url模块的方法有三个:

url.parse(urlStr[, parseQueryString][, slashesDenoteHost])

将url字符串转换成object对象

urlStr:需要处理的url字符串;

parseQueryString:是否将查询参数也解析成对象。为true时将使用查询模块分析查询字符串,默认为false

slashesDenoteHost:解析主机处理,双斜线表示主机。

  • 默认为false//foo/bar形式的字符串将被解释成{ pathname: '//foo/bar' }
  • 如果设置成true//foo/bar形式的字符串将被解释成{ host: 'foo', pathname: '/bar' }
url字符串组成
var url=require('url');
var url1='http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two';
console.log(url.parse(url1));
// { protocol: 'http:',    //使用协议
//   slashes: null,        //
//   auth: null,           // 验证信息
//   host: 'calc.gongjuji.net', //全小写的主机部分的URL,包括端口信息。
//   port: null,                //端口
//   hostname: 'calc.gongjuji.net',//小写的主机部分的主机
//   hash: '#one#two',             //页面锚点参数部分
//   search: '?name=zhangsan&age=18',//查询参数部分,带?
//   query: 'name=zhangsan&age=18',  //查询参数部分
//   pathname: '/byte/',             //目录部分
//   path: '/byte/?name=zhangsan&age=18',//目录+参数部分
//   href: 'http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two'  //最初解析的完整的网址。双方的协议和主机是小写。
// }

var url2='//www.gongjuji.net/byte/?name=zhangsan#one';
console.log(url.parse(url2,true,true));
// { protocol: null,  
//   slashes: true,  
//   auth: null,  
//   host: 'www.gongjuji.net',  
//   port: null,  
//   hostname: 'www.gongjuji.net',  
//   hash: '#one',  
//   search: '?name=zhangsan',  
//   query: { name: 'zhangsan' },  
//   pathname: '/byte/',  
//   path: '/byte/?name=zhangsan',  
//   href: '//www.gongjuji.net/byte/?name=zhangsan#one' } 

var url=require('url');
var url2='//www.gongjuji.net/byte/?name=zhangsan#one';
console.log(url.parse(url2,true));
// { protocol: null,
//   slashes: null,
//   auth: null,
//   host: null,
//   port: null,
//   hostname: null,
//   hash: '#one',
//   search: '?name=zhangsan',
//   query: { name: 'zhangsan' },
//   pathname: '//www.gongjuji.net/byte/',
//   path: '//www.gongjuji.net/byte/?name=zhangsan',
//   href: '//www.gongjuji.net/byte/?name=zhangsan#one' }
url.format(urlObj)

将json对象格式化成字符串。

var url=require('url');
var obj1={ protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'calc.gongjuji.net',
    port: null,
    hostname: 'calc.gongjuji.net',
    hash: '#one#two',
    search: '?name=zhangsan&age=18',
    query: 'name=zhangsan&age=18',
    pathname: '/byte/',
    path: '/byte/?name=zhangsan&age=18',
    href: 'http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two'
};
var url1=url.format(obj1);
console.log(url1);
//http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two

var url=require('url');
//请求参数为为json对象
var obj2={ protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'calc.gongjuji.net',
    port: null,
    hostname: 'calc.gongjuji.net',
    hash: '#one#two',
    search: '?name=zhangsan&age=18',
    query: { name: 'zhangsan', age: '18' }, //页面参数部分,已经解析成对象了
    pathname: '/byte/',
    path: '/byte/?name=zhangsan&age=18',
    href: 'http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two' };
var url2=url.format(obj2);
console.log(url2); 
//http://calc.gongjuji.net/byte/?name=zhangsan&age=18#one#two

var url=require('url');
//缺少参数的情况
var obj3={ protocol: null,
    slashes: true,
    auth: null,
    host: 'www.gongjuji.net',
    port: null,
    hostname: 'www.gongjuji.net',
    hash: '#one',
    search: '?name=zhangsan',
    query: { name: 'zhangsan' },
    pathname: '/byte/',
    path: '/byte/?name=zhangsan',
    href: '//www.gongjuji.net/byte/?name=zhangsan#one' };
var url3=url.format(obj3);
console.log(url3);
//www.gongjuji.net/byte/?name=zhangsan#one
url.resolve(from, to)

为URL或href插入或替换原有的标签。

from:源地址;

to:需要添加或替换的标签。

var url=require('url');
//指定相对路径
var url1=url.resolve('http://www.gongjuji.net/one/two/three','four');
console.log(url1); //http://www.gongjuji.net/one/two/four
//指定根目录的相对路径
var url3=url.resolve('http://www.gongjuji.net/one/two/three','/four');
console.log(url3); //http://www.gongjuji.net/four
//带参数的相对路径
var url2=url.resolve('http://www.gongjuji.net/one/two/three?name=zhangsan','four');
console.log(url2); //http://www.gongjuji.net/one/two/four
//非标准分隔符的原路径
var url4=url.resolve('http://www.gongjuji.net\\one#name1','/four');
console.log(url4);//http://www.gongjuji.net/four
//非标准分隔符的相对路径
var url5=url.resolve('http://www.gongjuji.net/one','\\two\\three');
console.log(url5);//http://www.gongjuji.net/two/three

querystring模块

querystring模块提供了一些实用函数,用于解析与格式化 URL 查询字符串。

querystring模块的方法有四个:

querystring模块方法 说明
querystring.escape(str) 将一个字符转义成一个编码
querystring.unescape(str) 将一个编码反转义成一个字符
querystring.stringify(obj[, sep[, eq[, options]]]) 将一个对象序列化为一个查询的字符串,中间使用&= 分别为字符串中的分隔符和赋值符。其中,obj:要序列化成 URL 查询字符串的对象。sep:用于界定查询字符串中的键值对的子字符串,默认为&eq:用于界定查询字符串中的键与值的子字符串,默认为=optionsencodeURIComponent <Function> 把对象中的字符转换成查询字符串时使用的函数,默认为querystring.escape()
querystring.parse(str[, sep[, eq[, options]]]) 将一个查询的字符串反序列化为一个对象,也就是说它与querystring.stringify是起相反作用的关系

Chap4-17.js

var queryString=require("querystring");
var str=queryString.escape("哈哈");
console.log(str);  //%E5%93%88%E5%93%88
console.log(queryString.unescape(str)); //哈哈
console.log(queryString.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }));
// 返回 'foo=bar&baz=qux&baz=quux&corge='
console.log(queryString.stringify({ foo: 'bar', baz: 'qux' }, ';', ':'));
// 返回 'foo:bar;baz:qux'
console.log(queryString.parse('name=pkcms&author=zh&date='));
//{ name: 'pkcms', author: 'zh', date: '' }
console.log(queryString.parse('name=pkcms&author=zh&author=hhy&date='));
//相同的参数名会反序列化为一个数组,{ name: 'pkcms', author: [ 'zh', 'hhy' ], date: '' }
console.log(queryString.parse('name=pkcms,author=zh,date=',','));
//第2个参数是用来指明分隔符是用了哪个字符,{ name: 'pkcms', author: 'zh', date: '' }
console.log(queryString.parse('name:pkcms,author:zh,date:',',',':'));
//第3个参数是用来指明赋值符是用了哪个字符,{ name: 'pkcms', author: 'zh', date: '' }

HTTP服务器与客户端

Node.js 标准库提供了http模块,其中封装了一个高效的HTTP服务器和一个简易的HTTP客户端。http.Server是一个基于事件的HTTP服务器,http.request则是一个HTTP客户端工具,用于向HTTP服务器发起请求。

HTTP服务器

http.Serverhttp模块中的HTTP服务器对象,用Node.js做的所有基于HTTP协议的系统,如网站、社交应用甚至代理服务器,都是基于http.Server实现的。

http.Server是一个基于事件的HTTP服务器,所有的请求都被封装为独立的事件,开发者只需要对它的事件编写响应函数即可实现HTTP服务器的所有功能。它继承自EventEmitter,提供了以下几个事件:

  • request:当客户端请求到来时,该事件被触发,提供两个参数reqres,分别是http.ServerRequesthttp.ServerResponse的实例,表示请求和响应信息。

  • connection:当TCP连接建立时,该事件被触发,提供一个参数socket,为net.Socket的实例。connection事件的粒度要大于request,因为客户端在Keep-Alive模式下可能会在同一个连接内发送多次请求。

  • close:当服务器关闭时,该事件被触发。注意不是在用户连接断开时。

在这些事件中, 最常用的就是request了, 因此http提供了一个捷径:http.createServer([requestListener]) , 功能是创建一个HTTP服务器并将requestListener作为 request 事件的监听函数。

Chap4-2.js

var http=require('http')
http.createServer(function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html"});
    res.write("<h1>Node.js</h1>");
    res.end("<p style='font-size: x-large'>Hello World</p>");
}).listen(3000);

Chap4-15.js

var http=require("http");
var server=new http.Server();
var count=0;
server.on("connection",function () {
    console.log("someone connected!")
});
server.on("request",function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html"});
    res.write("<h1>Node.js</h1>");
    res.end("<p style='font-size: x-large'>Hello World</p>");
});
server.on("close",function () {
    console.log("server closed!")
});
server.listen(3000);
setTimeout(function () {
    server.close();
},5000);
console.log("HTTP server is listening at port 3000.");
http.ServerRequest

http.ServerRequest是HTTP请求的信息,是后端开发者最关注的内容。它一般由http.Serverrequest事件发送,作为第一个参数传递,通常简称requestreq

HTTP请求一般可以分为两部分:请求头(Request Header)和请求体(Requset Body)

ServerRequest的属性 说明
complete 客户端请求是否已经发送完成
httpVersion HTTP协议版本,通常是 1.01.1
method HTTP请求方法,如 GETPOSTPUTDELETE
url 原始的请求路径,例如/static/image/x.jpg/user?name=byvoid
headers HTTP 请求头
trailers HTTP 请求尾(不常见)
connection 当前 HTTP 连接套接字,为 net.Socket 的实例
socket connection 属性的别名
client client 属性的别名

http.ServerRequest提供了以下3个事件用于控制请求体传输:

  • data :当请求体数据到来时,该事件被触发。该事件提供一个参数 chunk,表示接收到的数据。如果该事件没有被监听,那么请求体将会被抛弃。该事件可能会被调用多次。
  • end :当请求体数据传输完成时,该事件被触发,此后将不会再有数据到来。
  • close: 用户当前请求结束时,该事件被触发。不同于 end,如果用户强制终止了传输,也还是调用close
获取 GET 请求内容

由于 GET 请求直接被嵌入在路径中,URL是完整的请求路径,包括了 ? 后面的部分,因此你可以手动解析后面的内容作为 GET请求的参数。Node.js 的 url 模块中的 parse 函数提供了这个功能。

var http=require("http");
var url=require("url");
http.createServer(function (req,res) {
   console.log(url.parse(req.url));
}).listen(3000);
http://localhost:3000/chap4-16?name=zhangsan&password=123456
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=zhangsan&password=123456',
  query: 'name=zhangsan&password=123456',
  pathname: '/chap4-16',
  path: '/chap4-16?name=zhangsan&password=123456',
  href: '/chap4-16?name=zhangsan&password=123456' }

Chap4-16.html

<form action="http://localhost:3000/myNode/chap4/chap4-16.js" method="get">
    <span>用户名:</span><input type="text" name="user">
    <span>密码:</span><input type="password" name="password">
    <input type="submit" value="提交">
</form>

Chap4-16.js

var http=require("http");
var url=require("url");
var queryString=require("querystring");
http.createServer(function (req,res) {
    //可替换部分开始
    var query=url.parse(req.url).query;
    console.log(query);
    var params=queryString.parse(query);
    res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
    res.write("用户名为:"+params.user);
    res.end("密码为:"+params.password);
    //可替换部分结束
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

可替换部分:

var query=url.parse(req.url,true).query;
res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
res.write("用户名为:"+query.user);
res.end("密码为:"+query.password);
获取 POST 请求内容

POST 请求的内容全部都在请求体中。

http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作,譬如上传文件。所以 Node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。

http.ServerResponse

http.ServerResponse 是返回给客户端的信息,决定了用户最终能看到的结果。作为第二个参数传递,一般简称为responseres

http.ServerResponse 有三个重要的成员函数,用于返回响应头、响应内容以及结束请求。

  • response.writeHead(statusCode, [headers]):向请求的客户端发送响应头。statusCode 是 HTTP 状态码,如 200 (请求成功)、404 (未找到)等。headers是一个类似关联数组的对象,表示响应头的每个属性。该函数在一个请求内最多只能调用一次,如果不调用,则会自动生成一个响应头。

  • response.write(data, [encoding]):向请求的客户端发送响应内容。data 是一个 Buffer 或字符串,表示要发送的内容。如果 data 是字符串,那么需要指定encoding 来说明它的编码方式,默认是 utf-8。在 response.end 调用之前,response.write 可以被多次调用。

  • response.end([data], [encoding]):结束响应,告知客户端所有发送已经完成。当所有要返回的内容发送完毕的时候,该函数必须被调用一次。它接受两个可选参数,意义和 response.write 相同。如果不调用该函数,客户端将永远处于等待状态。

Chap4-18.html

<form action="http://localhost:3000/myNode/chap4/chap4-18.js" method=“post">
    <span>用户名:</span><input type="text" name="user">
    <span>密码:</span><input type="password" name="password">
    <input type="submit" value="提交">
</form>

Chap4-18.js

var http=require("http");
var querystring=require("querystring");
var util=require("util");
http.createServer(function (req,res) {
    var post="";
    req.on("data",function (chunk) {
        post+=chunk;
    });
    req.on("end",function () {
        post=querystring.parse(post);
        res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
        res.write("用户名为:"+post.user);
        res.end("密码为:"+post.password);
    });
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

HTTP 客户端

http模块提供了两个函数http.requesthttp.get,功能是作为客户端向 HTTP 服务器发起请求。

http.request(options, callback) 发起 HTTP 请求,并返回一个 http.ClientRequest 的实例。接受两个参数,option 是一个类似关联数组的对象,表示请求的参数,callback 是请求的回调函数。option常用的参数如下所示:

  • host :请求网站的域名或 IP 地址。
  • port :请求网站的端口,默认 80
  • method :请求方法,默认是 GET
  • path :请求的相对于根的路径,默认是/QueryString 应该包含在其中。例如 /search?query=byvoid
  • headers :一个关联数组对象,为请求头的内容。

http.get(options, callback)http 模块还提供了一个更加简便的方法用于处理GET请求:http.get。它是 http.request 的简化版,唯一的区别在于http.get自动将请求方法设为了 GET 请求,同时不需要手动调用 req.end()

Chap4-19.js

var http=require('http');
http.createServer(function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
    //get 请求外网
    var html='';
    http.get('http://www.njfu.edu.cn',function(req1,res1){
        req1.on('data',function(data){
            html+=data;
        });
        req1.on('end',function(){
            res.end(html);
        });
    });
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

Chap4-20.js

var http=require('http');
var querystring=require('querystring');
http.createServer(function (req,res) {
    res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
    //发送 http Post 请求
    var postData=querystring.stringify({
        msg:'中文内容'
    });
    var options={
        hostname:'www.njfu.edu.cn',
        port:80,
        path:'/',
        method:'POST',
        headers:{
            'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
            'Content-Length':Buffer.byteLength(postData)
        }
    };
    var html='';
    var req1=http.request(options, function(res1) {
        res1.setEncoding('utf-8');
        res1.on('data',function(chun){
            html+=chun;
        });
        res1.on('end',function(){
            res.end(html);
        });
    });
    req1.write(postData); // write data to request body
    req1.end();
}).listen(3000);
console.log("HTTP server is listening at port 3000.");

Express框架

  • Express框架概述
  • 路由
  • 模板引擎
  • 数据库集成

Express框架概述

Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。Express 作为开发框架,因为它是目前最稳定、使用最广泛,而且 Node.js 官方推荐的唯一一个 Web 开发框架。

使用 Express 可以快速地搭建一个完整功能的网站。Express 框架核心特性:

  • 可以设置中间件来响应 HTTP 请求。
  • 定义了路由表用于执行不同的 HTTP 请求动作。
  • 可以通过向模板传递参数来动态渲染 HTML 页面。

准备工作

  1. 安装Express:

    npm install express -g              //全局安装
    npm install express-generator -g    //安装全局变量
    
  2. 初始化项目:

    cd example            //进入项目文件夹 
    express project       //创建express目录,project是目录名 
    
  3. 执行如下命令:

    cd project           //进入项目根目录 
    npm install          //安装依赖 
    

最终目录:

express目录
  • /bin:用来启动应用(服务器)
  • node_modules:存放 package.json 中安装的模块,当你在 package.json 添加依赖的模块并安装后,存放在这
  • /public:存放静态资源(image、css、js)目录
  • /routes:路由用于确定应用程序如何响应对特定端点的客户机请求,包含一个 URI(或路径)和一个特定的 HTTP 请求方法(GET、POST 等)。每个路由可以具有一个或多个处理程序函数,这些函数在路由匹配时执行。
  • /views:模板文件所在目录
  • app.js程序:main文件,这个是服务器启动的入口
  • package.json:存储着工程的信息及模块依赖,当在 dependencies 中添加依赖的模块时,运行 npm install

app.js节略

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// 路由信息(接口地址),存放在routes的根目录
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// 模板开始
app.set('views', path.join(__dirname, 'views'));
app.engine('.html', require('ejs').__express);
app.set('view engine', 'html');

//略

//配置路由,('自定义路径',上面设置的接口地址)
app.use('/', indexRouter);�app.use('/users', usersRouter);

安装ejs模块:npm install ejs

index.js

var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
    res.type("text/html");
    res.render("home",{pic:"images/njfu.jpg"});   //重要
});
module.exports = router;

users.js

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.type("text/html");
  res.send('<h1>Welcome to Nanjing Forestry University</h1>');
});

module.exports = router;

home.html

<body class="HolyGrail">
<header>
 <img src=<%=pic%> width="100%" height="200">

<!-- <%= pic %> 的标签,功能是显示引用的变量,即 res.render 函数第二个参数传入的对象的属性。-->

</header>
<div class="HolyGrail-body">
    <div class="HolyGrail-nav">
    <ul>
            …
        </ul>
    </div>
    <div class="HolyGrail-content">
        <h1 style="text-indent: 20px">南林新闻</h1>
        <ul>
            …
        </ul>
    </div>
</div>
<footer>
    <h5>版权所有 © 2003-2012 南京林业大学 保留所有权利 地址:南京市龙蟠路159号</h5>
</footer>
</body>

启动服务器:npm start

启动完成后终端将输出 node ./bin/www

在浏览器中访问 http://localhost:3000/http://localhost:3000/users

Express网站的架构

浏览器发起请求,由路由控制器接受,根据不同的路径定向到不同的控制器。控制器处理用户的具体请求,可能会访问数据库中的对象,即模型部分。控制器还要访问模板引擎,生成视图的 HTML,最后再由控制器返回给浏览器,完成一次请求。

Express网站的架构

路由

路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求。

路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,它的结构如下:

router.METHOD(path, [callback...], callback)
  • routerexpress.Router 对象的一个实例,Router 实例是一个完整的中间件和路由系统。
  • METHOD 是一个 HTTP 请求方法,如GETPOST等。
  • path 是服务器上的路径。
  • callback 是当路由匹配时要执行的函数。
// GET method route
router.get('/', function (req, res) {
    res.send('GET request to the homepage');
});

// POST method route
router.post('/', function (req, res) {
    res.send('POST request to the homepage');
});

路由路径

路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式。

// 匹配根路径的请求 
router.get('/', function (req, res) { 
    res.send('root'); 
}); 
// 匹配 /about 路径的请求 
router.get('/about', function (req, res) { 
    res.send('about'); 
}); 
// 匹配 /random.text 路径的请求 
router.get('/random.text', function (req, res) { 
    res.send('random.text'); 
}); 
// 匹配 acd 和 abcd 
router.get('/ab?cd', function(req, res) { 
    res.send('ab?cd'); 
}); 
// 匹配 abcd、abbcd、abbbcd等 
router.get('/ab+cd', function(req, res) { 
    res.send('ab+cd'); 
}); 
// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等 
router.get('/ab*cd', function(req, res) { 
    res.send('ab*cd'); 
}); 
// 匹配 /abe 和 /abcde 
router.get('/ab(cd)?e', function(req, res) { 
    res.send('ab(cd)?e'); 
}); 

路由句柄

可以为请求处理提供多个回调函数,其行为类似中间件。唯一的区别是这些回调函数有可能调用 next('route') 方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。

路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合。

app.js节略

// 路由信息(接口地址),存放在routes的根目录
var chap422Router = require('./routes/chap4-22');

//配置路由,('自定义路径',上面设置的接口地址)
app.use("/chap4-22",chap422Router);

chap4-22.js

var express = require('express');
var router = express.Router();
router.get('/a', function (req, res) {
    res.type("text/html");
    res.send('<h1>Hello from A!</h1>');
});
router.get('/b', function (req, res, next) {
    console.log('response will be sent by the next function ...');
    next();
}, function (req, res) {
    res.type("text/html");
    res.send('<h1>Hello from B!</h1>');
});
var cb0 = function (req, res, next) {
    console.log('CB0');
    next();
};
var cb1 = function (req, res, next) {
    console.log('CB1');
    next();
};
var cb2 = function (req, res) {
    res.type("text/html");
    res.send('<h1>Hello from C!</h1>');
};
router.get('/c', [cb0, cb1, cb2]);
router.get('/d', [cb0, cb1], function (req, res, next) {
    console.log('response will be sent by the next function ...');
    next();
}, function (req, res) {
    res.type("text/html");
    res.send('<h1>Hello from D!</h1>');
});
module.exports = router;

响应方法

响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。

res.download(path [, filename] [, callback])方法使用res.sendfile()传输文件。

  • path:所需传输文件的路径(绝对路径),通常情况下浏览器会弹出一个下载文件的窗口。
  • filename:指定下载文件窗口中显示的默认文件名称。

app.js节略

app.get("/download",function (req, res) {
    var q = req.query;
    if (q.type == "jpg") {
        res.download(__dirname + "/public/images/njfu.jpg", "南林.jpg");
    } else if (q.type == "txt") {
        res.download(__dirname + "/public/files/content.txt");
    } else {
        res.send("错误的请求");
    }
});

res.end([data] [, encoding])方法终结响应处理流程。

res.json([body])方法发送一个json的响应。这个方法和将一个对象或者一个数组作为参数传递给res.send()方法的效果相同。

res.redirect([status,] path)方法发送重定向请求。

  • status: {Number},表示要设置的HTTP状态码,如果不指定http状态码,使用默认的状态码302:"Found"
  • path: {String},要设置到Location头中的URL

res.send([body])方法发送各种类型的响应。

res.render(view [, option] [, callback(err,html)])方法渲染视图模板(view),并将渲染后的HTML字符串发送到客户端。

  • view:视图模板文件名,默认在views文件夹下面;
  • option:对象,指定返回的数据;
  • callback(err,html):回调函数,err返回错误信息,html返回渲染的HTML字符串。注意:如果指定了回调函数,则服务器端不会自动将HTML字符串发送到客户端,需要用res.send()方法发送。

app.js节略

// 路由信息(接口地址),存放在routes的根目录
var chap423Router = require('./routes/chap4-23');
//配置路由,('自定义路径',上面设置的接口地址)
app.use("/chap4-23",chap423Router);

chap4-23.js

var express = require('express');
var url = require('url');
var router = express.Router();
router.get('/redirect', function (req, res) {
    res.redirect("/chap4-22/a");
});
router.get('/end', function (req, res) {
    res.type("text/html");
    res.end("<h1>终结响应处理流程</h1>");
});
router.get('/json', function (req, res) {
    res.json({user:"Zhangsan",password:"123456"});
});
router.get('/render', function (req, res) {
    var newpath=url.resolve(req.url,"/images/njfu.jpg");
    res.render("home",{pic:newpath},function (err,html) {
        if(err){
            console.error(err);
        }else{
            res.send(html);
        }
    });
});
module.exports = router;

请求方法

request是浏览器给服务器的请求,一般用到的是两种方法:postget,两种方法都会指定路由。request提供了三种方法来获取参数和内容:request.paramsrequest.queryrequest.body

  • request.body返回post方式传送的内容(名称数值对);

    app.js节略

    // 路由信息(接口地址),存放在routes的根目录
    var chap426Router = require('./routes/chap4-26');
    //ejs模板
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    //配置路由,('自定义路径',上面设置的接口地址)
    app.use("/chap4-26",chap426Router);
    

    chap4-26.js

    var express = require('express');
    var router = express.Router();
    
    router.get('/', function(req, res, next) {
        res.render('chap4-26');
    });
    router.post('/add', function (req, res) {
        res.type("text/html");
        res.write('<h1>姓名:'+req.body.name+'</h1>');
        res.write('<h1>年龄:'+req.body.age+'</h1>');
        res.end('<h1>职业:'+req.body.professional+'</h1>');
    });
    
    module.exports = router;
    

    chap4-26.ejs

    <div>
        <form action="/chap4-26/add" method="post">
            姓名:<input type="text" name="name">
            年龄:<input type="text" name="age">
            职业:<input type="text" name="professional">
            <input type="submit" value="post提交">
        </form>
    </div>
    
  • request.query返回get方式传送的内容,即获取URL中?后的名称数值对;

    chap4-26.ejs

    <div>
        <form action="/chap4-26/add1" method="get">
            姓名:<input type="text" name="name1">
            年龄:<input type="text" name="age1">
            职业:<input type="text" name="professional1">
            <input type="submit" value="get提交">
        </form>
    </div>
    

    chap4-26.js

    router.get('/add1', function (req, res) {
        res.type("text/html");
        res.write('<h1>姓名:'+req.query.name1+'</h1>');
        res.write('<h1>年龄:'+req.query.age1+'</h1>');
        res.end('<h1>职业:'+req.query.professional1+'</h1>');
    });
    
  • request.params返回从express路由器获取的参数。

    chap4-26.ejs

    <div>
        <a href="/chap4-26/Update/zhangsan">修改</a>
    </div>
    

    chap4-26.js

    router.get('/Update/:name', function (req, res) {
        res.type("text/html");
        res.send('<h1>姓名:'+req.params.name+'</h1>');
    });
    

模板引擎

模板引擎(Template Engine)是一个从页面模板根据一定的规则生成 HTML 的工具。在 MVC 架构中,模板引擎包含在服务器端。控制器得到用户请求后,从模型获取数据,调用模板引擎。模板引擎以数据和页面模板为输入,生成 HTML 页面,然后返回给控制器,由控制器交回客户端。

模板引擎

基于 JavaScript 的模板引擎有许多种实现,我们推荐使用 ejs(Embedded JavaScript),因为它十分简单,而且与 Express 集成良好。由于它是标准 JavaScript 实现的,因此它不仅可以运行在服务器端,还可以运行在浏览器中。

在 app.js 中通过以下两个语句设置了模板引擎和页面模板的位置:

app.js节略

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

ejs 的标签系统非常简单,它只有以下3种标签。

  • <% code %>:JavaScript 代码。
  • <%= code %>:显示替换过 HTML 特殊字符的内容。
  • <%- code %>:显示原始 HTML 内容。

chap4-24.ejs

<h1><%=title%></h1>
<p>Welcome to <%=title%></p>

chap4-24.js

var express = require('express');
var router = express.Router();

router.use("/",function (req, res, next) {
    res.render("chap4-24",{title:"Express"});
});
module.exports = router;

app.js节略

// 路由信息(接口地址),存放在routes的根目录
var chap425Router = require('./routes/chap4-25');
// 模板开始
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
//配置路由,('自定义路径',上面设置的接口地址)
app.use("/chap4-25",chap425Router);

chap4-25.ejs

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>EJS模板</title>
    <link rel="stylesheet" href="/stylesheets/chap4-25.css"/>
</head>
<body>
<h1>EJS模板引擎</h1>
<p>这是很简单的一个小流程就不在一一的标注流程了,注释的很清楚了</p>
<p>这里是姓名: <span><%= name %></span></p>
<p>这里是性别: <span><%= sex %></span></p>
<p>这里是性别: <span><%= content %></span></p>
</body>
</html>

chap4-25.css

h1{    text-align: center;}
p{    font-size:20px;}
span{    font-size:25px;    color: red;}

chap4-25.js

var express = require('express');
var router = express.Router();
var data={
    name : 'webarn',
    sex : '男',
    content : '参数,可以更改'
};
router.use("/",function (req, res, next) {
    res.render("chap4-25",data);
});
module.exports = router;

集成数据库

为 Express 应用添加连接数据库的能力,只需要加载相应数据库的 Node.js 驱动即可。

MySQL模块:npm install mysql

MySQL环境准备

安装MySQL时配置参数:

  • 服务器名:localhost(默认)
  • 用户名:root
  • 密码:0923

安装结束后,打开MySQL Workbench,选择要连接的服务器(localhost),在当前服务器中新建数据库(例如,educationdb),在当前数据库中用create table语句新建表格(例如:person),并添加数据。

-- ----------------------------
-- Table structure for person
-- ----------------------------
CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `professional` varchar(255) DEFAULT NULL,
)
-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', '武大', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('2', '王二', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('3', '张三', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('4', '李四', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('5', '孙五', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('6', '钱六', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('7', '赵七', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('8', '刘八', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('9', '张九', '25', '计算机科学与技术');
INSERT INTO `person` VALUES ('10', '郑十', '25', '计算机科学与技术');

连接

mysql模块是一个实现了MySQL协议的Node.js JavaScript客户端,通过这个模块可以与MySQL数据库建立连接、执行查询等操作。

建立连接可以使用createConnection()方法创建连接对象,再使用connect()建立连接。

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '0923',
    database: 'educationdb'
});
//可替换部分开始
connection.connect(function(err) {
    if (err) {
        return console.error('连接错误: ' + err.stack);
    }
    console.log('连接ID ' + connection.threadId);
});
//可替换部分结束

建立连接也可以隐式的由查询自动创建:

可替换部分:

connection.query('SELECT * from person', function(err, rows) {
    // 如果没有错误,则会连接成功!
});

在创建连接时,需要传入一些选项。可传入的选项参数如下:

  • host-连接主机名(默认为localhost
  • port-连接端口号(默认为3306
  • user-MySql连接用户名
  • password-MySql连接密码
  • database-要连接的数据库
  • connectTimeout-连接超时时间(默认10000毫秒)

关闭数据库连接可以使用两种方法。

  • 通过end()方法,在执行结束后关闭连接:

    connection.end(function(err) {
        // The connection is terminated now
    });
    
  • 使用destroy()方法,这个方法会立即关闭连接套接字,且这个方法没有回调函数:

    connection.destroy();
    

连接池

数据库连接是一种有限的,能够显著影响到整个应用程序的伸缩性和健壮性的资源,在多用户的网页应用程序中体现得尤为突出。

数据库连接池正是针对这个问题提出来的,它会负责分配、管理和释放数据库连接:允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个连接;释放空闲时间超过最大允许空闲时间的数据库连接,从而避免因为连接未释放而引起的数据库连接遗漏。

数据库连接池能明显提高对数据库操作的性能。

连接池连接时通过createPool()方法可以使用连接池连接。

当使用完连接池,要关闭所有连接,可以使用end()方法关闭连接池中的所有连接:

pool.end(function (err) {
    // all connections in the pool have ended
});

通过getConnection()方法连接可以共享一个连接,或管理多个连接。

连接使用完后通过调用connection.release()方法可以将连接返回到连接池中,这个连接可以被其它人重复使用。

connection.release()方法并不会将连接从连接池中移除,如果需要关闭连接且从连接池中移除,可以使用connection.destroy()

db.js

// 数据库包
var mysql = require(‘mysql’);
var pool = mysql.createPool({
    host: ‘localhost’,
    user: ‘root’,
    password: ‘0923’,
    database: ‘educationdb’
});

function query(sql, callback) {
    pool.getConnection(function (err, connection) {
        // Use the connection
        connection.query(sql, function (err, rows) {
            callback(err, rows);
            connection.release();//释放连接
        });
    });
}
exports.query = query;

执行SQL语句

对数据库的操作都是通过SQL语句实现的,通过SQL语句可以实现创建数据库、创建表、及对表中数据库的增/删/改/查等操作。

执行查询:在node-mysql中,通过ConnectionPool实例的query()执行SQL语句,所执行的SQL语句可以是一个SELECT查询或是其它数据库操作。query()方法有以下三种形式:

  • connection.query(sqlString, function(err,results,fields))

    • sqlString:要执行的SQL语句;
    • results:为查询结果;
    • fields:包含查询结果的字段信息。
    connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields){});
    
  • connection.query(sqlString, values, function(err,results,fields))

    • values:数组,表示要应用到查询占位符的值:
    connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields){});
    
    var post  = {id: 1, title: 'Hello MySQL'};
    var query = connection.query('INSERT INTO posts SET ?', post, function(err, result) {
        //...
    });
    console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL‘
    
    //标识符也可以使用??标识符占位符
    var userId = 1;
    var columns = ['username', 'email'];
    var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function(err, results) {
        // ...
    });
    console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
    
  • connection.query(options, function(err,results,fields))

    • options:对象,表示查询选项参数

      connection.query({
        sql: 'SELECT * FROM `books` WHERE `author` = ?',
        values: ['David']
      }, function (error, results, fields) {});
      
      
    • 当使用参数占位符时,第二种和第三种形式也可以结合使用。如:

      connection.query({
        sql: 'SELECT * FROM `books` WHERE `author` = ?',
      }, ['David'], function (error, results, fields) {});
      

app.js节略

// 路由信息(接口地址),存放在routes的根目录
var persons = require('./routes/person');

//ejs模板
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

//配置路由,('自定义路径',上面设置的接口地址)
app.use("/persons",persons);

person.js

var express = require('express');
var router = express.Router();
var db = require("./db.js");  //引入数据库包
router.get('/', function (req, res, next) {
    db.query('select * from person', function (err, rows) {
        if (err) {
            res.render('persons', {title: '人员管理', datas: []});
        } else {
            res.render('persons', {title: '人员管理', datas: rows});
        }
    })
});
//删除
router.get('/del/:id', function (req, res) {
    var id = req.params.id;
    db.query("delete from person where id=" + id, function (err, rows) {
        if (err) {
            res.end('删除失败:' + err)
        } else {
            res.redirect('/persons')
        }
    });
});
//新增
router.get('/add', function (req, res) {
    res.render('add');
});
router.post('/add', function (req, res) {
    var name = req.body.name;
    var age = req.body.age;
    var professional = req.body.professional;
    db.query("insert into person(name,age,professional) values('" + name + "'," + age + ",'" + professional + "')", function (err, rows) {
        if (err) {
            res.end('新增失败:' + err);
        } else {
            res.redirect('/persons');
        }
    })
});

//修改
router.get(‘/toUpdate/:id’, function (req, res) {
    var id = req.params.id;
    db.query(“select * from person where id=” + id, function (err, rows) {
        if (err) {
            res.end(‘修改页面跳转失败:’ + err);
        } else {
            res.render(“update”, {datas: rows});       //直接跳转
        }
    });
});
router.post('/update', function (req, res) {
    var id = req.body.id;
    var name = req.body.name;
    var age = req.body.age;
    var professional = req.body.professional;
    db.query("update person set name='" + name + "',age='" + age + "',professional= '" + professional + "' where id=" + id, function (err, rows) {
        if (err) {
            res.end('修改失败:' + err);
        } else {
            res.redirect('/persons');
        }
    });});
//查询
router.post('/search', function (req, res) {
    var name = req.body.s_name;
    var age = req.body.s_age;
    var professional = req.body.s_professional;
    var sql = "select * from person";
    if (name) {
        sql += " and name like '%" + name + "%' ";
    }
    if (age) {
        sql += " and age=" + age + " ";
    }
    if (professional) {
        sql += " and professional like '%" + professional + "%' ";
    }
    sql = sql.replace("person and","person where");
    db.query(sql, function (err, rows) {
        if (err) {
            res.end("查询失败:", err)
        } else {
            res.render("persons", {title: '人员管理', datas: rows});
        }
    });});
module.exports = router;

persons.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <style>
        div{width:800px;margin: 0 auto;}
        table {border-collapse:collapse;border-spacing:0;width:800px;}
        table tr td ,table tr th {border-top:solid 1px #ccc;border-left:solid 1px #ccc;line-height: 40px;text-align: center;}
        table tr td:last-child, table tr th:last-child {border-right:solid 1px #ccc;}
        table tr:last-child td{border-bottom:solid 1px #ccc;}
        a{text-decoration: none;font-size: 14px;}
        .text{width:150px;}
    </style>
</head>
<body>
<div>
    <div style="">
        <div style="float: left;width:10%;">
            <a href="/persons/add">新增</a>
        </div>
        
<div style="float: right;width:90%;">
            <form action="/persons/search" method="post">
                姓名:<input type="text" name="s_name" value="" class="text"> &nbsp;&nbsp;
                年龄:<input type="text" name="s_age" value=""  class="text"> &nbsp;&nbsp;
                职业:<input type="text" name="s_professional" value="" class="text"> &nbsp;&nbsp;
                <input type="submit" value="查询">
            </form>
        </div>
    </div>
<table style="">
        <tr>
            <th width="10%">编号</th>
            <th width="15%">操作</th>
            <th width="15%">姓名</th>
            <th width="10%">年龄</th>
            <th width="50%">职业</th>
        </tr>
        <% if (datas.length) { %>
        <% datas.forEach(function(person){ %>
        <tr>
            <td><%= person.id %></td>
            <td><a href="/persons/del/<%= person.id %>">删除</a>&nbsp;&nbsp;<a href="/persons/toUpdate/<%= person.id %>">修改</a></td>
            <td><%= person.name %></td>
            <td><%= person.age %></td>
            <td><%= person.professional %></td>
        </tr>
        <% }) %>
        <% } %>
    </table>
</div>
</body>
</html>

add.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新增页面</title>
</head>
<body>
<div style="width: 800px;margin: auto;">
    <form action="/persons/add" method="post">
        姓名:<input type="text" name="name">
        年龄:<input type="text" name="age">
        职业:<input type="text" name="professional">
        <input type="submit" value="提交">
    </form>
</div>
</body>
</html>

update.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>修改页面</title>
</head>
<body>
<div style="width: 800px;margin: auto;">
    <form action="/persons/update" method="post">
        <input type="hidden" value="<%= datas[0].id %>" name="id">
        姓名:<input type="text" name="name" value="<%= datas[0].name %>">
        年龄:<input type="text" name="age" value="<%= datas[0].age %>">
        职业:<input type="text" name="professional" value="<%= datas[0].professional %>">
        <input type="submit" value="提交">
    </form>
</div>
</body>
</html>
课堂笔记
Web note ad 1