首页 » 前端 » JavaScript » 正文

(三)JavaScript 引用类型

1. 介绍

引用乐行的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。它也常被称为类,当这种称呼并不妥当。

尽管 ECMAScript 从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。

提示:虽然引用类型看起来相似,但它们并不是相同的概念。为了避免混淆,本书将不使用类这个概念。

如前所述,对象是某个特定引用类型的实例。新对象是使用 new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数时出于创建新对象的目的而定义的。

var person = new Object();

这行代码创建了 Object 引用类型的一个新实例,然后把该实例保存在了变量 person 中。使用的构造函数是 Object,它只为了新对象定义了默认的属性和方法。ECMAScript 提供了很多原生引用类型(例如Object),以便开发人员用以实现常见的计算任务。

2. Object类型

到目前为止,我们看到的大多数引用类型值都是 Object 类型的实例;而且,Object 也是 ECMAScript 中使用最多的一个类型。虽然 Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。

创建 Object 实例的方式有两种:

  1. new 操作符后跟 Object 构造函数
    var person = new Object();
    // var person = {}; 与上面等同
    person.name = "Nicholas";
    person.age = 29;
    
  2. 使用对象字面量表示法,对象字面量是对象定义的一种简写形式,目的在于简化包含大量属性的对象的过程。
    var person = {
    name: "Nicholas",
    age: 29
    }
    

提示:在通过对象字面量定义对象时,实际上不会调用 Object 构造函数。

虽然可以使用前面介绍的任何一种方法来定义对象,但开发人员更青睐字面量语法,因为这种语法要求的代码量少,而且能够给人封装数据的感觉。实际上,对象字面量也是向函数传递大量可选参数的首选方式,例如:

function displayInfo(args) {
    var output = "";

    if(typeof args.name == "string"){
        output += "Name:" + args.name + "\n";
    }

    if(typeof args.age == "number"){
        output += "Age:" + args.age + "\n";
    }

    alert(output);
}

displayInfo({
    name: "Nicholas",
    age: 29
})

displayInfo({
    name: "Greg"
})

访问对象有两种:

  • 点表示法

  • 方括号语法

    alert(person["name"]); // Nicholas  方括号语法
    alert(person.name);    // Nicholas  点表示法
    

    从功能上看,这两种访问对象属性的方法没有任何区别。

    但方括号语法的主要优点是可以通过变量来访问属性,例如:

    var propertyName = "name";
    alert(person[propertyName]); // Nicholas
    

    如果属性名包含会导致错误的字符,或者属性名使用的关键字或保留字,也可以使用方括号表示法。例如:

    person["first name"] = "Nicholas";
    

    由于”first name “中包含空格,所以不能使用点表示法来访问它。但属性名中可以包含非字母非数字的,这时可以使用方括号表示法来访问它们。

注意:除非特殊情况使用方括号来访问属性,否则建议使用点表示法。

3. Array类型

除了 Object 之外,Array 类型是 ECMAScript 中最常用的类型了。而且 ECMAScript 的数组与其他语言中的数组有着相当大的区别。虽然 ECMAScript 数组与其他语言中数组都是数据有序列表

但与其他语言不同的是:

  • ECMAScript 数组每一项都可以保存任何类型的数据
  • ECMAScript 数组的大小可以动态调整,可以随着数据的添加自动增长空间存储新增数据。

创建数组两种方法:

第一种,构造函数创建:

// 使用 Array 构造函数
var colors = new Array();

// 预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而数量自动变成 length 属性的值
var colors = new Array(20);

// 也可向 Array 构造函数传递数组中包含的项
var colors = new Array("red", "blue", "green");

第二种,字面量表示法创建:

var colors = ["red", "blue", "green"]; // 创建包含三个字符串的数组
var names = []; // 创建空数组
var values = [1, 2,]; // 不要这样!这样会创建一个包含 2 或 3 项的数组,IE8之前存在 bug
var options = [,,,,,];// 不要这样!这样会创建一个包含 5 或 6 项的数组

提示:与对象一样,在使用数组字面量表示法时,也不会调用 Array 构造函数。

读取或设置、新增数组可以使用索引,例如:

var colors = ["red", "blue", "green"]; // 定义一个字符串数组
alert(colors[0]); // 显示第一项 red
colors[2] = "black";  // 设置第三项 black
colors[3] = "brown";  // 新增第四项 brown

另外数组的数据项保存在其 length 属性中,有两个特点:

  • 这个属性始终返回 0 或更大的值。
  • 这个属性可读也可设置,通过设置可以从数组末尾移除项或向数组中添加项
var colors = ["red", "blue", "green"]; 
colors.length = 2;
alert(colors[2]); // 读取第三项 undefined

colors[99] = "black";
alert(colors.length); // 100

提示:数组最多可以包含 4 294 967 295 个项,这几乎已经能够满足任何编程需求了。如果还想添加的项超过这个上限值,就会发生异常。而创建一个初始大小与这个上值接近的数组,则可能会导致运行时间超长的脚本错误。

3.1. 检测数组

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果页面包含多个框架,那实际就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。

为了解决这个问题, ECMAScript5 新增 Array.isArray()方法。这个方法的目的确定某值到底是不是数组,而不管它在哪个全局执行环境中创建的。

// instanceof 
if( value instanceof Array){
  // 对数组执行某些操作
}

// isArray()
if( Array.isArray(value)){
  // 对数组执行某些操作
}

3.2. 转换方法

如前所述,所有对象都具有 toLocalString()、toString()、valueOf()方法。

其中调用 valueOf()返回的还是数组本身,而调用数组的 toString() 方法会返回由数组中每个值的字符串形式凭借而成的一个以逗号分隔的字符串。

var person1 = {
    toLocaleString: function (){
        return "Nikolaos";
    },
    toString: function (){
        return "Nicholas";
    }
}

var person2 = {
    toLocaleString: function (){
        return "Grigorios";
    },
    toString: function (){
        return "Greg";
    }
}

var people = [preson1, person2];
alert(people);                   // Nicholas,Greg
alert(people.toString());        // Nicholas,Greg
alert(people.toLocaleString());  // Nikolaos,Grigorios

如果使用 join() 方法,则可以使用不同的分隔符来构建这个字符串。join()方法只接受一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。

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

提示:如果数组中的某一项的值是 null 或者 Undefined,那么该值在 join()、toLocaleString()、toString()、valueOf()方法返回的结果中以字符串表示。

3.3. 栈方法(手枪)

