主题选择:system

JavaScript 引用类型

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构, 用于将数据和功能组织在一起。它也常被称为类,但这种称呼并不妥当。尽管 ECMAScript 从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构。引 用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。

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

var person = new Object();

5.1 Object 类型

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

创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数

var person = new Object() person.name = 'tom' person.age = 18

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建 包含大量属性的对象的过程。下面这个例子就使用了对象字面量语法定义了与前面那个例子中相同的 person 对象:

var person = { name : 'tom', age : 18 }

在使用对象字面量语法时,属性名也可以使用字符串

var person = { "name" : 'tom', "age" : 18, 5 : true }

一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法。不过, 在 JavaScript 也可以使用方括号表示法来访问对象的属性。在使用方括号语法时,应该将要访问的属性 以字符串的形式放在方括号中

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

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

5.2 Array类型

除了 Object 之外,Array 类型恐怕是 ECMAScript 中最常用的类型了。而且,ECMAScript 中 的数组与其他多数语言中的数组有着相当大的区别。虽然 ECMAScript 数组与其他语言中的数组都是 数据的有序列表,但与其他语言不同的是,ECMAScript 数组的每一项可以保存任何类型的数据。 也 就是说,可以用数组的第一个位置来保存字符串,用第二位置来保存数值,用第三个位置来保存对象, 以此类推,而且,ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容 纳新增数据

创建数组的方法1:

var arr = new Array()

如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成 length 属性的值。

var arr = new Array(20)// 创建了一个长度为20的数组

也可以向 Array 构造函数传递数组中应该包含的项。以下代码创建了一个包含 3 个字符串值的数组

var colors = new Array("red", "blue", "green")

当然,给构造函数传递一个值也可以创建数组。但这时候问题就复杂一点了,因为如果传递的是数 值,则会按照该数值创建包含给定项数的数组;而如果传递的是其他类型的参数,则会创建包含那个值 的只有一项的数组。

var arr = new Array(3) //创建一个3个长度的数组 var arr = new Array('sa') //创建为长度为1的sa

另外,在使用 Array 构造函数时也可以省略 new 操作符

5.2.1 检测数组

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实 际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从 一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自 不同的构造函数。

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

Array.isArray([]) //true

5.2.2 转换方法

如前所述,所有对象都具有 toLocaleString()、toString()和 valueOf()方法。其中,调用 数组的 toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。而 调用 valueOf()返回的还是数组。实际上,为了创建这个字符串会调用数组每一项的 toString()方 法。

var colors = ['red','blue','green'] alert(colors.toString()) // red,blue,green alert(colors.valueOf()) // red,blue,green alert(colors) // red,blue,green

在这里,我们首先显式地调用了 toString()方法,以便返回数组的字符串表示,每个值的字符串 表示拼接成了一个字符串,中间以逗号分隔。接着调用 valueOf()方法,而最后一行代码直接将数组 传递给了 alert()。由于 alert()要接收字符串参数,所以它会在后台调用 toString()方法,由此 会得到与直接调用 toString()方法相同的结果。

另外,toLocaleString()方法经常也会返回与 toString()和 valueOf()方法相同的值,但也 不总是如此。当调用数组的 toLocaleString()方法时,它也会创建一个数组值的以逗号分隔的字符 串。而与前两个方法唯一的不同之处在于,这一次为了取得每一项的值,调用的是每一项的 toLocaleString()方法,而不是 toString()方法。

var val = { toString : function () { return '1' }, toLocalString : function () { return '-1' } } var val1 = { toString : function () { return '2' }, toLocalString : function () { return '-2' } } var content = [val,val1] content.toString() // 1 ,2 content.toLocalString() // -1 ,-2 调用每项的toLocalString content // 1 ,2 默认调用toString 方法

数组继承的 toLocaleString()、toString()和 valueOf()方法,在默认情况下都会以逗号分隔的字 符串的形式返回数组项。而如果使用 join()方法,则可以使用不同的分隔符来构建这个字符串。join()方 法只接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。

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

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

5.2.3 栈方法

ECMAScript 数组也提供了一种让数组的行为类似于其他数据结构的方法。具体说来,数组可以表 现得就像栈一样,后者是一种可以限制插入和删除项的数据结构。栈是一种 LIFO(Last-In-First-Out, 后进先出)的数据结构,也就是最新添加的项最早被移除。而栈中项的插入(叫做推入)和移除(叫做 弹出),只发生在一个位置——栈的顶部。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(color.length) //2

