《JavaScript高级程序设计》学习笔记,第五章- 上

虽然JavaScript从技术上讲是一门面向对象的语言,但是它并不具备传统的面向对象语言所支持的类和接口等基本结构。类在JavaScript中常被称为对象定义,或者统称引用类型。

在ECMAScript中定义了很多原生的引用类型,以方便开发人员的使用。

Object类型

Object是ECMAScript中使用最多的一个类型,虽然Object的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们是非常理想的选择。

关于Object的详细讨论,可以参考这篇博客,这里就不再赘述。

Array类型

JavaScript中的数组(Array)与其它语言最大的不同在于,数组当中的每一项都可以保存不同数据类型的数据,并且数组是变长的。

创建数组有两种方法:

  1. 使用Array构造函数

     var colors = new Array(); // 创建空数组
     var colors = new Array(20); // 创建初始长度为20的数组
     var colors = new Array("red", "blue", "green"); // 创建包含3个元素的数组
    
  2. 使用数组字面量

     var colors = []; // 创建空数组
     var colors = ["red", "blue", "green"]; // 创建包含3个元素的数组
    

可以使用基于0数字索引来读取或者设置数组的值。如果读取超过数组项数的索引,则会返回undefined,如果设置的索引超过了数组的项数,则数组长度会自动增加到索引值的长度,并且中间的值自动填充为undefined

    var colors = ["red", "blue", "green"];
    alert(colors[3]); // undefined
    colors[99] = "black";
    alert(colors.length); // 100

数组的项数保存在length属性中,这个属性始终会返回0或者更大的数值。并且length属性不是只读的,也就是说,我们可以通过设置length的大小,来改变数组的长度。

检测数据

可以使用instanceof操作符来确实某个对象是不是数组。

    if(value instanceof Array) {
        // do something
    }

转换方法

之前说过,每个对象都有toLocaleString()toString()valueOf()方法。对数组调用toString()会返回数组中每一项的字符串拼接而成的一个以逗豆做分隔的字符串。valueOf()方法会返回数据本身,而toLocaleString()会依次调用数组中每一项的toLocaleString()而不是toString()来构成字符串。

使用join()方法可以自己指定生成字符串的分隔符。

    var colors = ["red", "green", "blue"];
    alert(colors.toString()); //red,green,blue
    alert(colors.join(",")); //red,green,blue
    alert(colors.join("||")); //red||green||blue

栈方法

ECMAScript数组提供了可以模拟栈操作的方法,即push()pop()

push()方法可以接收任意数量的参数,并把它们添加到数组的末尾,并返回添加后的数组长度。

pop()方法则从数组末尾移除最后一项,减少数组的length值,然后返回移除的项。

    var colors = new Array(); // 创建一个数组
    var count = colors.push("red", "green"); // 推入两项
    alert(count); //2

    count = colors.push("black"); // 推入另一项
    alert(count); //3

    var item = colors.pop(); // 取得最后一项
    alert(item); //"black"
    alert(colors.length); //2

队列方法

ECMAScript提供了shift()方法来移除数组中的第一项并返回该项。结合使用shift()push()方法,可以像使用队列一样使用数组。

    var colors = new Array(); //创建一个数组
    var count = colors.push("red", "green"); //推入两项
    alert(count); //2

    count = colors.push("black"); //推入另一项
    alert(count); //3

    var item = colors.shift(); // 取得第一项
    alert(item); //"red"
    alert(colors.length); //2

ECMAScript同时还提供了一个unshift()方法,用途与shift()正相反,它在数组前端添加任意个项并返回新数组的长度。同时使用unshift()pop()可以用来模拟反向队列。

重排序方法

reverse()sort()可以用来对数组进行重排序。

reverse()方法会反转数组项的顺序。

    var values = [1, 2, 3, 4, 5];
    values.reverse();
    alert(values); //5,4,3,2,1

sort()方法会调用数组中每一项的toString()方法,并根据返回的字符串来对整个数组进行升序的排序,显然大部分情况下这种默认行为都不是我们所需要的。所以,我们可以传入一个比较函数作为sort()方法的参数,以便确定排序的顺序。

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。

    function compare(value1, value2) {
        return value1 - value2;
    }

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

操作方法

ECMAScript为操作数组中的项提供了很多方法。

concat()可以创建一个当前数组的副本,然后将接收到的参数添加到这本副本的结尾,最后返回新构建的数组。

    var colors = ["red", "green", "blue"];
    var colors2 = colors.concat("yellow", ["black", "brown"]);
    alert(colors); //red,green,blue
    alert(colors2); //red,green,blue,yellow,black,brown