ECMAScript 数组也提供了一种让数组的行为类似于其他数据结构的方法。数组可以表现的像栈一样,后者是一种可以限制插入和删除项的数据结构。栈是一种 LIFO (Last-In-First-Out,后进先出)的数据结构。

  • 推入 push,可接受任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组长度。
  • 移除 pop(弹出),从数组末尾移除最后一项
var colors = ["red", "blue"];
colors.push("brown");// 添加另一项
colors[3] = "black"; // 添加一项
alert(colors.length); // 4

var item = colors.pop(); // 删除最后一项,并返回删除项
alert(item); // black

3.4. 队列方法(机枪)

队列数据结构的访问规则是 FIFO(First-In-First-Out,先进先出),队列在列表的末端添加项,从列表的前端移除项。

  • 推入 push,可接受任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组长度。
  • 移除 shift,从数组前端移除第一项
  • 推入 unshift,可接受任意数量的参数,插入到数组前端任意个项并返回新数组的长度。
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

count = colors.unshift("yellow", "orange"); // 推入数组前端两项,并返回新数组长度
alert(count); // 4

3.5. 重排序方法

数组中存在两个可以直接用来重新排序的方法:

  • reverse() 反转数组项的顺序
  • sort() 默认情况按升序排列数据项–即小值位于最前面,大值排在后面,sort方法会调用每个数组的 toString() 转型方法,然后比较得到的字符串。
// reverse
var values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1

// sort
var values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5
// 虽然可以比对,但不稳定,所以这种排序方式在很多情况下都是不是最佳方案。
// 因此 sort() 方法可以接受一个比较函数作为参数,以便我们指定哪个值位于那个值前面。

比较函数可以接受两个参数:

  • 参数一 位于 参数二 之前则返回一个负数

  • 两个参数相等返回0

  • 参数一 位于 参数二 之后则返回一个正数

    // 升序
    function compare(value1, value2){
        if(value1 < value2) {
        return -1;
      } else if (value1 > value2){
        return 1;
      } else{
        return 0;
      }
    }
    
    // 这个比较函数适用于大多数数据类型,只要将其作为参数传递给 sort() 方法即可
    var values = [0, 1, 5, 10, 15];
    values.sort(compare);
    alert(values); // 0,1,5,10,15
    
    // 降序
    function compare(value1, value2){
        if(value1 < value2) {
        return 1;
      } else if (value1 > value2){
        return -1;
      } else{
        return 0;
      }
    }
    
    var values = [0, 1, 5, 10, 15];
    values.sort(compare);
    alert(values); // 15,10,5,1,0
    

    提示:reverse() 和 sort() 方法的返回值是经过排序之后的数组。

3.6. 操作方法

ECMAScript 为操作已经包含在数组中的项提供了很多方法。

  • concat() 方法可以基于当前数组中的所有项创建一个新数组,让后将参数接受到的数组添加其末尾,如果传递的值不是数组,这些值会被简单地添加到结果数组的末尾。

  • slice() 基于当前数组中一或多个创建一个数组,可以接受一或两个参数,即要返回项的起始和接受位置。

    • 删除:可以删除任意数量的项,只需要指定 2 个参数:删除的第一项的位置和要删除的项数。例如:splice(0,2) 会删除数组中的前两项。

    • 插入:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、0(要删除的项数)和插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。

    例如:splice(2,0,”red”,”green”) 会从当前数组的位置 2 开始插入字符串 “red” 和 “green”

    替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。

    例如:splice(2,1,”red”,”green”) 会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串 “red” 和 “green”

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

removed = colors.splice(1, 0, "yeelow", "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, 返回的数组中只包含一项

colors.splice(-1,0,'yellow','yellow'); // 
alert(colors); // green,red,purple,orange,yellow,yellow,blue

提示:如果 slice() 方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置。例如,在一个包含 5 项的数组上调用 slice(-2, -1) 与调用 slice(3, 4) 得到的结果相同。如果结束位置小于起始位置,则返回空数组。

3.7. 位置方法

ECMAScript5 为数组实例添加两个方法:

  • indexOf() 从数组的开头(位置0)开始向后查找
  • lastIndexOf() 从数组的末尾向前开始查找

这两个方法都接受两个参数,要查找的项(可以选的)表示查找起点位置的。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-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

使用 idnexOf() 和 lastIndexOf() 方法查找特定在数组中的位置非常简单。

3.8. 迭代方法

ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接受两个参数:要在每一项上运行的函数项(可选的)运行函数的作用域对象–影响 this 的值。

传入这些方法中的函数会接受三个参数:数组的值、该项的数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能会也可能不会影响方法的返回值。

以下这 5 个迭代方法:

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

以上方法都不会修改数组中的包含的值。

这些方法中,最相似的是 every() 和 some(),它们都用于查询数组中的项是否满足某个条件。

  • 对 every() 传入的函数必须对每一项都返回 true,否则终止迭代,返回false

  • 而 some() 传入的函数必须对每一项都返回 false,否则终止迭代,返回true

    var numbers = [1,2,3,4,5,4,3,2,1];
    
    var everyResult = numbers.every(function(item, index, array){
    console.log(item)
    return (item > 2);
    })
    
    alert(everyResult); // false
    
    var someResult = numbers.some(function(item, index, array){
    return (item > 2);
    })
    
    alert(someResult); // true
    
  • filter() 利用指定的函数确定是否在返回的数组中包含某一项
    // 例如范湖一个数值都大于 2 的数组
    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() 也返回数组,而这个数组的每一项都是在原始数组中的对应项运行传入函数的结果。
    // 例如,可以给数组中的每一项乘以 2,然后返回这些乘积组成的数组
    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]
    // 以上代码返回的数组中包含给每个乘以 2 之后的结果。这个方法适合创建包含的项与另一个数组一一对应的数组。
    
  • forEach() 它只是对数组的每一项运行传入的函数,这个函数没有返回值,本质和 for 循环迭代数组一样。
    var numbers = [1,2,3,4,5,4,3,2,1];
    
    numbers.forEach(function(item, index, array){
    // 执行某些操作
    });
    

这些数组方法通过执行不同的操作,可以大大方便处理数组的任务。

3.9. 归并方法

ECMAScript5 还新增了两个归并数组的方法: reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构造一个最终的返回值。

  • reduce() 方法从数组的第一项开始,逐个遍历到最后。
  • reduceRight() 则从数组的最后一项开始,向前遍历到第一项。

这两个方法都接受两个参数:一个在每一项调用的函数和(可选的)作为归并基础的初始值。

传给 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() 的作用类似,只不过方向相反而已
// 第一次执行回调函数 prev 是 1,cur 是 2
// 第二次执行回调函数 prev 是 3 (1 加 2 的结果)