5.2.4 队列方法

由于 push()是向数组末端添加项的方法, 因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是 shift(),它能够移 除数组中的第一个项并返回该项,同时将数组长度减 1。结合使用 shift()和 push()方法,可以像使 用队列一样使用数组。

var color = new Array() var count = color.push('red','green') alert(count) // 2 var item = color.shift() alert(item) // red color.length //1

ECMAScript 还为数组提供了一个 unshift()方法。顾名思义,unshift()与 shift()的用途相反: 它能在数组前端添加任意个项并返回新数组的长度。因此,同时使用 unshift()和 pop()方法,可以 从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项

5.2.5 重新排序方法

  • reverse()
  • sort()
  1. reverse 反转数组
var number = [1,2,3,4,5] number.reverse() number // [5,4,3,2,1]
  1. sort方法

在默认情况下,sort()方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。 为了实现排序,sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以 确定如何排序。即使数组中的每一项都是数值,sort()方法比较的也是字符串

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

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

function compare(val1,val2){ if(val1 < val2){ return -1 }else if(val1 > val2){ return 1 }else{ return 0 } }

对于数值类型或者其 valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函 数。这个函数只要用第二个值减第一个值即可。

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

由于比较函数通过返回一个小于零、等于零或大于零的值来影响排序结果,因此减法操作就可以适 当地处理所有这些情况。

5.2.6 操作方法

ECMAScript 为操作已经包含在数组中的项提供了很多方法。其中,concat()方法可以基于当前数 组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数 添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是 复制当前数组并返回副本。如果传递给 concat()方法的是一或多个数组,则该方法会将这些数组中的 每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。

var colors = ['red' , 'green' ,'blue'] var colors2 = colors.concat('1',['2','3']) colors // ['red' , 'green' ,'blue'] colors2 // ['red' , 'green' ,'blue' , '1','2','3']

下一个方法是 slice(),它能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以 接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该 参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项—但不包括结束位置的项。注意,slice()方法不会影响原始数组。

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

下面我们来介绍 splice()方法,这个方法恐怕要算是最强大的数组方法了,它有很多种用法。 splice()的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下 3 种。

  • 删除:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。 例如,splice(0,2)会删除数组中的前两项。
  • 插入:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、0(要删除的项数) 和要插入的项。 如果要插入多个项, 可以再传入第四、第五, 以至任意多个项。 例如, splice(2,0,“red”,“green”)会从当前数组的位置 2 开始插入字符串"red"和"green"。
  • 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起 始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如, splice (2,1,“red”,“green”)会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串 “red"和"green”。

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

5.2.7 位置方法

ECMAScript 5 为数组实例添加了两个位置方法:indexOf()和 lastIndexOf()。这两个方法都接收 两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()方法从数组的开头(位 置 0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回1。在比较第一个参数 与数组中的每一项时,会使用全等操作符;也就是说,要求查找的项必须严格相等(就像使用===一样)。

5.2.8 迭代方法

ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数 组项的值、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能 会也可能不会影响方法的返回值。

  • every() 用于查询数组中的项是否满足某个条件,如果全部为true则返回true,否则返回false

  • some() 用于查询数组中的项是否满足某个条件,如果一个为true则返回true

    var numbers = [1,2,3,4,5,4,3,2,1]; var everyResult = numbers.every(function(item, index, array){ return (item > 2); }); // false 因为有小于2的值 var someResult = numbers.some(function(item,index,array){ return (item > 2) })//true 因为有大于2的值
  • filter()函数,它利用指定的函数确定是否在返回的数组中包含某一项。返回过滤的数组(过滤器)

    var numbers = [1,2,3,4,5,4,3,2,1]; var filterResult = numbers.filter(function(item,index,array){ return item > 2 }) 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 }) mapResult // [2,3,6,8,10,8,6,4,2]
  • forEach(),它只是对数组中的每一项运行传入的函数。

    //简单复制数组 var numbers = [1,2,3,4,5,4,3,2,1] var newGroup = [] numbers.forEach(function(item,index,array){ newGroup.push(item) }) newGroup //[1,2,3,4,5,4,3,2,1]

5.2.9 归并方法

ECMAScript 5 还新增了两个归并数组的方法:reduce()和 reduceRight()。这两个方法都会迭 代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历 到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。

reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。

这 个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第 一个参数是数组的第一项,第二个参数就是数组的第二项。

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

