《JavaScript 高级程序设计》学习总结五(4)
引言:这一章节我们对正则表达式和Function 类型进行总结。
RegExp 类型:
ECMAScript 通过RegExp类型 来支持正则表达式, var ex=/pattern /flag。 其中 pattern部分可以是任何简单或复杂的正则表达式,且正则表达式的匹配模式支持下列三个标志:
1、g 表示全局(globla)模式,该模式应用于所有字符串,而非在第一个匹配项时立即停止。
2、i 表示不区分大小写(case——insensitive)模式,即在确定匹配项时忽略模式与字符串大小写。
3、m 表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在于模式匹配的项。
JavaScript正则表达式规则 (点击查看正则表达式规则)。
RegExp 实例属性:
1、global :布尔值,表示是否设置了g 标志。
2、ignoreCase:布尔值,表示是否设置了 i标志。
3、lastIndex :整数,表示开始搜索下一个匹配项的字符位置,从0 开始。
4、multiline :布尔值,表示是否设置了 m 标志。
5、source :正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
通过这些属性可以获知一个正则表达式的各方面信息,但却没有多大用处,因为这些信息全都包含 在模式声明中。例如:
1 var pattern1 = /\[bc\]at/i; 2 3 alert(pattern1.global);//false 4 5 alert(pattern1.ignoreCase); //true 6 7 alert(pattern1.multiline); //false 8 9 alert(pattern1.lastIndex); //0 10 11 alert(pattern1.source); //"\[bc\]at" var pattern2 = new RegExp("\\[bc\\]at", "i"); 12 13 alert(pattern2.global); //false alert(pattern2.ignoreCase); //true alert(pattern2.multiline); //false 14 15 alert(pattern2.lastIndex); //0 alert(pattern2.source); //"\[bc\]at"
我们注意到,尽管第一个模式使用的是字面量,第二个模式使用了 RegExp 构造函数,但它们的 source 属性是相同的。可见,source 属性保存的是规范形式的字符串,即字面量形式所用的字符串
RegExp实例方法 :
RegExp 对象的主要方法是exec( ) 方法,exec() 方法用于检索字符串中的正则表达式的匹配。如果字符串中有匹配的值返回该匹配值,否则返回 null。
实例:
1 var str="Hello world!";//查找"Hello" 2 3 var patt=/Hello/g; 4 5 var result=patt.exec(str); document.write("返回值: " + result); // hello 6 7 //查找 "RUNOOB" patt=/RUNOOB/g; 8 9 result=patt.exec(str); document.write("<br>返回值: " + result); //null 10 11 12 13 返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。其中,index 表示匹配 项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配 的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。 请看下面的例子: 14 15 var text = "mom and dad and baby"; 16 17 var pattern = /mom( and dad( and baby)?)?/gi; 18 19 var matches = pattern.exec(text); 20 21 alert(matches.index); // 0 alert(matches.input); // "mom and dad and baby" 22 23 alert(matches[0]); // "mom and dad and baby" 24 25 alert(matches[1]); // " and dad and baby" alert(matches[2]); // " and baby"
正则表达式的第二个方法是 test():它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false。在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的 情况下,使用这个方法非常方便。因此,test()方法经常被用在 if 语句中,如下面的例子所示。
var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
alert("The pattern was matched.");
}
即使上例中的模式是通过调用 RegExp 构造函数创建的,但 toLocaleString()和 toString() 方法仍然会像它是以字面量形式创建的一样显示其字符串表示。
正则表达式的 valueOf()方法返回正则表达式本身
RegExp 构造函数属性:
RegExp 构造函数包含一些属性(这些属性在其他语言中被看成是静态属性)。这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。关于这些属性的另一个独 特之处,就是可以通过两种方式访问它们。换句话说,这些属性分别有一个长属性名和一个短属性名 (Opera 是例外,它不支持短属性名)。下表列出了 RegExp 构造函数的属性。
使用这些属性可以从 exec()或 test()执行的操作中提取出更具体的信息。请看下面的例子。
var text = "this has been a short summer";
var pattern = /(.)hort/g;
/* * 注意:Opera 不支持 input、lastMatch、lastParen 和 multiline 属性 * Internet Explorer 不支持 multiline 属性 */
1 if (pattern.test(text)){ 2 3 alert(RegExp.input); // this has been a short summer 4 5 alert(RegExp.leftContext); // this has been a 6 7 alert(RegExp.rightContext); // summer 8 9 alert(RegExp.lastMatch); // short 10 11 alert(RegExp.lastParen); // s alert(RegExp.multiline); // false 12 13 }
以上代码创建了一个模式,匹配任何一个字符后跟 hort,而且把第一个字符放在了一个捕获组中。 RegExp 构造函数的各个属性返回了下列值:
1、 input 属性返回了原始字符串;
2、 leftContext 属性返回了单词 short 之前的字符串,而 rightContext 属性则返回了 short 之后的字符串;
3、lastMatch 属性返回最近一次与整个正则表达式匹配的字符串,即 short;
4、 lastParen 属性返回最近一次匹配的捕获组,即例子中的 s。
模式的局限性:
尽管 ECMAScript 中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支 持的高级正则表达式特性。下面列出了 ECMAScript 正则表达式不支持的特性(要了解更多相关信息, 请访问 www.regular-expressions.info)。
1、匹配字符串开始和结尾的\A 和\Z
2、向后查找(lookbehind)
3、并集和交集类 原子组(atomic grouping)
4、Unicode 支持(单个字符除外,如\uFFFF)
5、命名的捕获组③ s(single,单行)和 x(free-spacing,无间隔)匹配模式
6、条件匹配
7、 正则表达式注释 即使存在这些限制,ECMAScript 正则表达式仍然是非常强大的,能够帮我们完成绝大多数模式匹 配任务。 ——————
---------------------------------------------------------------------------------------以上就是正则表达式内容,接下来是Function 类型的总结------------------------------------------------------------------------------
Function 类型:
此 Function 非彼 function。而 所有函数都是 此Function 的实例(所以这个 Function就是函数的幕后黑手),作为引用类型,在这里,Function 也一样具有其他引用类型的方法和属性。由于函数是对象,所以实际上函数名就是一个指向函数对象的指针。
函数声明方式:
function sum (num1, num2) {
return num1 + num2;
}
这与下面使用函数表达式定义函数的方式几乎相差无几。
var sum = function(num1, num2){return num1 + num2; };
最后一种定义函数的方式是使用 Function 构造函数。Function 构造函数可以接收任意数量的参数, 但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。来看下面的例子: var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
从技术角度讲,这是一个函数表达式。但是,我们不推荐读者使用这种方法定义函数,因为这种语 法会导致解析两次代码(第一次是解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串), 从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针”的概念倒是非常直观的。
(这里后面到变量提升会再次详细说明,这也是JavaScript 一个比较有意思的地方吧)。
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话 说,一个函数可能会有多个名字,如下面的例子所示。
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为 Null 但是这样的举动对anotherSum 函数并没有影响,因为 sum 只是一个指针而已,anotherSum 依旧指向那个实例。
没有重载:
将函数名想象为指针,也有助于理解为什么 ECMAScript 中没有函数重载的概念。以下是曾在第 3 章使用过的例子。
function addSomeNumber(num) { return num + 100; }
function addSomeNumber(num) { return num + 200; }
var result = addSomeNumber(100); //300 显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。
以上代码实际 上与下面的代码没有什么区别。
var addSomeNumber = function (num){ return num + 100; };
addSomeNumber = function (num) { return num + 200; };
var result = addSomeNumber(100); //300 通过观察重写之后的代码,很容易看清楚到底是怎么回事儿——在创建第二个函数时,实际上覆盖 了引用第一个函数的变量 addSomeNumber。
函数声明与函数表达式:
函数声明与函数表达式都可以创建一个函数,但是具体情况是有所不同,正如我上面提到的变量提升一样,函数在创用这两种方式创建的时候,也会有类似“预加载”这样的效果,在这里解析器会优先解析函数声明,并使其在执行任何代码前可用。至于函数表达式,则必须等待到解析器执行到所它所在的代码行,才会被真正执行。 举个栗子:
alert(sum(10,10));
function sum(num1, num2){ return num1 + num2; } //可以执行。因为在代码开始执行前,解析器就已经通过一个名为函数声明提升 的过程(“就是我说的预加载,这里预加载不是指真的预加载,而是一种类比让那个读者更容易理解”),将函数声明添加到执行环境中。
如果是函数表达式;
alert(sum(10,10)); var sum = function(num1, num2){ return num1 + num2; }; //这样就会执行失败了。这样的情况,在执行函数所在的语句前,,变量sum 中不会保存有对函数的引用,而且由于第一行代码就会导致 “unexpected identity”(意外标识符)错误,所以一般情况下代码也不会运行下去。
作为值的函数:
在ECMAScript 中,函数名就是变量,所以也可以作为参数传递到函数中。比如:
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
当然也可以从一个函数中返回另一个函数,(闭包的应用就是通过这样的方式),以及我们上次对 sort( )方法进行解说时,也是用了这样的方式:
1 function createComparisonFunction(propertyName) { 2 3 return function(object1, object2){ 4 5 var value1 = object1[propertyName]; 6 7 var value2 = object2[propertyName]; 8 9 if (value1 < value2){ 10 11 return -1; 12 13 } 14 15 else if (value1 > value2){ 16 17 return 1; } 18 19 else { return 0; 20 21 } 22 23 }; 24 25 }
在函数中嵌套另一个函数,里面的函数通过 return 操作符进行结果返回。
函数内部属性:
函数内部有两个特殊的对象,一个是 arguments 另一个是 this , arguments 这个属性如果看过前面几章的小伙伴应该不陌生,这个属性的作用就是用来保存函数的参数,同时这个属性还有一个名叫 callee 的属性,这个属性相当于一个指针,一直执向拥有这个属性的 arguments对象的函数。
比如:
1 function factorial(num){ 2 3 if (num <=1) { return 1; 4 5 } else { return num * factorial(num-1) 6 7 } 8 9 } 10 11 可以相当于: 12 13 function factorial(num){ 14 15 if (num <=1) { return 1; 16 17 } else { 18 19 return num * arguments.callee(num-1) 20 21 } 22 23 }
使用callee 属性的好处就是不需要修改函数名,就像上面这个例子一样,如果函数名改变 ,return 中的函数名也要改变,这样是很容易出错以及不方便的,所以使用callee 就可以避免这样的情况了。
函数还有一个内部属性 this ,这个this 在这我就大概说一说吧,作为JavaScript 中的难点之一,this 总是让人感觉懵逼,this 引用的是函数据以执行的环境对象。所以 看this 不能看声明而是要看到执行的那一刻,到底是谁在引用他,这样才能判断他执向谁。
这里不做赘述,还是留待后面再详细解说。
ECMAScript 5也规范化了一个函数对象的属性:caller 。这个属性的作用是 保存着 调用当前函数 的 函数的引用,话有点绕,直接看实例:
1 function outer(){ 2 3 inner(); 4 5 } 6 7 function inner(){ 8 9 alert(inner.caller); 10 11 } 12 13 outer(); //弹出 function outer(){inner() 在这里 outer() 调用了 inner () ,所以 inner.caller 就指向 outer()。 14 15 } 16 17 18 19 为了实现更松散的耦合,也可以通过 arguments.callee.caller 来访问相同的信息。 20 21 function outer(){ 22 23 inner(); 24 25 } 26 27 function inner(){ 28 29 alert(arguments.callee.caller); 30 31 } 32 33 outer();
IE、Firefox、Chrome 和 Safari 的所有版本以及 Opera 9.6 都支持 caller 属性。 当函数在严格模式下运行时,访问 arguments.callee 会导致错误。ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。定义这个属性是为了分清 arguments.caller 和函数的 caller 属性。以上变化都是为 了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。 严格模式还有一个限制:不能为函数的 caller 属性赋值,否则会导致错误。
函数属性和方法:
函数作为对象,也拥有自己的属性和方法,每个函数都包含:length 和 prototype。其中length 这个属性我们应该是不陌生的,但是在这里我们还以为是返回一个对象长度或者是什么长度那么就理解错了,在函数对象中,这个length 指的是函数接收到的参数的个数。
比如:
function name( name){
alert(name.length); //1
}
而另一个属性 prototype 就更耐人寻味了,prototype 是保存它们所有实例方法的真正所在。换句话说,诸如 toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访 问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要。在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。(这里不做赘述,将会在第六章中详细解释。)
每个函数都包含两个非继承而来的方法 apply() 以及 call() 。这两个方法的作用是在特定的作用域中调用函数,实际上等于设置函数体内的 this 对象的值,首先 apply( )接收两个参数:一个是在其运行函数的作用域,另一个是参数数组,其中第二个可以是 Array 的实例,也可以是 arguments 对象。
比如:
guments 对象。例如:
apply() 方法: function sum(num1, num2){ return num1 + num2; } function callSum1(num1, num2){ return sum.apply(this, arguments); // 传入 arguments 对象 } function callSum2(num1, num2){ return sum.apply(this, [num1, num2]); // 传入数组 } alert(callSum1(10,10)); //20 alert(callSum2(10,10)); //20
在上面这个例子中,callSum1()在执行 sum()函数时传入了 this 作为 this 值(因为是在全局 作用域中调用的,所以传入的就是 window 对象)和 arguments 对象。而 callSum2 同样也调用了 sum()函数,但它传入的则是 this 和一个参数数组。这两个函数都会正常执行并返回正确的结果
PS:在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。 除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是 undefined。
call()方法:
与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用 call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。
1 function sum(num1, num2){ 2 3 return num1 + num2; 4 5 } 6 7 function callSum(num1, num2){ 8 9 return sum.call(this, num1, num2); 10 11 } 12 13 alert(callSum(10,10)); //20
在使用 call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用 apply()没有 什么不同。至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。 如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply() 肯定更方便;否则,选择 call()可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所 谓。)
事实上,传递参数并非 apply()和 call()真正的用武之地;它们真正强大的地方是能够扩充函数 赖以运行的作用域。下面来看一个例子。
1 window.color = "red"; 2 3 var o = { color: "blue" }; 4 5 function sayColor(){ alert(this.color); } 6 7 sayColor(); //red 8 9 sayColor.call(this); //red 10 11 sayColor.call(window); //red 12 13 sayColor.call(o); //blue
使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。 在前面例子的第一个版本中,我们是先将 sayColor()函数放到了对象 o 中,然后再通过 o 来调用它的; 而在这里重写的例子中,就不需要先前那个多余的步骤了。
ECMAScript 5 还扩充了一个方法——bind( ):
这个方法会创建一个函数的实例,其 this 的值会被绑定到 blind( )函数的值。比如:
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } var objectSayColor = sayColor.bind(o); objectSayColor(); //blue 在这里,sayColor()调用 bind()并传入对象 o,创建了 objectSayColor()函数。objectSayColor()函数的 this 值等于 o,因此即使是在全局作用域中调用这个函数,也会看到"blue"
支持 bind()方法的浏览器有 IE9+、Firefox 4+、Safari 5.1+、Opera 12+和 Chrome。 每个函数继承的 toLocaleString()和 toString()方法始终都返回函数的代码。返回代码的格 式则因浏览器而异——有的返回的代码与源代码中的函数代码一样,而有的则返回函数代码的内部表 示,即由解析器删除了注释并对某些代码作了改动后的代码。由于存在这些差异,我们无法根据这两个 方法返回的结果来实现任何重要功能;不过,这些信息在调试代码时倒是很有用。另外一个继承的 valueOf()方法同样也只返回函数代码。
---------------------------------------------------------------本章节完-------------------------------------------------------------------------------------------------------------------------------
这一章节的内容有些多,我在总结的时候也是考虑好久才敲下键盘,最后打算直接全部记录下来,因为正则表达式那里说实话还是有很多知识点,但是仅仅靠记是不行的,还要多练习,所以正则表达式我还是放了链接以及只进行了一些例子说明而已。
下一章节,继续对引用类型的内容进行讲解,主要要点:基本包装类型以及一些内置对象。