0%

引用类型

本章内容

  • 使用对象
  • 创建并操作数组
  • 理解基本的javascript类型
  • 使用基本类型和基本包装类型

引用类型的值(对象)是引用类型的一个实例。在ECMAScript中,引用类型是一种数据结构。引用类型有时也被称为对象定义,因为他们描述的是一类对象所具有的属性和方法。

如上所说,对象是某个特定引用类型的实例。新对象是用new操作符后跟一个构造函数来创建的。

object类型

创建object实例的方式有两种。第一种是使用new操作符后跟object构造函数,如下所示:

1
2
3
var person = new Object();
person.name = "Nicholas";
person.age = 29;

另一种方式是使用对象字面量表示法。{是对象字面量的开始,例如:

1
2
3
4
person = {
name : "Nicholas";
age : 29;
}

使用对象字面量语法时,如果留空其花括号,则可以定义只包括默认属性和方法的对象,如下:

1
2
3
var person = {};  // 同new object()
person.name = "lihua";
person.age = 29

通过对象字面量定义对象时,实际上不会调用Object构造函数。
开发人员更倾向于对象字面量语法

在JavaScript中也可以通过方括号来访问对象的属性。使用[]时,应该将要访问的属性以字符串的形式放在方括号中,如下例所示:

1
2
alert(person["name"])
alert(person.name)

方括号语法的主要优点是,可以通过变量来访问属性。
如果属性名中,会包含导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。
除非必须使用变量来访问属性,否则推荐使用点表示法。

Array

  • ECMAScript数组的每一项可以保存任何类型的数据。
  • ECMAScript的数组大小是可以动态调整的。

创建数组的基本方式

  • 使用array构造函数
1
2
3
var colors = new Array(20)
var colors = new Array("red", "yellow", "blue")
var colors = new Array(20)

可以给构造函数传递数量,数量会自动变成length属性的值。

也可以向构造函数中传递数组中应该包含的项。

另外,构造函数可以省略new操作符

  • 数组字面量表示法
1
2
3
4
var colors = ["red", "yellow", "blue"]
var colors = [] // 空数组
var colors = [1,2,] // 不要这样,会创建包含2或3项的数组
var colors = [,,,,,] // 不要这样,会创建包含5或6项的数组

与对象字面量一样,使用数组字面量时也不会调用Array构造函数。

1
2
var colors = ["red", "yellow", "blue"]
colors[3] = "pink"

若是设置的索引超过了数组现有项数,数组会自动添加到该索引值+1的长度,如上代码。

数组的length属性,不是只读的,通过这个属性,可以从数组的末尾移除项或向数组中添加新项,被移除/添加的项将为undefined

检测数组

  • 对于一个网页或全局作用域,使用instanceof可以满足判断array类型的需求。
1
2
3
if (value instanceof Array) {

}

instanceof存在的问题:它假定只存在一个全局执行环境。如果从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

  • Array.isArray()方法: 最终确定这个值到底是不是数组。

转换方法

每个对象都具有toLocalString(), toString(), valueOf方法。

  • valueOf: 返回的还是数组本身。
  • toString(): 返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。
  • alert(array):alert接受字符串参数,所以在后台调用toString()方法,由此与toString()的结果相同。
  • join(): 使用不同的分隔符创建这个字符串。

栈方法

ECMAScript为数组专门提供了push()pop()方法,以便实现类似栈的行为。

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

队列方法

  • shift():它能够移除数组中的第一个项并返回该项,同时数组长度减1;
  • unshift():它能在数组前端添加任意个项并返回新数组的长度。

重排序方法

  • reverse():反转数组项的顺序。
  • sort()
    • 默认情况下,sort()方法按升序排列数组项,sort()会调用每个数组项的toString()转型方法,然后比较得到的字符串,确定如何排序。
    • sort()可以接受一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。比较函数接受2个参数,若第一个参数在第二个参数前,返回一个负数,反之为正数,相等为0.

操作方法

  • concat():基于当前数组中的所有项创建一个新数组。这个方法会先创建一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。

  • slice():基于当前数组中的一个或多个创建一个新数组。可以接受一个或两个参数,即要返回的起始和结束位置。slice()不会影响原始数组。

  • splice():主要用途是向数组中间插入项。

    • 删除:删除任意数量的项。2个参数:起始位置,要删除的项数。
    • 插入:指定位置插入任意数量的项,3个参数:起始位置,0(要删除的项数),要插入的项。
    • 替换:指定位置插入任意数量的项,且同时删除任意数量的项,需要指定3个参数:起始位置,要删除的项数,要插入的任意数量的项。
    • splice()方法始终会返回一个数组,该数组中包含从原始数组中删除的项,如果没有删除任何项,则返回一个空数组。

位置方法

  • indexOf():2个参数:要查找的项、查找起点位置的索引,从数组开头向后找。
  • lastIndexOf():从数组末尾开始向前查找。

返回查找项在数组中的位置,没找到返回-1
使用全等操作符(===),要查找的项必须严格相等

迭代方法

ECMAScript为数组定义了5个迭代方法,每个方法接受2个参数:在每一项上运行的函数(可选),运行该函数的作用域对象——影响this的值。传入这些方法的函数接受3个参数:数组项的值、该项在数组中的位置和数组对象本身

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

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

归并方法

迭代数组中所有的项,然后构建一个最终返回的值。

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

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

reduce()reduceRight()的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数是数组的第二项。

利用reduce()方法实现数组累加:

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

Date类型

  • Date类型保存的日期能够精确到1970年1月1日之前或之后的100 000 000天。
  • 创建一个日期对象:
1
var now = new Date();
  • Date.parse():接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。
  • Date.UTC():同样也返回表示日期的毫秒数,但他与Date.parse()的构建值时使用不同的信息。
  • Date.now():返回表示调用这个方法时的日期和时间毫秒数。

继承的方法

  • toLocalString():按照与浏览器设置的地区相适应的格式返回日期和时间。
  • toString():返回带有时间信息的日期和时间,其中时间一般以军用时间表示。
  • valueOf:根本不返回字符串,而是返回日期的毫秒表示,故可以方便使用比较操作符来比较日期值。

日期格式化方法

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

  • toDateString()
  • toTimeString()
  • toLocalDateString()
  • toLocalTimeString()
  • toUTCString()

RegExp类型

创建正则表达式

  • 通过字面量形式来定义

创建一个正则表达式:

1
var expression = / pattern / flags ;
  • pattern:可以是任何简单或复杂的正则表达式。

  • flags:标志,用以标明正则表达式的行为。

    • g:全局模式,即模式将被用于所有字符串,而非在发现第一个匹配项时立即停止。
    • i:表示不区分大小写模式。
    • m:表示多行模式,到达一行末尾时还会到下一行继续查找。
  • 模式中所使用的所有元字符都必须转义。正则表达式中的元字符包括:([{\ ^ $ |) ? * + . ] }

  • 这些元字符在正则表达式中都有一种或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,就必须对它们进行转义。

1
2
3
4
5
var pattern1 = /[bc]at/i  // 匹配第一个“cat” or “bat”
var pattern2 = /\[bc\]at/i // 匹配第一个/[bc]at/i

var pattern3 = /.at/gi
var pattern4 = /\.at/gi

pattern1匹配的是第一个“cat” or “bat”,而想要直接匹配/[bc]at/i,就需要对其中的两个方括号进行转义。

  • RegExp函数构建
1
2
var pattern1 = /[bc]at/i 
var pattern2 = new RegExp("[bc]at", "i")

传递给RegExp表达式的两个参数是字符串,所以有时候需要对字符进行双重转义。所有的元字符都必须双重转义,那些已经转义过的字符也是。

ECMAScript3中,正则表达式字面量始终会共享一个RegExp实例,而使用构造函数创建的每一个新RegExp实例都是一个新的实例。所以,字面量二次调用时,会从上一次匹配的末尾开始,可能会匹配不到。

  • ECMAScript5明确规定,使用正则表达式字面量必须像直接调用RegExp构造函数一样,每次都创建新的RegExp实例。

RegExp实例属性

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

RegExp实例方法

  • RegExp对象的主要方法是exec(),该方法是专门为捕获组而设置的。
  • exec()接收一个参数(要应用模式的字符串),然后返回包含第一个匹配项信息的数组;若是没有匹配项,则返回null。
  • 返回的项虽然是Array的实例,但是包含两个额外的属性:index(匹配项在字符串中的位置)和input(应用正则表达式的字符串)。
  • 对于exec()来说,即使设置了全局标志g,他每次也只会返回一个匹配项。
  • 不设置全局标志时,在同一个字符串上多次调用exec()始终返回第一个匹配项的信息。
  • 在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var text = "cat, bat, sat, fat";
var pattern1 = /.at/;

var matches = pattern1.exec(text);
alert(matches.index) // 0
alert(matches[0]) // cat
alert(pattern1.lastIndex) // 0

var matches = pattern1.exec(text);
alert(matches.index) // 0
alert(matches[0]) // cat
alert(pattern1.lastIndex) // 0


var pattern2 = /.at/g;

var matches = pattern1.exec(text);
alert(matches.index) // 0
alert(matches[0]) // cat
alert(pattern1.lastIndex) // 3

var matches = pattern1.exec(text);
alert(matches.index) // 5
alert(matches[0]) // bat
alert(pattern1.lastIndex) // 8 lastIndex每次调用exec后都会增加
  • 第二个方法是test(),它接收一个字符串参数。
  • 在模式与该参数匹配的情况下返回true,否则,返回false。
  • 因为只想知道目标字符串与某个模式是否匹配,此方法常用于if语句中。

RegExp构造函数属性

  • input
  • lastMatch
  • lasParen
  • leftContext
  • multiline
  • rightContext

模式的局限性

ECMAScript正则表达式不支持的特性

  • 匹配字符串开始和结尾的\A和\Z锚
  • 向后查找
  • 并集会让交集类
  • 原子组
  • Unicode支持
  • 命名的捕获组
  • s(单行)和x(无间隔)匹配模式
  • 条件匹配
  • 正则表达式注释

Function类型

  • 每个函数都是Function类型的实例,而且与其他引用类型一样具有属性和方法。

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

  • 函数通常是使用函数声明语法定义的。

  • 通过定义一个变量,来初始化一个函数

  • 使用Function构造函数。Function构造函数可以接收任意数量的参数,但是最后一个函数始终会被看成是函数体,前面的参数枚举出了新函数的参数。

没有重载

  • 函数名看作是指向函数对象的指针
  • 声明两个同名函数,后面的函数会覆盖前面的函数

函数声明与函数表达式

  • 解析器在向执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码前可用。
  • 函数表达式,必须等到解析器执行到它所在的代码行,才会真正被解析执行。
1
2
3
4
5
alert(sum(10, 10));

function sum(num1, num2) {
return num1 + num2;
}
  • 以上代码可以正常运行
  • 代码执行前,解析器已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。
  • 对代码求值时,JavaScript引擎在第一遍时会声明函数并将它们放到源代码树的顶部。
  • 所以,即使声明函数在调用他的代码的后面,JavaScript引擎也能把函数声明提升到顶部。
1
2
3
4
5
alert(sum(10, 10));

var sum = function(num1, num2) {
return num1 + num2;
}
  • 上述代码运行时会产生错误
  • 函数位于一个初始化语句中,而不是函数声明中
  • 在执行到函数所在的语句之前,变量sum中不会保存有对函数的引用
  • 第一行代码就会导致unexpected identifier(意外标识符)错误,实际上也不会执行到下一行。

作为值的函数

  • 可以把一个函数作为参数传给另一个函数
  • 可以将一个函数作为另一个函数的结果返回。
1
2
3
4
5
6
7
8
9
10
function call(someFuction, someArgument) {
return someFuction(someArgument)
}

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

var result = call(add10, 10);
alert(result) // 20
  • 要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对大括号。
  • 因此,传递给call()函数的是add10,而不是执行后他们的结果。

函数内部属性

  • 函数内部,有两个特殊对象:argumentsthis
  • arguments主要是用来保存函数参数,是一个类数组对象,有一个叫callee()的属性,该属性是一个指针,指向拥有这个arguments对象的函数
  • 肥肠经典的阶乘函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function factorial(num) {
if (num<=1) {
return 1;
} else {
return num * factorial(num-1);
}
}

// 这个函数的执行与函数名factorial紧紧耦合在了一起,不好。

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

// 没有与函数名耦合,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用

var trueFactorial = factorial;

factorial = function() {
return 0;
}

alert(trueFactorial(5)) // 120
alert(factorial(5)) // 0
  • this

    • this引用的是函数执行的环境对象
    • 全局环境调用函数,this对象引用的是window
    • 对象调用函数时,this对象引用的是调用函数的对象
  • caller:这个属性保存着调用当前函数的函数的引用

  • 在全局作用域中调用当前函数,他的值为null

  • 严格模式下,arguments.calleearguments.caller会导致错误,非严格模式下,arguments.caller始终为undefined

  • arguments.callee是为了分清arguments.caller和函数的caller属性。

  • 以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。

函数属性和方法

  • 函数是个对象,因此函数也有属性和方法
  • length:函数希望接受的命名参数的个数
  • prototype:保存它们所有实例方法的真正存在,创建自定义引用类型以及实现继承时非常重要。
    • prototype是无法枚举的,因此for-in无法发现。
  • 每个函数都包含两个非继承而来的方法:apply()call()用途都是在特定的作用域中调用函数,实际等于设置函数体内this对象的值
    • apply:接收两个参数
      • 在其中运行的函数的作用域
      • 参数数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
// this 在全局作用域中调用,所以传入的是window对象
  • call:第一个参数是this,其余参数都直接传给函数。

  • apply()call()能够扩充函数赖以运行的作用域。对象和方法不需要有任何耦合关系。

  • bind

    • 这个方法会创建一个函数的实例,其this值会被绑定到传给bind函数的值。
1
2
3
4
5
6
7
8
9
10
window.color = "red";
var o = {
color : "blue";
}

function sayColor() {
alert(this.color);
}
var objectsayColor = sayColor.bind(o);
objectsayColor(); // blue

基本包装类型

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

  • Boolean
  • Number
  • String

这3个其实属于基本数据类型,但是它们仍然有一些方法。这是因为,实际上,每当读取一个基本类型值时,后台就会创建一个对应的基本包装类型的对象,从而能让我们调用一些方法来操作这些数据。

1
2
var s1 = "some text"
var s2 = s1.subString(2)
  • s2的后台:
    • 创建string类型的一个实例
    • 在实例上调用指定的方法
    • 销毁这个实例

引用类型和基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内容中。而自动创建的基本包装类型的对象,只存在一行代码执行的一瞬间,然后立即被销毁。

1
2
var obj = new Object("nihao")
alert(obj instance of String) // true

Boolean

1
var obj = new Boolean("true")  // Object

Number

1
var obj = new Number(10)  // Object

String

1
var obj = new String("nihao")  // Object

单体内置对象

内置对象:由ECMAScript实现提供的,不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。

Global对象

所有在全局作用域中定义的属性和函数,都是 Global对象的属性。

URL编码方法

  • encodeURI():用于整个URI,不会对本身属于URI的特殊字符进行编码,如冒号、正斜杠等。
  • encodeURIComponent():用于URI的某一段,会对他发现的任何非标准字符进行编码。
  • decodeURI():解码
  • decodeURIComponent():解码

eval方法

  • 只接受一个参数,即要执行的ECMAScript字符串。

  • 当解析器发现eval方法时,会把传入的参数当成实际的ECMAScript语句来解析,然后把结果插入原位置。

  • 通过eval执行的代码被认为包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链,这意味着,eval可以引用在包含环境中定义的变量。

  • eval中创建的任何变量或函数都不会被提升。

window对象

web浏览器将全局对象Global作为window对象的一部分加以实现。

因此,在全局变量中声明的变量或函数,都成为了window对象的属性。