第一次执行回调函数,prev 是 1,cur 是 2。第二次,prev 是 3(1 加 2 的结果),cur 是 3(数组 的第三项)。这个过程会持续到把数组中的每一项都访问一遍,最后返回结果。

reduceRight()的作用类似,只不过方向相反而已。

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

5.3 Date类型

ECMAScript 中的 Date 类型是在早期 Java 中的 java.util.Date 类基础上构建的。为此,Date 类型使用自 UTC(Coordinated Universal Time,国际协调时间)1970 年 1 月 1 日午夜(零时)开始经过 的毫秒数来保存日期。

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

创建日期对象 var time = new Date()

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根 据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数(即从 UTC 时间 1970 年 1 月 1 日午 夜起至该日期止经过的毫秒数)。为了简化这一计算过程,ECMAScript 提供了两个方法:Date.parse() 和 Date.UTC()。

  • Date.parse() 接受一个日期字符串,反馈一个相应日期的毫秒数

    var ms = Date.parse('1993-09-28') var birthDay = new Date(ms) birthDay //Tue Sep 28 1993 08:00:00 GMT+0800 (中国标准时间) //与下面类似 var birthDay = new Date('1993-09-28')

    ECMA-262 没有定义 Date.parse()应该支持哪种日期格式,因此这个方法的行为因实现 而异,而且通常是因地区而异。

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

    Date.UTC()的参数分别是年份、基于 0 的月份(一月是 0,二月是 1,以此类推)、月 (1 到 31)、小时数(0 到 23)、分钟、秒以及毫秒数。

    在这些参数中,只有前两个参数(年和月)是必 需的。如果没有提供月中的天数,则假设天数为 1;如果省略其他参数,则统统假设为 0。

    // GMT 时间 2000 年 1 月 1 日午夜零时 var y2k = new Date(Date.UTC(2000, 0)); //类似于 var y2k = new Date(2000, 0); // GMT 时间 2005 年 5 月 5 日下午 5:55:55 var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55)); //类似于 var allFives = new Date(2005, 4, 5, 17, 55, 55);
  • ECMAScript 5 添加了 Data.now()方法,返回表示调用这个方法时的日期和时间的毫秒数。这个方 法简化了使用 Data 对象分析代码的工作。

    var start = Date.now() dosomething() var stop = Date.now() //可以用以下替换 var start = +new Date() //+号替换成字符串 调用new Date().valueOf() dosomething() var stop = +new Date()

5.3.2 📅日期格式化

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

ECMAScript 推荐现在编写的代码一律使用 toUTCString()方法。

5.4 RegExp 类型

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

var expression = / pattern / flags ;

其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、 向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。 正则表达式的匹配模式支持下列 3 个标志。

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

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

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

前面举的这些例子都是以字面量形式来定义的正则表达式。另一种创建正则表达式的方式是使用 RegExp 构造函数,它接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。可以 使用字面量定义的任何表达式,都可以使用构造函数来定义,

/* * 匹配第一个"bat"或"cat",不区分大小写 */ var pattern1 = /[bc]at/i; //使用构造函数创建正则表达式 var pattern1 = new RegExp("[bc]at","i")

5.4.1 RegExp实例属性

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

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

5.5 Function 类型

说起来 ECMAScript 中什么最有意思,我想那莫过于函数了——而有意思的根源,则在于函数实际 上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函 数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函 数声明语法定义的

函数的三种创建方式:

//函数声明 function sum(num1,num2){ return num1 + num2 } //函数表达式 var sum = function(num1,num2){ return num1 + num2 } //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; alert(anotherSum(10,10)); //20 sum = null; alert(anotherSum(10,10)); //20

以上代码首先定义了一个名为 sum()的函数,用于求两个值的和。然后,又声明了变量 anotherSum, 并将其设置为与 sum 相等(将 sum 的值赋给 anotherSum)。注意,使用不带圆括号的函数名是访问函 数指针,而非调用函数。此时,anotherSum 和 sum 就都指向了同一个函数,因此 anotherSum()也 可以被调用并返回结果。即使将 sum 设置为 null,让它与函数“断绝关系”,但仍然可以正常调用 anotherSum()。

5.5.1 没有重载(深入理解)

将函数名想象为指针,也有助于理解为什么 ECMAScript 中没有函数重载的概念。(重复声明的函数会被覆盖)

function addSomeNum(num){ return num + 100 } function addSomeNum(num){ return num + 200 } addSomeNum(100) //300

函数被覆盖。函数表达式也是如此。

5.5.2 函数声明与函数表达式