4. Date 类型

ECMAScript 中的 Date 类型是早期 Java 中的 java.util.Date 类基础上构建的。

为此,Date类使用自 UTC(Coordinated Universal Time,国际协调时间)1970年1月1日午夜(零时)开始经过的毫秒数来保存日期。

在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 100 000 000 天

4.1. 继承的方法

与其他引用类型一样,Date 类型也重写了 toLocaleString()、toString()、valueOf()方法。

  • toLocaleString() 按照浏览器相适应的格式返回日期和时间。时间格式中会包含 AM 或 PM,但不会包含时区地区信息。
  • toString() 方法则通常返回带有时区信息的日期和时间,其中时间一般以军用时间(即小时的范围是 0 到 23)表示
  • valueOf() 根本不返回字符串,而是返回日期的毫秒表示。

4.2. 日期格式化方法

Date 类型还有一些专门用于将日期格式化为字符串的方法,这些方法如下:

  • toDateString() 以特定于实现的格式显示星期几、月、日和年;
  • toTimeString() 以特定于实现的格式显示时、分、秒和时区;
  • toLocaleDateString() 以特定于地区的格式显示星期几、月、日和年;
  • toLocaleTimeString() 以特定于实现的格式显示时、分、秒;
  • toUTCString() 以特定于实现的格式完整的 UTC 日期;
  • toLocaleString() 和 toString() 方法一样。

以上的这些字符串格式方法的输出也是因浏览器而异,因此没有哪一个方法能够用来在用户界面中显示一致的日期信息。

提示:除了前面介绍的方法之外,还有一个名叫 toGMTString() 的方法,这是一个与 toUTCString() 等价的方法,其存在目的在于确保向后兼容。不过,ECMAScript 推荐现在编写的代码一律使用 toUTCString() 方法。

4.3. 日期/时间组件方法

到目前为止,剩下还未介绍的 Date 类型的方法(如下表所示),都是直接取得和设置日期值中特定部分的方法了。需要注意的是,UTC 日期指的是在没有时区偏差的情况下(将日期转换为 GMT 时间)的日期值。

方法 说明
getTime() 返回表示日期的毫秒数;与 valueOf() 方法返回的值相同
setTime(毫秒) 以毫秒数设置日期,会改变整个日期
getFullYear() 取得 4 位数的年份(如2007而非仅07)
getUTCFullYear() 返回UTC日期的4位数年份
setFullYear(年) 设置日期的年份。传入的年份值必须是4位数组(如2007而非仅07)
setUTCFullYear(年) 设置UTC日期中年份。传入的年份值必须是4位数字(如2007而非仅07)
getMonth() 返回日期中的月份,其中0表示一月,11表示十二月
getUTCMonth() 返回UTC日期中的月份,其中0表示一月,11表示十二月
setMonth(月) 设置日期的月份。传入的月份值必须大于0,超过11则增加年份
setUTCMonth() 设置UTC日期的月份。传入的月份值必须大于0,超过11则增加年份
getDate() 返回日期月份中的天数(1到31)
getUTCDate() 返回UTC日期月份中的天数(1到31)
setDate(日) 设置日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
setUTCDate(日) 设置UTC日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
getDay() 返回日期中星期的星期几(其中0表示星期日,6表示星期六)
getUTCDay() 返回UTC日期中星期的星期几(其中0表示星期日,6表示星期六)
getHours() 返回日期中的小时数(0到23)
getUTCHours() 返回UTC日期中的小时数(0到23)
setHours(时) 设置日期中的小时数。传入的值超过了23则增加月份中的天数
setUTCHours(时) 设置UTC日期中的小时数。传入的值超过了23则增加月份中的天数
getMinutes(分) 返回日期中的分钟数(0到59)
getUTCMinutes(分) 返回UTC日期中的分钟数(0到59)
setMinutes(分) 设置日期中的分钟数。传入的值超过59则增加小时数
setUTCMinutes(分) 设置UTC日期中的分钟数。传入的值超过59则增加小时数
getSeconds() 返回日期中的秒数(0到59)
getUTCSeconds() 返回UTC日期中的秒数(0到59)
setSeconds(秒) 设置日期中的秒数。传入的值超过了59会增加分钟数
setUTCSeconds(秒) 设置UTC日期中的秒数。传入的值超过了59会增加分钟数
getMilliseconds() 返回日期中的毫秒数
getUTCMilliseconds() 返回UTC日期中的毫秒数
setMilliseconds(毫秒) 设置日期中的毫秒数
setUTCMilliseconds(毫秒) 设置UTC日期中的毫秒数
getTimezoneOffset() 返回本地时间与UTC时间相差的分钟数。例如,美国东部标准事件返回300。在某地进入夏令时的情况下,这个值会有所变化。

5. RegExp类型

ECMAScript 通过 RegExp 类型来支持正则表达式。使用下面类似 Perl 的语法,就可以创建一个正则表达式。

var expression = / pattern / flags;

其中的模式(pattern)部分可以是

  • 任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找、反向引用

每个正则表达式都可带有一或多个标志(flags),用以表明正则表达式的行为。

正则表达式的匹配模式支持下列 3 个标志。

  • g: 表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在于模式匹配的项。

因此,一个正则表达式就是一个模式与上述3个标志的组合体。不同组合产生不同的结构。

如下列所示:

// 匹配字符串中所有 at 的实例
var pattern1 = /at/g;

// 匹配所有以 at 结尾的 3 个字符的组合,不区分大小写
var pattern3 = /.at/gi;
``
// 匹配第一个 bat 或 cat,不区分大小写
var pattern2 = /[bc]at/i;

// 与 pattern1 相同,只不过是使用构造函数创建的
var pattern2 = new RegExp("[bc]at", "i");

与其他语言中的正则表达式类似,模式中使用的所有元字符必须转义。

// 正则表达式中的元字符包括:
([{\^$|)?*+.]}

使用正则表达式字面量和使用 RegExp 构造函数和创建的正则表达式不一样。

5.1. RegExp实例属性

RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

  • global: 布尔值,表示是否设置了 g 标志。
  • ignoreCase: 布尔值,表示是否设置了 i 标志。
  • lastIndex: 整数,表示开始搜索下一个匹配项的字符位置,从0算起
  • multiline: 布尔值,表示是否设置了 m 标志。
  • source: 正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

通过这些属性可以获知一个正则表达式的各方面信息,但却没有多大用处,因为这些信息全部都包含在模式声明中。