slice()方法可以基于当前数组中的一个或多个项创建一个新数组。slice()方法可以接受一个或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,该方法会返回从该参数指定的位置到数组末尾的所有项。

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

splice()主要用途是向数组的中部插入项,但是这方法有三种使用方式:

  • 删除:指定两个参数,要删除的第一项的位置,和要删除的项数。
  • 插入:提供三个参数,起始位置、0(要删除的项数为0)和要插入的项,如果要插入多个项,则可以提供第四、第五,以至任意个参数。
  • 替换:指定三个参数:起始位置、要删除的基数和要插入任意数量的项。插入的项数不必与删除的基数相等。

splice()方法始终会返回一个数组,该数组包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。

    var colors = ["red", "green", "blue"];
    var removed = colors.splice(0,1); // 删除第一项
    alert(colors); // green,blue
    alert(removed); // red,返回的数组中只包含一项

    removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项
    alert(colors); // green,yellow,orange,blue
    alert(removed); // 返回的是一个空数组

    removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
    alert(colors); // green,red,purple,orange,blue
    alert(removed); // yellow,返回的数组中只包含一项

位置方法

indexOf()lastIndexOf()用来查找数组当中的项,这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。indexOf()从数组开头向后查找,lastIndefox()从结尾向前查找。如果没有找到则返回-1。

    var numbers = [1,2,3,4,5,4,3,2,1];

    alert(numbers.indexOf(4)); //3
    alert(numbers.lastIndexOf(4)); //5

    alert(numbers.indexOf(4, 4)); //5
    alert(numbers.lastIndexOf(4, 4)); //3

    var person = { name: "Nicholas" };
    var people = [{ name: "Nicholas" }];
    var morePeople = [person];

    alert(people.indexOf(person)); //-1
    alert(morePeople.indexOf(person)); //0

迭代方法

ECMAScript 5为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象。传入这些方法中的函数会接收三个参数:数组项的值、该项的索引和数组对象本身。

  • every():如果给定函数对数组每一项都返回true,则返回true
  • filter():对数组的每一项执行给定函数,返回该函数会返回true的项组成的数组。
  • forEach():对数组中的每一项执行给定函数,这个方法没有返回值。
  • map():返回每次函数调用的结果组成的数组。
  • some():如果给定函数对数组中的任一项返回true,则返回true

every()some()都用于查询数组中的项是否满足某项条件。

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

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

filter()用于对数组中的项进行筛选。

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

map()对数组的每一项运行指定函数,返回函数执行结果的数组。

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

forEach()会对数组中的每一项执行给定的函数,并没有返回值。

    var numbers = [1,2,3,4,5,4,3,2,1];
    numbers.forEach(function(item, index, array){
        alert(item);
    });

归并方法

ECMAScript 5还新增了两个归并数组的方法:reduce()reduceRight()

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。传入的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数的返回值会作为参数自动传给下一项。

使用reduce()对数组求和:

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

reduceRight()除了遍历方向的不同之外,与reduce()方法完全一致。

Date类型

Date类型使用自UTC 1970年1月1日零时开始经过的毫秒时来保存日期。

要创建一个日期对象,使用new操作符和Date构造函数即可:

    var now = new Date(); // 获得当前日期和时间

如果调用了无参的构造函数,则新创建的对象自动获得当前日期和时间。同时,它还接受字符串或毫秒数两种不同的参数。Date.parse()Date.UTC()能返回表示日期的毫秒数。

    var someDate = new Date(Date.parse("May 25, 2004"));
    var someDate = new Date("May 25, 2004");

如果传入的字符串不能表示日期,则会返回NaN

Date.UTF()的参数为年、月(以0为基数)、月中哪一天、小时、分钟、秒和毫秒。其中,只有前两项是必传的参数。

    // GMT 时间 2000 年 1 月 1 日午夜零时
    var y2k = new Date(Date.UTC(2000, 0));
    // GMT 时间 2005 年 5 月 5 日下午 5:55:55
    var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

    // 本地时间 2000 年 1 月 1 日午夜零时
    var y2k = new Date(2000, 0);
    // 本地时间 2005 年 5 月 5 日下午 5:55:55
    var allFives = new Date(2005, 4, 5, 17, 55, 55);

继承的方法

与其它引用类型一样,Date类型也重写了toString()toLocaleString()valueOf()方法。其中,前两个方法返回的字符串根据浏览器的不同,表现形式也不一样,而后一个方法返回的是日期表示的毫秒数,可以利用这一特性直接对日期进行比较。

    var date1 = new Date(2007, 0, 1); //"January 1, 2007"
    var date2 = new Date(2007, 1, 1); //"February 1, 2007"
    alert(date1 < date2); //true
    alert(date1 > date2); //false