本节到目前为止,我们一直没有对函数声明和函数表达式加以区别。而实际上,解析器在向执行环 境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行 任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真 正被解释执行。

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

上述代码可以运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升 (function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript 引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后 面,JavaScript 引擎也能把函数声明提升到顶部。

如果改变为函数表达式则会出现错误

alert(sum(10,10)) var sum = function(num1,num2){ return num1 + num2 }

原因在于函数位于一个初始化语句中,而不是一个函数声 明。换句话说,在执行到函数所在的语句之前,变量 sum 中不会保存有对函数的引用;而且,由于第一 行代码就会导致“unexpected identifier”(意外标识符)错误,实际上也不会执行到下一行。

5.5.3 作为值的函数

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

function execFunction(fn,params){ return fn(params) } function addSomeNum(num){ return num + 10 } execFunction(addSomeNum,10) //20

当然,可以从一个函数中返回另一个函数,而且这也是极为有用的一种技术。例如,假设有一个 对象数组,我们想要根据某个对象属性对数组进行排序。而传递给数组 sort()方法的比较函数要接收 两个参数,即要比较的值。可是,我们需要一种方式来指明按照哪个属性来排序。要解决这个问题, 可以定义一个函数,它接收一个属性名,然后根据这个属性名来创建一个比较函数,下面就是这个函 数的定义。

function compare(objectName){ return function(obj1,obj2){ var val1 = obj1[objectName] var val2 = obj2[objectName] if(val1 > val2){ return -1 }else if(val1 < val2){ return 1 }else{ return 0 } } } //实例 var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}]; data.sort(compare("name")); alert(data[0].name); //Nicholas data.sort(compare("age")); alert(data[0].name); //Zachary

5.5.4 函数内部属性

在函数内部,有两个特殊的对象:arguments 和 this。其中,arguments 在第 3 章曾经介绍过, 它是一个类数组对象,包含着传入函数中的所有参数。虽然 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)。

window.color = 'red' var o = { color : "blue" } function sayColor(){ return this.color } sayColor() // red o.sayColor = sayColor o.sayColor() // blue

上面这个函数 sayColor()是在全局作用域中定义的,它引用了 this 对象。由于在调用函数之前, this 的值并不确定,因此 this 可能会在代码执行过程中引用不同的对象。当在全局作用域中调用 sayColor() 时, this 引用的是全局对象 window ;换句话说, 对 this.color 求值会转换成对 window.color 求值,于是结果就返回了"red"。而当把这个函数赋给对象 o 并调用 o.sayColor() 时,this 引用的是对象 o,因此对 this.color 求值会转换成对 o.color 求值,结果就返回了"blue"。

caller属性。这个属性保存着当前函数的引用对象(是谁来调用这个函数)

function myFunc(){ if(arguments.callee.caller == 'null'){ console.log("调用我的人是全局环境") }else{ console.log("调用我的人是" + arguments.callee.caller) } } myFunc() // 调用我的人是全局环境 function test(){ return myFunc() } test() // 调用我的人是test 打印test函数

5.5.5 函数属性和方法

ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个 属性:length 和 prototype。其中,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

在 ECMAScript 核心所定义的全部属性中, 最耐人寻味的就要数 prototype 属性了。 对于 ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。换句话说,诸如 toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访 问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要的(第 6 章将详 细介绍)。在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。

每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作 用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个 是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象。

function sum(num1,num2){ return num1 + num2 } function sumCall(num1,num2){ return sum.call(this,[num1,num2]) } function sumCall2(num1,num2){ return sum.call(this,arguments) } sumCall(10,10) //20 sumCall2(10,10) //20

call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用 call()方法时,传递给函数的参数必须逐个列举出来

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

window.color = 'red' var o = { color: 'blue' } function sayColor(){ return this.color } sayColor() // red sayColor.call(window) //red sayColor.call(o) //blue

而 sayColor.call(this)和 sayColor.call(window),则是两 种显式地在全局作用域中调用函数的方式,结果当然都会显示"red"。但是,当运行 sayColor.call(o) 时,函数的执行环境就不一样了,因为此时函数体内的 this 对象指向了 o,于是结果显示的是"blue"。

使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。 在前面例子的第一个版本中,我们是先将 sayColor()函数放到了对象 o 中,然后再通过 o 来调用它的; 而在这里重写的例子中,就不需要先前那个多余的步骤了。

//bind window.color = 'red' var o = { color: 'blue' } function sayColor(){ return this.color } var objectBind = sayColor.bind(o) objectBind() // blue