var pattern1 = /\[bc\]at/i; // 等同 new RegExp("\\[bc\\]at","i")
alert(pattern1.global); // false
alert(pattern1.ignoreCase); // true
alert(pattern1.multiline); // false
alert(pattern1.lastIndex); // 0
alert(pattern1.source); // "\[bc\]at"

5.2. regexp实例方法

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

var test = "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

5.3. regexp构造函数属性

RegExp 构造函数包含一些属性(这些属性在其他语言中被看成是静态属性)

长属性名 短属性名 说明
input $_ 最近一次要匹配的字符串
lastMatch $& 最近一次的匹配项
lastParen $+ 最近一次匹配的捕获组
leftContext $` input字符串中 lastMatch之前的文本
multiline $* 布尔值,表示是否所有表达式都使用多行模式
rightContext $’ input 字符串中lastMatch之后的文本
var text = "this has been a short summer";
var pattern = /(.)hort/g;

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
}

5.4. 模式的局限性

尽管 ECMAScript 中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支持的高级正则表达式特性。下面列出了 ECMAScript 正则表达式不支持的特性

  • 匹配字符串开始和结尾\A和\Z锚
  • 向后查找(lookehind)
  • 并集和交集类
  • 原子组(atomic grouping)
  • Unicode 支持(单个字符除外,如\uFFFF)
  • 命名的捕获组
  • s(Single,单行) 和 X (free-spacing,无间隔)匹配模式
  • 条件匹配
  • 正则表达式注释

即使存在这些限制,ECMAScript 正则表达式仍然是非常强大的,能够帮我们完成绝大多数模式匹配任务。

6. Function类型

ECMAScript 中最有意思的就是函数,而有意思的根源,则在于函数实际上是对象,每个函数都是 Function 类型的实例,而且都与引用类型一样具有属性和方法。

由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

// 1.函数声明
function sum (num1, num2){
    return num1 + num2;
}

// 2.函数表达式
var sum = function(num1, num2){
  return num1 + num2;
}

// 3.Function 构造函数,这是函数表达式,不推荐使用这种定义函数,因为这种语法会导致解析两次代码
var sum = new Function("num1", "num2", "return num1+num2"); // 不推荐

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。

换句话说,一个函数可能会有多个名字,如下面所示:

function sum(num1, num2){
    return num1+num2;
}
alert(sum(10,10)); // 20

var anotherSum = sum;  // anotherSum 和 sum 都指向了同一个函数
alert(anotherSum(10,10)); // 20

sum = null; // 即使 sum 设置为 null,让它与函数"断绝关系",但仍然可以正常调用 anotherSum()
alert(anotherSum(10,10)); // 20

6.1. 没有重载(深入理解)

将函数名想象为指针,也有助于理解为什么 ECMAScript 中没有函数重载的概念。

function addSomeNumber(num) {
    return num + 100;
}

function addSomeNumber(num) {
  return num + 200;
}

var result = addSomeNumber(100); // 300

显然,这个例子中声明了两个同名的函数,而结果则是后面的函数覆盖了前面的函数。

6.2. 函数声明与函数表达式

函数声明和函数表达式区别,解析器在向执行环境中加载数据时,对函数声明和表达式并非一视同仁。

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问)
// 函数声明
alert(sum(10,10));
function sum(num1, num2){
    return num1 + num2;
}
  • 至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行
alert(sum(10,10));
var sum = function(num1, num2){
    return num1 + num2;
}

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。

除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。也可以同时使用函数声明和函数表达式,例如 var sum = function sum(){} ,不过这种语法在 safari 中会导致错误。

6.3. 作为值的函数

因为 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。

function callSomeFunction(someFunction, someArgument){
  return someFunction(someArgument);
}

function add10(num){
  return num + 10;
}

var result = callSomeFunction(add10, 10);
alert(result1); // 20

function getGreeting(name){
  return "Hello, " + name;
}

var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); // "Hello, Nicholas"

这里的 callSomeFunction() 函数是通用的,即无论第一个参数中传递出来的是什么函数,它都会返回执行第一个参数后的结果。

从一个函数中返回另一个函数,而且这也是极为有用的一种技术。

function createComparisonFunction(propertyName){
    return function(object1, object2){
    var value1 = object1[propertyName];
    var value2 = object2[propertyName];

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

var data = [{name: "Zachary", age: 28}, {name:"Nicholas", age: 29}];
data.sort(createComparisonFunction("name"));
alert(data[0].name); // Nicholas

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

每个对象都包含一个 name 属性和一个 age 属性,sort() 方法会调用每个对象的 toString() 方法以确定他们的次序;

6.4. 函数内部属性

函数内部,有两个特殊的对象:arguments 和 this

  • arguments 是一个类数组对象,包含着传入函数中的所有参数。但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。

    看下面非常经典的乘阶函数

    function factorial(num){
    if(num <= 1){
      return 1;
    }else{
      return num*factorial(num-1);
    }
    }
    

    定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了小数这种紧密耦合的现象,可以像下面这样使用 arguments.callee

    function factorial(num){
    if(num <= 1){
      return 1;
    }else{
      return num * arguments.callee(num-1);
    }
    }
    
    // 这个重写后的 factorial() 函数的函数体内,没有再引用的函数名 factorial,这样无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。
    // 例如:
    var trueFactorial = factorial;
    factorial = function (){
    return 0;
    };
    
    alert(trueFactorial(5)); //120
    alert(factorial(5)); //0
    
  • 函数内部的另一个特殊对象 this,其行为与 Java 和 C#中的 this 大致类似。
    • this 引用的是函数执行的环境对象—或者也可以说是 this 值(当在页面的全局作用域中调用函数时,this 对象引用的就是 window)

提示:请一定牢记,函数的名字仅仅是一个包含指针的变量而已。因此,即使在不同的环境中执行,全局的 factorial() 函数 和 trueFactorial 指向的仍然是同一个函数,后面又重新赋值函数就不同了。

6.5. 函数属性和方法

ECMAScript 中函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:lengthprototype

其中 length 属性表示希望接受的命名参数的个数,如下面:

function sayName(name){
    alert(name);
}

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

function sayHi(){
    alert("hi");
}

alert(sayName.length); // 1
alert(sum.length);     // 2
alert(sayHi.length);   // 0

每个函数都包含两个非继承而来的方法:

  • apply() 方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组、Array的实例、arguments 对象
    function sum(num1, num2){
    return num1 + num2;
    }
    
    function callSum1(num1, num2){
    return sum.apply(this, arguments);
    }
    
    function callSum2(num1, num2){
    return sum.apply(this, [num1, num2]);
    }
    
    alert(callSum1(10,10)); // 20
    alert(callSum2(10,10)); // 20
    
  • call() 与 apply()方法作用相同,它们的区别仅在于接受参数的方式不同。第一个参数是 this 值没有变化,变化的是其余参数都是直接传递给函数。
    function sum(num1, num2){
    return num1 + num2;
    }
    
    function callSum(num1, num2){
    return sum.call(this, num1, num2);
    }
    
    alert(callSum(10,10)); //20
    

    这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。

    事实上,传递参数并非 apply() 和 call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

    window.color = "red";
    var o = {color:"blue"};
    
    function sayColor(){
    alert(this.color);
    }
    
    sayColor(); // red
    
    sayColor.call(this);   // red
    sayColor.call(window); // red
    sayColor.call(o);      // blue,此时函数体内的 this 对象指向了 o,于是结果显示 blue
    

​ 使用 call() 和 apply() 来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

  • 还有一个 bind() ,这个方法会创建一个函数的实例,其 this 值会绑定到传给 bind() 函数的值。
    window.color = "red";
    var o = { color: 'blue' };
    
    function sayColor(){
    alert(this.color);
    }
    
    var objectSayColor = sayColor.bind(o);
    objectSayColor(); // blue
    

每个函数继承的 toLocaleString() 和 toString() 方法始终都返回函数的代码。返回代码的格式则因浏览器而异。valueOf() 方法同样也只返回函数代码。

7. 基本包装类型

ECMAScript 提供了 3 个特殊的引用类型:

  • Boolean
  • Number
  • String

这些类型与本章介绍的其他引用类型相似,但同时也具有各自的基本类型相关的特殊行为。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。

// 1.创建 String 类型的一个实例
var s1 = "some text";
// 2.在实例上调用指定的方法
var s2 = s1.substring(2);
// 3.销毁这个实例
s1 = null;

经过此番处理,基本的字符串值就变得跟对象一样了。上面这三个步骤也分别适用于 BooleanNumber 类型对应的布尔值和数字值。

引用类型基本包装类型的主要区别就是对象的生存期

  • 使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中
  • 而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性方法

注意:使用 new 调用基本包装类型的构造函数,与直接调用同名的转型函数不一样。

var value = "25";
var number = Number(value); // 转型函数
alert(typeof number);  // "number"

var obj = new Number(value); // 构造函数
alert(typeof obj); // "object"

尽管我们不建议显式地创建基本包装类型的对象,但它们操作基本类型值的能力还是相当重要的。而每个基本包装类型都提供了操作相应值的便捷方法。

7.1. Boolean类型

Boolean 类型是与布尔值对应的引用类型。要创建 Boolean 对象,可以像下面这样调用 Boolean 构造函数并传入 truefalse 值。

var booleanObject = new Boolean(true);

Boolean 类型的实例重写了 valueOf() 方法,返回基本类型值 true 或 false;重写了 toString() 方法,返回字符串 “true” 和 “false”。可是,Boolean 对象在 ECMAScript 中的用处不大,因为它经常造成人们的误解。其中最常见的问题就是在布尔表达式中使用 Boolean 对象。

var falseObject = new Boolean(false);
var result = falseObject && true;
alert(result); // true

var falseValue = false;
result = falseValue && true;  
alert(result); // false

基本类型引用类型的布尔值还有两个区别。

  • Tyepof 操作符对基本类型返回 boolean,而对引用类型返回 object
  • Boolean 对象是 Boolean 类型的实例,所以使用 instanceof 操作符测试 Boolean 对象返回 true
  • 测试基本类型的布尔值返回 false
alert(typeof falseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false

理解基本类型的布尔值与 Boolean 对象之间的区别非常重要–当然,我们的建议是永远不要使用 Boolean 对象。

7.2. Number类型

Number 是与数字值对应的引用类型。要创建 Number 对象,可以在调用 Number 构造函数时向其床底相应的数值。

var numberObject = new Number(10);
  1. 与 Boolean 类型一样,Number 类型也重写了 valueOf() 、 toLocaleString() 和 toString() 方法
  2. 重写后的 valueOf() 方法返回对象表示的基本类型的数值,另外两个方法则返回字符串形式的数值。
  3. toString() 方法传递一个表示基数的参数,告诉它返回几进制数值的字符串形式。
var num = 10;
alert(num.toString());   //10
alert(num.toString(2));  //1010
alert(num.toString(8));  //12
alert(num.toString(10)); //10
alert(num.toString(16)); //a

除了继承方式外,Number 类型还提供了一些用于将数值格式化字符串的方法。

  • toFixed() 方法会按照指定的小数位返回数值的字符串表示
    var num = 10;
    alert(num.toFixed(2)); //10.00
    // 这里传入数值2,意思是显示几位小数。
    // 如果数值本身包含的小数位比指定的还多,那么接近指定的最大小数位的值就会舍入
    var num = 10.005;
    alert(num.toFixed(2)); //10.01
    

    能够自动舍入的特性,使得 toFixed() 方法很适合处理货币值。但注意的是,不同浏览器给这个方法设定的舍入规则可能会有所不同。

提示:toFixed() 方法可以表示带有 0 到 20 个小数位的数值。但这只是标准实现的范围,有些浏览器也可能支持更多位数。

  • toExponential() 格式化数值方法,该方法返回指数表示法(也称 e 表示法),表示的数值字符串形式。与 toFixed() 一样,toExponential() 也接受一个参数,而且该参数同样也是指定输出结果中的小数位。

  • var num = 10;
    alert(num.toExponential(1)); // 1.0e+1
    

    以上代码输出了 1.0e+1 不过,这么小的数值一般不必使用 e 表示法。

  • toPrecision() 方法可能会返回固定大小 (fixed) 格式,也可能返回指数(exponential) 格式;具体规则是看哪种格式最合适。这个方法接受一个参数,即表示数值的所有数字位数(不包括指数部分)

  • var num = 99;
    alert(num.toPrecision(1)); // 1e+2  一位数来表示99
    alert(num.toPrecision(2)); // 99    二位数来表示99
    alert(num.toPrecision(3)); // 99.0  三位数来表示99
    

    实际上 toPrecision() 会根据要处理的数值决定到底是调用 toFixed() 还是 toExponential(),这三个方法都可以通过向上或向下舍入,做到以最准确的形式来表示带有正确小数位的值。

提示:toPrecision() 方法可以表现 1 到 21 位小数。某些浏览器支持的范围更大,但这是典型实现的范围。

与 Boolean 对象类似,Number 对象也以后台方式为数值提供了重要的功能。但与此同时,我们仍然不建议直接实例化 Number 类型,而原因与显式创建 Boolean 对象一样。

具体来讲,就是在使用 typeof 和 instanceof 操作符测试基本类型引用类型数值时,得到的结果完全不同。

var numberObject = new Number(10);
var numberValue = 10;
alert(typeof numberObject); // object 引用类型
alert(typeof numberValue);  // number 基本类型
alert(numberObject instanceof Number); // true  引用类型
alert(numberValue instanceof Number);  // false 基本类型

在使用 typeof 操作符测试基本类型数值时,始终会返回 number,而在测试 Number 对象时,则会返回 object 。

类似地,Number 对象是 Number 类型的实例,而基本类型的数值则不是。

7.3. String类型

String 类型是字符串的对象包装类型,可以像下面这样使用 String 构造函数来创建。

var stringValue = "hello world";

String 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的 valueOf()、toLocaleString() 和 toString() 方法,都返回对象所表示的基本字符串值。

String 类型的每个实例都有一个 length 属性,表示字符串中包含多个字符。

var stringValue = "hello world";
alert(stringValue.length); // 11

这个例子输出了字符串”hello world”中的字符串数量,即11 应该注意的是,即使字符串中包含双字节(不是占一个字节的 ASCII 字符),每个字符也仍然算一个字符。

String 类型提供了很多方法,用于辅助完成对 ECMAScript 中字符串的解析和操作。

7.3.1. 字符串方法

  • charAt() 根据索引以单个字符返回给定位置的那个字符(ECMAScript 中没有字符类型,只有字符串)

  • charCodeAt() 根据索引以单个字符返回给定位置的那个字符编码

  • var stringValue = "hello world";
    alert(stringValue.charAt(1));     // 输出:e
    alert(stringValue.charCodeAt(1)); // 输出:101 也就是 e 的字符编码
    
    // ECMAScript 5 还定义了另一个访问个别字母的方法,使用方括号表示法访问个别字符串,IE7以上才支持
    alert(stringValue[1]); // 输出: e
    

    这两个方法都接受一个参数,即基于 0 的字符位置。

7.3.2. 字符串操作方法

  • concat() 将一个或多个字符串拼接起来,返回拼接得到的新字符串。

​ 以下方法,都会创建新字符串,不会修改原字符串,而且都接受一或两个参数。

​ 参数一:指定子字符串的开始位置,参数二:(在指定的情况下)表示子字符串到哪里结束。如果没指定,默认到末尾作为结束位置。

  • slice()

  • substr()

  • substring() 参数一:起始位置 ,参数二:返回字符串个数

    var stringValue = "hello world";
    
    alert(stringValue.slice(3));        //lo world
    alert(stringValue.substring(3));    //lo world
    alert(stringValue.substr(3));       //lo world
    
    alert(stringValue.slice(3, 7));     //lo w
    alert(stringValue.substring(3, 7)); //lo w
    alert(stringValue.substr(3, 7));    //lo worl 
    

    在传递的参数是负值的情况下,它们的行为就不尽相同了。

    var stringValue = "hello world";
    alert(stringValue.slice(-3));     //rld, 将传入的负值与字符串的长度相加
    alert(stringValue.substring(-3)); //hello world, 会将所有负值参数都转为0
    alert(stringValue.substr(-3));    //rld, 将传入的负值与字符串的长度相加,将负数的第二个参数转为 0
    
    alert(stringValue.slice(3, -4));     //lo w
    alert(stringValue.substring(3, -4)); //hel·
    alert(stringValue.substr(3, -4));    //''空字符串
    

    提示:IE 的 JavaScript 实现在处理向 substr() 方法传递负值的情况时存在问题,它会返回原始的字符串。IE9 修复了问题。

7.3.3. 字符串位置方法

​ 这两个方法都是从一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没找到该字符串,则返回-1)

  • indexOf() 从字符串的开头向后搜索子字符串

  • lastIndexOf() 从字符串的末尾向前搜索子字符串

    var stringValue = "hello world";
    alert(stringValue.indexOf("o"));     //4
    alert(stringValue.lastIndexOf("o")); //7
    

    这两个方法都可以接受可选的第二个参数,表示从字符串中的哪个位置开始搜索。

    indexOf() 会从该参数指定的位置向后搜索,忽略该位置之前的所有字符;

    lastIndexOf() 则会从指定的位置向前搜索,忽略该位置之后的所有字符;

    var stringValue = "hello world";
    alert(stringValue.indexOf("o", 6));     //7
    alert(stringValue.lastIndexOf("o", 6)); //4
    

    在使用第二个参数的情况下,可以通过循环调用 indexOf() 或 lastIndexOf(0) 来找到所有匹配的子字符串

    var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
    var positions = new Array();
    var pos = stringValue.indexOf("e");
    
    while(pos > -1){
    positions.push(pos);
    pos = stringValue.indexOf("e", pos + 1);
    }
    
    alert(positions); //3,24,32,35,52
    

7.3.4. trim()方法

​ ECMAScript5 为所有字符串定义了 trim() 方法。这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。

  • var stringValue = "   hello world   ";
    var trimmedStringValue = stringValue.trim(); 
    alert(stringValue);        //"   hello world   "
    alert(trimmedStringValue); //hello world
    

    由于 trim() 返回的是字符串的副本,所以原始字符串中的前置及后缀空格会保持不变。

7.3.5. 字符串大小写转换方法

  • toLowerCase()

  • toLocaleLowerCase() 针对特定地区的少数语言实现。

  • toUpperCase()

  • toLocaleUpperCase() 针对特定地区的少数语言实现。

  • var stringValue = "hello world";
    alert(stringValue.toLocaleUpperCase()); //HELLO WORLD
    alert(stringValue.toUpperCase());       //HELLO WORLD
    alert(stringValue.toLocaleLowerCase()); //hello world
    alert(stringValue.toLowerCase());       //hello world
    

    一般来说,在不知道自己的代码将在那种语言环境中运行的情况下,还是使用针对地区的方法更稳妥一些。

7.3.6. 字符串的模式匹配方法

  • match() 本质上与 RegExp 的 exec() 相同,match() 只接受一个参数,要么一个正则表达式,要么 RegExp 对象。
    var text = "cat, bat, sat, fat";
    var pattern = /.at/;
    
    // 与 pattern.exec(text) 相同
    var matches = text.match(pattern);
    alert(matches.index);      //0
    alert(matches[0]);         //cat
    alert(pattern.lastIndex);  //0
    

    本例中的 match() 方法返回了一个数组;

    如果调用 RegExp 对象的 exec() 方法并传递本例中的字符串作为参数,那么也会得到与此相同的数组:数组的一个项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串。

  • search() 参数与match() 一样

    • 由字符串或 RegExp 对象指定的一个正则表达式。
    • search() 方法返回字符串中第一个匹配项的索引
    • 如果没有找到匹配项,则返回 -1。始终从字符串开头向后查找
    var text = "cat, bat, sat, fat";
    var pos = text.search(/at/);
    alert(pos); //1
    
  • replace() 替换方法
    • 参数一:可以是一个 RegExp 对象或一个字符串(这个字符串不会被转换成正则表达式)

    • 参数二:可以是一个字符串或者一个函数

    • 如果第一个字符串,那么只会替换第一个子字符串,要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局(g)标志

      var text = "cat, bat, sat, fat";
      var result = text.replace("at", "ond");
      alert(result); // cond, bat, sat, fat
      
      result = text.replace(/at/g, "ond");
      alert(result); // cond, bond, sond, fond
      
    • 如果是一个函数,在只有一个匹配项(即与模式匹配的字符串)的情况下,会向这个函数传递 3 个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串。
      function htmlEscape(text){
          return text.replace(/[<>"&]/g, function(match, pos, originlText) {
      switch(match) {
            case "<":
              return "<";
            case ">":
              return ">";
            case "&":
              return "&";
            case "\"":
              return """;
          }
        })
      }
      
      console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
      // <p class="greeting">Hello world!</p>
      

      这里,我们为插入 HTML 代码定义了函数 htmlEscape(),这个函数能够转义 4 个字符:小于号、大于号、和号以及双引号。实现这种转义的最简单方法,就是使用正则表达式查找这个几个字符,然后定义一个能够针对每个匹配的字符返回特定 HTML 实体的函数。

  • split() 方法可以基于指定的分隔符将一个字符串分割成多个字符串,并将结果放在一个数组中。

    • 分隔符可以是字符串,也可以是一个 RegExp 对象(这个方法不会将字符串看成正则表达式)
    • 可接受第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。
    var colorText = "red,blue,green,yellow";
    var color1 = colorText.split(",");      // ["red", "blue", "green", "yellow"]
    var color2 = colorText.split(",", 2);   // ["red", "blue"]
    var color3 = colorText.split(/[^\,]+/); // ["", ",", ",", ",", ""]
    

    ​ 在这个例子中,colorText 是逗号分隔的颜色名字符串。基于该字符串调用 split(“,”) 会得到一个包含其中颜色名的数组,用于分割字符串的分隔符是逗号。

    ​ 为了将数组截短,让它只包含两项,可以为 split() 方法传递第二个参数2。最后,通过使用正则表达式,可以使用取得包含逗号字符串的数组。需要注意的是,在最后一次调用 split() 返回的数组中,第一项和最后一项是两个孔空字符串。之所以会这样,是因为通过正则表达式指定的分隔符出现在了字符串的开头(即子字符串”red”) 和末尾 (即子字符串”yellow”)

    ​ 对 split() 中正则表达式的支持因浏览器而异。尽管对于简单的模式没有什么差别,但对于未发现匹配项以及带有捕获组的模式,匹配的行为就大不相同了。

7.3.7. localeCompare() 方法

与操作字符串有关的最后一个方法 localeCompare(),这个方法比较两个字符串,并返回下列值中的一个:

  • 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定)

  • 如果字符串等于字符串参数,则返回 0

  • 如果字符串在字母表中应该排在字符串参数之前,则返回一个正数(大多数情况下是1,具体的值同样要视实现而定)

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

7.3.8. fromCharCode() 方法

String 构造函数本身还有一个静态方法:fromCharCode()。这个方法的任务是接收一或多个字符编码,然后将它们转换成一个字符串。从本质上来看,这个方法与实例方法 charCodeAt() 执行的是相反的操作。

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

7.3.9. HTML 方法

早期 Web 浏览器提供商察觉到了使用 JavaScript 动态格式化 HTML 的需求。于是,这些提供商就扩展了标准,实现了一些专门用于简化常见 HTML 格式化任务的方法。下表列出了这些 HTML 方法。不过,需要注意的是,应该尽量不使用这些方法,因为它们创建的标记通常无法表达语义。

方法 输出结果
anchor(name) string
big() string
bold() string
fixed() string
fontcolor(color) string
fontsize(size) string
italics() string
link(url) string
small() string
strike() string
sub() string
sup() string

8. 单体内置对象

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

前面以及介绍了大多数内置对象,例如 Object、Array、String

ECMA-262 还定义了两个单体内置对象:Global 和 Math

8.1. Global对象

Global (全局)对象可以说是 ECMAScript 中最特别的一个对象了,因为不管你从什么角度上看,这个对象都是不存在的。ECMAScript 中的 Global 对象在某种意义上是作为一个终极的 “兜底儿对象” 来定义的。

换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。之前学的 isNaN()、isFinite()、parseInt()、parseFloat(),实际上全都是 Global 对象的方法。除此之外,Global 对象还包含其他一些方法。

8.1.1. URI 编码方法

Global 对象的

  • encodeURI() 编码
  • encodeURIComponent() 编码
  • decodeURI() 解码
  • decodeURIComponent() 解码

可以对 URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的 URI 中不能包含某些字符,例如空格。而这两个 URI 编码方法就可以对 URI 进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。

其中,encodeURI() 主要用于整个 URI(例如,http://www.wrox.com/illegal value.htm),而 encode-URIComponent() 主要用于对 URI 中的某一段(例如前面 URI 中的 illegal value.htm) 进行编码。它们的主要区别在于,encodeURI() 不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;而 encodeURIComponent() 则会对它发现的任何非标准字符进行编码。

var uri = "http://www.wrox.com/illeagal value.htm#start";

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

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


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

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

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

使用 encodeURI() 编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了%20。而 encodeURIComponent() 方法则会使用对应的编码替换所有非字母数字字符。这也正式可以对整个 URI 使用 encodeURI(),而只能对附加在现有 URI 后面的字符串使用 encodeURIComponent() 的原因所在。

提示:一般来说,我们使用 encodeURIComponent() 方法的时候要比使用 encodeURI() 更多,因为在实践中更常见的是查询字符串参数而不是对基础 URI 进行编码。

8.1.2. eval()方法

eval() 是整个 ECMAScript 语言中最强大的一个方法,eval() 方法就像是一个完整的 ECMAScript 解析器,它只接受一个参数,即要执行的 ECMAScript 或 JavaScript字符串。

eval("alert('hi')");
// 这行代码的作用等价于下面这行代码:
alert('hi');

var msg = "hello world";
eval("alert(msg)"); // hello world

eval("function sayHi() { alert('hi'); }");
sayHi();

eval("var msg = 'hello world;'");
alert(msg); // hello world

在 eval() 中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在 eval() 执行的时候创建。

严格模式下,在外部访问不到 eval() 中创建的任何变量或函数,因此前面两个例子都会导致错误。

"use strict";
eval = "hi";  // causes error

提示:能够解释代码字符串的能力非常强大,但也非常危险。因此在使用 eval() 时必须极为谨慎,特别是在用它执行用户输入数据的情况下。否则,可能会有恶意用户输入威胁你的站点或应用程序安全的代码(即所谓的代码注入)。

8.1.3. Global 对象的属性

Global 对象还包含一些属性,其中一部分属性已经在本书本前面介绍过了。例如,特殊的值 undefined、NaN 以及 Infinity 都是 Global 对象的属性。此外,所有原生引用类型的构造函数,像 Object 和 Function,也都是 Global 对象的属性。

下表列出了 Global 对象的所有属性:

属性 说明
undefined 特殊值undefined
NaN 特殊值NaN
Infinity 特殊值Infinity
Object 构造函数Object
Array 构造函数Array
Function 构造函数Function
Boolean 构造函数Boolean
String 构造函数String
Number 构造函数Number
Date 构造函数Date
RegExp 构造函数RegExp
Error 构造函数Error
EvalError 构造函数EvalError
RangeError 构造函数RangeError
ReferenceError 构造函数ReferenceError
SyntaxError 构造函数SyntaxError
TypeError 构造函数农户TypeError
URIError 构造函数URIError

ECMAScript5 明确禁止给 undefined、NaN 和 Infinity 赋值,这样做即使在非严格模式下也会导致错误。

8.1.4. window 对象

ECMAScript 虽然没有指出如何直接访问 Global 对象,当 Web 浏览器都是将这个全局对象作为 window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了 window 对象的属性。

var color = "red";

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

window.sayColor(); // red

这里定义了一个名为 color 的全局变量和一个名为 sayColor() 的全局函数。在 sayColor() 内部,我们通过 window.color 来访问 color 变量,以说明全局变量是 window 对象的属性。然后,又使用 window.sayColor() 来直接通过 window 对象调用这个函数,结果显示在了警告框中。

提示:JavaScript 中的 window 对象除了扮演 ECMAScript 规定 Global 对象的角色外,还承担了很多别的任务。

// 另一种取得 Global 对象的方法是使用以下代码:
var global = function(){
    return this;
}();

以上代码创建了一个立即调用的函数表达式,返回 this 的值。如前所述,在没有给函数明确指定 this 值的情况下(无论是通过函数添加为对象方法还是通过调用 call() 或 apply() ),this 值等于 Global 对象。而像这样通过简单地返回 this 来取得 Global 对象,在任何执行环境下都是可行的。

8.2. Math对象

ECMAScript 还为保存数学公式和信息提供了一个公共位置,即 Math 对象。与我们在 JavaScript 直接编写的计算功能相比,Math 对象提供的计算功能执行起来要快得多。Math 对象中还提供了辅助完成这些计算的属性和方法。

8.2.1. Math 对象的属性

Math对象包含的属性大都是数学计算可能会用到的一些特殊值。下表列出了这些属性。

属性 说明
Math.E 自然对数的底数,即常量e的值
Math.LN10 10的自然对数
Math.LN2 2的自然对数
Math.LOG2E 以2位底e的对数
Math.LOG10E 以10为底e的对数
Math.PI π的值
Math.SQRT1_2 1/2的平方根(即2的平方根的倒数)
Math.SQRT2 2的平方根

虽然条轮这些值的含义用途超出了本书范围,但你确实可以随时使用它们。

8.2.2. min() 和 max() 方法

Mathu i对象还包含许多方法,用于辅助完成简单和复杂的数学计算。

var max = Math.max(3, 54, 32, 16);
alert(max); //54

var min = Math.min(3, 54, 32, 16);
alert(min); //3

要找到数组中的最大或最小值,可以像下面这样使用 apply() 方法

var values = [1, 2, 3, 4, 5, 6, 7, 8];
var max = Math.max.apply(Math, values);

这个技巧的关键是把 Math 对象作为 apply() 的第一个参数,从而正确地设置 this 值。然后,可以将任何数组作为第二个参数。

8.2.3. 舍入方法

将小数值舍入为整数的几个方法:

  • Math.ceil() 执行向上舍入,即它总是将数值向上舍入为最接近的整数

  • Math.floor() 执行向下舍入,即它总是将数值向下舍入为最接近的整数

  • Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)

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

8.2.4. random()方法

8.2.5. 其他方法

Mathu 对象中还包含其他一些与完成各种简单或复杂计算有关的方法,但详细讨论其中每一个方法的细节及适用情形超出了本书的范围。

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

虽然 ECMA-262 规定了这些方法,但不同实现可能会对这些方法采用不同的算法。毕竟,计算某个值的正弦、余弦和正弦的方法多种多样。也正因为如此,这些方法在不同的实现中可能会有不同的精度。

9. 小结

对象在 JavaScript 中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象。

简要总结如下:

  • 引用类型与传统面向对象程序设计中的类相似,但实现不同;
  • Object 是一个基础类型,其他所有类型都是从 Object 继承了基本的行为;
  • Array 类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;
  • Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
  • RegExp 类型是 ECMAScript 支持正则表达式的一个接口,提供了最基本的和一些高级的正则表达式功能。

函数实际上 Function 类型的实例,因此函数也是对象;而这一点是 JavaScript 最有特色的地方。由于函数时对象,所以函数也拥有方法,可以用来增强其行为。

因为有了基本包装类型,所以 JavaScript 中的基本类型值可以被当做对象来访问。

三种基本包装类型分别是:Boolean、Number、String。以下是它们的共同的特征:

  • 每个包装类型都映射到同名的基本类型;
  • 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;
  • 操作基本类型的语句一经执行完毕,就会立即销毁新创建的包装对象。

在所有代码执行之前,作用域就已经存在两个内置对象:GLobal 和 Math。

  • 在大多数 ECMAScript 实现中都不能直接访问 Global 对象;
  • 不过,Web 浏览器实现了承担该角色的 window 对象。全局变量和函数都是 Global 对象的属性。
  • Math 对象提供了很多属性和方法,用于辅助完成复杂的数学计算任务。

发表评论