四、RegExp类型
JS
通过RegExp
类型来支持正则表达式,语法如下:
var expression = / pattern / flags ;
其中的模式(pattern
)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags
),用以标明正则表达式的行为。可以有如下三个标志:
-
g
:表示全局(global
)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止; -
i
:表示不区分大小写模式。即在确定匹配项时忽略模式与字符串的大小写; -
m
:表示多行模式。即在达到一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
注意:在正则表达式中,模式中使用的所有元字符都必须转义(转义符号是\\
)。元字符如下:
( [ { \ ^ $ | ) ? * + . ] }
例如:
//匹配所有".at",不区分大小写
var pattern = /\.at/gi;
以上例子是以字面量形式来定义的正则表达式。另一种创建正则表达式的方式是使用RegExp
构造函数,接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。
//两种方式定义同一个正则表达式,匹配第一个"bat"或"cat",不区分大小写
var pattern1 = /[bc]at/i;
var pattern2 = new RegExp("[bc]at", "i");
说明:这里要注意,传递给RegExp
构造函数的两个参数都是字符串(不能把正则表达式字面量传递给构造函数)。由于构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。如:
字面量模式 | 等价字符串 |
---|---|
/\[bc\]at/ |
"\\\\[bc\\\\]at" |
/\.at/ |
"\\\\.at" |
/name\/age/ |
"name\\\\/age" |
/\d.\d{1,2}/ |
"\\\\d.\\\\d{1,2}" |
/\w\\\\hello\\\\123/ |
"\\\\w\\\\\\\\hello\\\\\\\\123" |
使用正则表达式字面量和使用RegExp
构造函数创建的正则表达式不一样。在ECMAScript 3
中,正则表达式字面量始终会共享同一个RegExp
实例,而使用构造函数创建的每一个新RegExp
实例都是一个新实例:
var re = null, i;
for(i = 0; i < 10; i++){
re = /cat/g;
re.test("catastrophe");
}
for(i = 0; i < 10; i++){
re = new RegExp("cat", "g");
re.test("catastrophe");
}
说明:在第一个循环中,机试是循环体中指定的,但实际上只为/cat/
创建了一个RegExp
实例。由于实例属性(后面介绍)不会重置,所以在循环中再次调用test()
方法会失败。因为第一次调用test()
方法找到了"cat"
,但第二次调用是从索引为3
的字符(上一次匹配的末尾)开始的,所以就找不到它了。由于测试到字符串末尾,所以再下一次调用test()
方法就又从头开始了。而使用RegExp
构造函数会每次都创建新实例,所以不会失败。
4.1 RegExp 实例属性
RegExp
的每个实例都具有下列属性:
-
global
:布尔值,表示是否设置了g
标志 -
ignoreCase
:布尔值,表示是否设置了i
标志 -
lastIndex
:整数,表示开始搜索下一个匹配项的字符位置,从零算起 -
multiline
:布尔值,表示是否设置了m
标志 -
source
:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
var pattern1 = /\[bc\]at/i;
alert(pattern1.global); //false
alert(pattern1.ignoreCase); //true
alert(pattern1.multiline); //false
alert(pattern1.lastIndex); //0
alert(pattern1.source); //"\[bc\]at"
4.2 RegExp 实例方法
-
exec()方法
RegExp
对象的主要方法是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"
说明:上述代码中,最外层的捕获组是" and dad( and baby)?"
,里层的捕获组是"( and baby)"
。注意:对于exec()
方法而言,即使在模式中设置了全局标志,它每次也只会返回一个来匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用此方法将始终返回第一个匹配项的信息。而设置了全局标志时,则每次调用都会在字符串中继续查找新匹配项。
-
test()方法
此方法接受一个字符串参数。在模式与该参数匹配的情况下返回true,否则返回false。在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,使用此方法是一个不错的选择。 -
RegExp
实例继承的toLocaleString()
和toString()
方法都会返回正则表达式的字面量。而其valueOf()
方法返回正则表达式本身。
var pattern = new RegExp("\\[bc\\]at", "gi");
alert(pattern.toString()); // /\[bc\]at/gi
alert(pattern.toLocaleString()); // /\[bc\]at/gi
4.3 RegExp构造函数属性
RegExp
构造函数属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。可以通过两种方式访问它们。
长属性名 | 短属性名 | 说明 |
---|---|---|
input |
$_ |
最近一次要匹配的字符串 |
lastMatch |
$& |
最近一次的匹配项 |
lastParen |
$+ |
最近一次的捕获组 |
leftContext |
$` (tab 上方的符号) |
input 字符串中lastMatch 之前的文本 |
nultiline |
$* |
布尔值,表示是否所有表达式都使用多行模式 |
rightContext |
$' (单引号) |
input 字符串中lastMatch 之后的文本 |
使用这些属性可以从exec()
或test()
执行的操作中提取出更具体的信息。
var text = "this has been a short summer";
var pattern = /(.)hort/g;
/*
* 注意: Opera 不支持 input, lastMatch, lastParen, 和 multiline属性
* IE不支持multiline
*/
if (pattern.test(text)){
alert(RegExp.input); //this has been a short summer
alert(RegExp.leftContext); //this has been a注意:没有后面的s
alert(RegExp.rightContext); // summer
alert(RegExp.lastMatch); //short
alert(RegExp.lastParen); //s
alert(RegExp.multiline); //false
}
说明:
-
input
属性返回了原始的字符串 -
leftContext
属性返回了单词short
之前的字符串,而rightContext
属性则返回了short
之后的字符串; -
lastMatch
属性返回了最近一次与整个正则表达式匹配的字符串,即short
-
lastParen
属性返回了最近一次匹配的捕获组,即例子中的s
例子中使用的长属性名都可以使用相应的短属性名来代替,只是这些短属性名大都不是有效的ECMAScript
标识符,因此必须通过方括号语法来访问它们:
var text = "this has been a short summer";
var pattern = /(.)hort/g;
/*
* 注意: Opera 不支持 input, lastMatch, lastParen, 和 multiline属性
* IE不支持multiline
*/
if (pattern.test(text)){
alert(RegExp.$_); //this has been a short summer
alert(RegExp["$`"]); //this has been a
alert(RegExp["$'"]); // summer
alert(RegExp["$&"]); //short
alert(RegExp["$+"]); //s
alert(RegExp["$*"]); //false
}
说明:还有多达(只有)九个用于存储捕获组的构造函数属性。访问这些属性的语法是RegExp.$1、RegExp.$2、...、RegExp.$9
,分别用于存储第一、第二、...、第九个匹配的捕获组。
var text = "this has been a short summer";
var pattern = /(..)or(.)/g;
if (pattern.test(text)){
alert(RegExp.$1); //sh
alert(RegExp.$2); //t
}
五、Function类型
函数实际上是对象,每个函数都是Function
类型的实例,因为函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。定义如下:
//函数声明
function sum(num1, num2){
return num1 + num2;
}
//函数表达式
var sum = function(num1, num2){
return num1 + num2;
};//注意这里有个分号
//使用构造函数
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
说明:在函数赋值中,没有使用圆括号,所以这是访问函数指针,而不是调用函数。anotherSum
和sum
都指向同一个函数,因此即使将sum
设置为null
,让它与函数“断绝关系”,但仍然可以正常调用anotherSum()
。
5.1 函数声明与函数表达式
解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用(可以访问),至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
alert(sum(10,10)); //20
function sum(num1, num2){
return num1 + num2;
}
说明:以上代码完全可以正常执行,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。如果上面代码中函数定义不是使用函数声明,而是使用函数表达式则会出现错误。
5.2 作为值的函数
函数本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。例如,假设有一个对象数组,我们想要根据某个对象属性进行排序。而传递给数组sort()
方法的比较函数要接收两个参数,即要比较的值。这里,我们可以定义一个函数,它接收一个属性名,然后根据这个属性名来创建一个比较函数。
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
说明:这里返回的函数会应用到数组中的每一项,实现按照相关属性对数组的排序。
5.3 函数内部属性(arguments、this、callee、caller)
在函数内部,有两个特殊的对象:arguments
和this
。arguments
是一个类数组对象,包含着传入函数中的所有参数。arguments
对象还有一个callee
的属性,该属性是一个指针,指向拥有这个arguments
对象的函数。
fucntion factorial(num){
if(num <= 1){
return 1;
}else{
return num * factorial(num - 1);
}
}
说明:这个函数是一个递归函数。这样定义没有问题,但问题是这个函数的执行与函数名factorial
紧密耦合在一起,在实际调用的时候会发现会出现问题,比如使用别的函数名就不能调用。这里可以使用arguments.callee
解决。
fucntion factorial(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num - 1);
}
}
说明:此时,在调用函数时可以使用任意的函数名。如:
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5));
alert(factorial (5));
函数内部另一个特殊对象是this
,其行为和Java
中的this
大致类似。也就是this
引用的是函数据以执行的环境——或者也可以说是this
值(当在网页的全局作用域中调用函数时,this
对象引用的就是window
)。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
o.sayColor = sayColor;
o.sayColor(); //blue
说明:上面定义的函数sayColor()
是在全局作用域中定义的,引用了this
对象。由于在调用函数之前,this
的值并不确定,因此this
可能会在代码执行过程中引用不同的对象。从两次调用函数返回不同的值也可以看出来。
ECMAScript 5
规范了另一个函数对戏那个的属性:caller
。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null
。
function outer(){
inner();
}
function inner(){
alert(inner.caller);
//alert(arguments.callee.caller);//为了实现跟松散的耦合
}
outer();
说明:以上代码会导致警告框中显示outer()
函数的源代码。严格模式下,arguments.callee
和caller
都会导致错误,同时不能为函数的caller
属性赋值,否则会导致错误。
5.4 函数属性和方法(length、prototype、applay()、call()、bind())
每个函数都包含两个属性:length
和prototype
。其中,length
属性表示函数希望接收的命名参数的个数。对于ECMAScript
中的引用类型而言,prototype
是保存它们所有实例方法的真正所在。也就是说,如toString()
和valueOf()
等方法实际上都保存在prototype
名下,只不过是通过各自对象的实例访问罢了。prototype
属性是不可枚举的,因此使用for-in
无法发现。
每个函数都包含两个非继承而来的方法:applay()、call()
。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this
对象的值。首先,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
说明:可以看到不管是传递arguments
还是传递数组都可以正常运行。callSum1
方法中传递了this
作为this
值(因为是在全局作用域中调用的,所以传入的是window
对象)。而call()
方法和apply()
方法没有什么本质上的不同,只是调用的时候传入参数的时候,必须明确传入每一个参数。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20
说明:实际上,传递参数并非这两个函数的用武之地,真正强大的地方是能够扩充函数赖以运行的作用域。
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
说明:可以看到,我们传入什么作用域,则函数就在哪个作用域上执行。
函数还有一个方法:bind()
。这个方法会创建一个函数的实例,其this
值会被绑定到传给bind()
函数的值。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
说明:起始功能很简单,就是设置一个函数的this
值。
最后,每个函数继承的toLocaleString()、toString()、valueOf()
方法始终都返回函数的代码。
网友评论