5.6 基本包装类型

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

var s1 = "some text"; var s2 = s1.substring(2);

这个例子中的变量 s1 包含一个字符串, 字符串当然是基本类型值。 而下一行调用了 s1 的 substring()方法,并将返回的结果保存在了 s2 中。我们知道,基本类型值不是对象,因而从逻辑上 讲它们不应该有方法(尽管如我们所愿,它们确实有方法)。其实,为了让我们实现这种直观的操作, 后台已经自动完成了一系列的处理。当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要 从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理。

  1. 创建 String 类型的一个实例;
  2. 在实例上调用指定的方法;
  3. 销毁这个实例。

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

5.6.1 Boolean类型

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

var booleanObject = new Boolean(true);

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

var falseObj = new Boolean(false) var result = falseObj && true result // true 此时falseObj没有进行求值 仅仅是把falseObj当成一个 obj对象 var falseValue = false; result = falseValue && true; alert(result); //false

基本类型与引用类型的布尔值还有两个区别。首先,typeof 操作符对基本类型返回"boolean", 而对引用类型返回"object"。其次,由于 Boolean 对象是 Boolean 类型的实例,所以使用 instanceof 操作符测试 Boolean 对象会返回 true,而测试基本类型的布尔值则返回 false。

5.6.2 Number类型

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

var num = new Number(10)

与 Boolean 类型一样,Number 类型也重写了 valueOf()、toLocaleString()和 toString() 方法。重写后的 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 = new Number(10) num.toFixed(2) // 10.00 var a = 10.005 a.toFixed(2) // 10.01

5.6.3 String 类型

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

var str = new String('hello world')

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

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

var str = 'hello world' str.length // 11

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

  1. 字符方法

两个用于访问字符串中特定字符的方法是:charAt()和 charCodeAt()。这两个方法都接收一个 参数,即基于 0 的字符位置。其中,charAt()方法以单字符字符串的形式返回给定位置的那个字符 (ECMAScript 中没有字符类型)。

var str = 'hello world' str.charAt(1) // e str.charCodeAt(1) // 101 输出的是"101",也就是小写字母"e"的字符编码

ECMAScript 5 还定义了另一个访问个别字符的方法。在支持此方法的浏览器中,可以使用方括号加数 字索引来访问字符串中的特定字符

var str = 'hello world' str[1] // e
  1. 字符串操作方法

concat() 拼接字符串,返回一个新字符串

var str = 'hello ' var result = str.concat('world') result // hello world str // hello

concat()方法可以接受任意多个参数,也就是说可以通过它 拼接任意多个字符串。

var str = 'hello ' var result = str.concat('world','~') result // hello world~

ECMAScript 还提供了三个基于子字符串创建新字符串的方法:slice()、substr()和 substring()。 这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。

var str = 'hello world' str.slice(3) // lo world str.substr(3) // lo world str.substring(3)// lo world str.slice(3,7) // lo w str.substr(3,7) //lo worl str.substring(3,7) //lo w

在传递给这些方法的参数是负值的情况下,它们的行为就不尽相同了。其中,slice()方法会将传 入的负值与字符串的长度相加,substr()方法将负的第一个参数加上字符串的长度,而将负的第二个 参数转换为 0。最后,substring()方法会把所有负值参数都转换为 0。

  1. 字符串位置方法

有两个可以从字符串中查找子字符串的方法:indexOf()和 lastIndexOf()。这两个方法都是从 一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没有找到该子字符串,则返回-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
  1. trim()方法

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

var str = ' hello world ' var newStr = str.trim() newStr // hello wold 去除了两端空格 str // hello world 原字符串
  1. 字符串大小写转换方法

toLowerCase() 全部转换为小写

toUpperCase() 全部转换为大写

返回一个新的字符串

var str = 'hello world' var newStr = str.toUpperCase() //HELLO WORLD
  1. 字符串的模式匹配方法

String 类型定义了几个用于在字符串中匹配模式的方法。第一个方法就是 match(),在字符串上 调用这个方法,本质上与调用 RegExp 的exec()方法相同。match()方法只接受一个参数,要么是一 个正则表达式,要么是一个 RegExp 对象。

var text = "cat, bat, sat, fat"; var pattern = /.at/; var matches = text.match(pattern); alert(matches.index); // 0 alert(matches[0]); //cat alert(pattern.lastIndex); //0