日期格式化方法

Date类型还有一些专门用于日期格式格式化的方法,很不幸地,这些方法也是有兼容性问题的,显示的方法因浏览器而有差异。所以,这里也不详细记录了。

日期/时间获取方法

剩下的还未介绍的方法都是Date类型中用于取得日期特定部分的方法。具体的方法列表可查阅相关的文档,这里因为空间关系,就不列出来了。

RegExp类型

ECMAScript通过RegExp来支持正则表达式。创建RegExp对象的方法有两种,一种是直接使用字面量,一种是使用构造函数。

    var expression = / pattern / flags;

其中的模式(pattern)可以是任何简单或复杂的正则表达式。每个正则表达的可带有一个或多个标志(flag),用以标明正则表达式的行为:

  • g:表示全局模式,即将模式应用于所以字符串,而非在发现第一个匹配进就停止。
  • i:表示匹配时忽略大小写。
  • m:表示多行模式,即在到达一行的末尾时还会继续查找一下行。

构造函数接收两个参数:匹配字符串的模式和可选的标志字符串。

    var pattern1 = /[bc]at/i;
    var pattern2 = new RegExp("[bc]at", "i");

RegExp实例属性

RegExp的每个实例都有下列属性,可用于取得有关模式的信息:

  • global:表示是否设置g标志
  • ignoreCase:表示是否设置了i标志
  • multiline:表示是否设置了m标志
  • lastIndex:表示开始搜索下一个匹配项的字符位置,从0算起
  • source:正则表达式的字符串表示,按照字面量形式返回

RegExp实例方法

RegExp对象的主要方法是exec(),该方法专门为捕获组设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组,或者没有匹配的时候返回null。返回的数组虽然是Array的实例,但是包含了两个额外的属性:index和input。其中,index表示匹配项在字符串中的位置,input表示应用正则表达式的字符串。

    var text = "mom and dad and baby";
    var pattern = /mom( and dad( and baby)?)?/gi;

    var matches = pattern.exec(text);
    alert(matches.index); // 0
    alert(matches.input); // "mom and dad and baby"
    alert(matches[0]); // "mom and dad and baby"
    alert(matches[1]); // " and dad and baby"
    alert(matches[2]); // " and baby"

正则表达式的第二个方法是test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回true,否则返回false

    var text = "000-00-0000";
    var pattern = /\d{3}-\d{2}-\d{4}/;
    if (pattern.test(text)){
        alert("The pattern was matched.");
    }

RegExp实例继承的toString()toLocaleString()都会返回正则表达式的字面量。

    var pattern = new RegExp("\\[bc\\]at", "gi");
    alert(pattern.toString()); // /\[bc\]at/gi
    alert(pattern.toLocaleString()); // /\[bc\]at/gi

RegExp构造函数属性

RegExp构造函数包含一些属性。这些属性适用于作用域内的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。

  • input 最近一次要匹配的字符串。Opera未实现
  • lastMatch 最近一次的匹配项。Opera未实现
  • lastParen 最近一次匹配的捕获组。Opera未实现
  • leftContext input字符串中的lastMatch之前的文本
  • multiline 是否所有表达式都使用多行模式。IE和Opera未实现
  • rightContext input字符串中lastMatch之后的文本

除了这几个属性,构造构造函数还有多达9个用于存储捕获组的属性。语法为RegExp.$1RegExp.$2等,分别用于存储第一到第九个捕获组。

    var text = "this has been a short summer";
    var pattern = /(.)hort/g;
    /*
    * 注意:Opera 不支持 input、lastMatch、lastParen 和 multiline 属性
    * Internet Explorer 不支持 multiline 属性
    */
    if (pattern.test(text)){
        alert(RegExp.input); // this has been a short summer
        alert(RegExp.leftContext); // this has been a
        alert(RegExp.rightContext); // summer
        alert(RegExp.lastMatch); // short
        alert(RegExp.lastParen); // s
        alert(RegExp.multiline); // false
    }

    pattern = /(..)or(.)/g;
    if (pattern.test(text)){
        alert(RegExp.$1); //sh
        alert(RegExp.$2); //t
    }

模式的局限性

ECMAScript中的正则还是比较完备,但是还是缺少某些语言支持的高级特性。虽然如此,ECMAScript中的正则已经能够帮我们完成绝大多数的模式匹配任务了。