另一个用于查找模式的方法是 search()。这个方法的唯一参数与 match()方法的参数相同:由字 符串或 RegExp 对象指定的一个正则表达式。search()方法返回字符串中第一个匹配项的索引;如果没 有找到匹配项,则返回-1。而且,search()方法始终是从字符串开头向后查找模式。

var text = "cat, bat, sat, fat"; var pos = text.search(/at/) pos // 1

这个例子中的 search()方法返回 1,即"at"在字符串中第一次出现的位置。

为了简化替换子字符串的操作,ECMAScript 提供了 replace()方法。这个方法接受两个参数:第 一个参数可以是一个 RegExp 对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参 数可以是一个字符串或者一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替 换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局(g)标志

var text = "cat, bat, sat, fat"; text.replace(/at/g,'on') text // con,bon,son,fon

最后一个与模式匹配有关的方法是 split(),这个方法可以基于指定的分隔符将一个字符串分割成 多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象(这个方 法不会将字符串看成正则表达式)。split()方法可以接受可选的第二个参数,用于指定数组的大小, 以便确保返回的数组不会超过既定大小。

var colorText = "red,blue,green,yellow"; var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"] var colors2 = colorText.split(",", 2); //["red", "blue"]

localeCompare()方法 fromCharCode()方法 有需要再看

5.7 单体内置对象

单体内置对象:Global 和 Math。

5.7.1 Global对象

Global(全局)对象可以说是 ECMAScript 中最特别的一个对象了,因为不管你从什么角度上看, 这个对象都是不存在的。ECMAScript 中的 Global 对象在某种意义上是作为一个终极的“兜底儿对象” 来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全 局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。本书前面介绍 过的那些函数,诸如 isNaN()、isFinite()、parseInt()以及 parseFloat(),实际上全都是 Global 对象的方法。除此之外,Global 对象还包含其他一些方法。

  1. URI 编码方法

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

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

var uri = "http://www.wrox.com/illegal value.htm#start"; alert(encodeURI(uri)); //"http://www.wrox.com/illegal%20value.htm#start" alert(encodeURIComponent(uri)); // //"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"

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

与 encodeURI() 和 encodeURIComponent() 方法对应的两个方法分别是 decodeURI() 和 decodeURIComponent()。其中,decodeURI()只能对使用 encodeURI()替换的字符进行解码。例如, 它可将%20 替换成一个空格,但不会对%23 作任何处理,因为%23 表示井字号(#),而井字号不是使用 encodeURI()替换的。同样地,decodeURIComponent()能够解码使用 encodeURIComponent()编码的所有字符,即它可以解码任何特殊字符的编码。

  1. eval()方法

现在,我们介绍最后一个——大概也是整个 ECMAScript 语言中最强大的一个方法:eval()。eval() 方法就像是一个完整的 ECMAScript 解析器,它只接受一个参数,即要执行的 ECMAScript(或 JavaScript) 字符串。

eval('alert('hi')')等同于alert('hi')

当解析器发现代码中调用 eval()方法时,它会将传入的参数当作实际的 ECMAScript 语句来解析, 然后把执行结果插入到原位置。通过 eval()执行的代码被认为是包含该次调用的执行环境的一部分, 因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过 eval()执行的代码可以引用在包 含环境中定义的变量

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

可见,变量 msg 是在 eval()调用的环境之外定义的,但其中调用的 alert()仍然能够显示"hello world"。这是因为上面第二行代码最终被替换成了一行真正的代码。同样地,我们也可以在 eval() 调用中定义一个函数,然后再在该调用的外部代码中引用这个函数:

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

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

  1. Global 对象的属性

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

  1. window 对象

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

取得全局对象的方法

var global = function(){ return this }()

5.7.2 Math对象

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

  1. Math 对象的属性

Math.E 自然对数的底数

  1. min()和max()方法

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

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

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

var values = [1, 2, 3, 4, 5, 6, 7, 8]; var max = Math.max.apply(Math,values) //这个技巧的关键是把 Math 对象作为 apply()的第一个参数,从而正确地设置 this 值。然后,可 以将任何数组作为第二个参数。
  1. 舍入方法
  • Math.ceil() 执行向上舍入,即它总是将数值向上舍入为最接近的整数;
  • Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
  • Math.round() 执行标准舍入,即它总是将数值四舍五 上学到的舍入规则)。
  1. random()方法

Math.random()方法返回大于等于 0 小于 1 的一个随机数。对于某些站点来说,这个方法非常实用, 12 因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式,就可以利用 Math.random() 从某个整数范围内随机选择一个值。

值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)

评论