JavaScript高级程序设计学习(四)之引用类型(续)
一、Date类型
其实引用类型和相关的操作方法,远远不止昨天的所说的那些,还有一部分今天继续补充。
在java中日期Date,它所属的包有sql包,也有util包。我个人比较喜欢用util包的。理由,顺手习惯,个人也觉得比较好用。sql的Date类用的不算多。也用过。
下面就进入正题吧,不单单在java,在js中也存在日期函数,或换言之,日期对象。
var now = new Date();
通过now.getYear(),now.getMonth(),now.getMonth(),简单的翻译,可以理解为获得当前的年月日。
now翻译过来表示现在,之所以采取这样的命名,还是那就话,规范起见。
js获得的日期,通常情况要么通过字符串拼接,要么通过格式转换,达到自己想要的那样。
常用的格式转换方法如下:
toDateString()——以特定于实现的格式显示星期几、月、日和年;
toTimeString()——以特定于实现的格式显示时、分、秒和时区;
toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
toUTCString()——以特定于实现的格式完整的 UTC日期。 与 toLocaleString()和 toString()方法一样,以上这些字符串格式方法的输出也是因浏览器 而异的,因此没有哪一个方法能够用来在用户界面中显示一致的日期信息;
日期和时间组件方法如下(大家可以参考):
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(毫秒) 设置日期中的毫秒数
二、 RegExp 类型
RegExp,简单的说,就是正则表达式,正则表达式,在开发中用的也很多,比如表单校验,手机号码,邮箱,网关,昵称,qq号,身份证等,当然在java中也有相关的方法和工具类可以完成验证,但是那样的话,全部请求就会走向服务器,那么对于服务器负载也是很大的。所以这也是js的一大亮点,减少服务器压力,同时也给用户比较好的体验。试问,如果走服务器的话,在过去网络这玩意,是个新鲜货,人们那时填写表单点击提交,基本都是跑服务器,但是有的时候不小心点错了或者大意疏忽了,好不容易填完十几个表单输入框,最后告知你,有几个必填项或者几个不符合要求的项,要求你重新填写,在那个时候,人们不得不接受,但是在这个用户量即市场的互联网时代,可以说用户就是上帝,如果用户不满意相当于自取灭亡。
正则表达式在此就可以发挥作用了,当用户填写不合法,直接提示相关文字信息,当必填项没填写直接表单被红框标记,现在的html5表单校验就是基于正则和一些相关原生js方法,这个我曾经在我开发的博客中用过。挺好用的。
当然,正则表达式的作用远远不止这么点,它可以根据字符匹配,这样就有个小小的匹配功能,也可以说搜索功能,当然这个搜索肯定是不能和百度那样的公司比的。这个搜索只不过是字符匹配。
(1)RegExp实例属性
global:布尔值,表示是否设置了 g 标志。
ignoreCase:布尔值,表示是否设置了 i 标志。
lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从 0算起。
multiline:布尔值,表示是否设置了 m 标志。
source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
(2)RegExp实例方法
RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。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 andbaby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
alert(matches[2]); // " and baby"
var text = "this has been a short summer";
var pattern = /(.)hort/g;
/* * 注意:Opera 不支持 input、lastMatch、lastParen 和 multiline 属性 * Internet Explorer 不支持 multiline 属性 */
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
}
以上代码创建了一个模式,匹配任何一个字符后跟 hort,而且把第一个字符放在了一个捕获组中。 RegExp 构造函数的各个属性返回了下列值: input 属性返回了原始字符串; leftContext 属性返回了单词 short 之前的字符串,而 rightContext 属性则返回了 short 之后的字符串; lastMatch 属性返回近一次与整个正则表达式匹配的字符串,即 short; lastParen 属性返回近一次匹配的捕获组,即例子中的 s。
(3)模式的局限性
尽管 ECMAScript中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支 持的高级正则表达式特性。下面列出了 ECMAScript正则表达式不支持的特性。
匹配字符串开始和结尾的\A 和\Z 锚①
向后查找(lookbehind)②
并集和交集类
原子组(atomic grouping)
Unicode支持(单个字符除外,如\uFFFF)
命名的捕获组③
s(single,单行)和 x(free-spacing,无间隔)匹配模式
条件匹配
正则表达式注释
三、Function
说起来 ECMAScript中什么有意思,我想那莫过于函数了——而有意思的根源,则在于函数实际 上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函 数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函 数声明语法定义的。
function sum (num1, num2) {
return num1 + num2;
}
与下面这个并没有什么差异
var sum = function(num1, num2){
return num1 + num2;
};
以上代码定义了变量 sum 并将其初始化为一个函数。有读者可能会注意到,function 关键字后面 没有函数名。这是因为在使用函数表达式定义函数时,没有必要使用函数名——通过变量 sum 即可以引 用函数。另外,还要注意函数末尾有一个分号,就像声明其他变量时一样。
下面示例不推荐使用:
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()。
var addSomeNumber = function (num){
return num + 100;
};
addSomeNumber = function (num) {
return num + 200;
};
var result = addSomeNumber(100); //300
通过观察重写之后的代码,很容易看清楚到底是怎么回事儿——在创建第二个函数时,实际上覆盖 了引用第一个函数的变量 addSomeNumber。
(1)函数声明和函数表达式
我们一直没有对函数声明和函数表达式加以区别。而实际上,解析器在向执行环 境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行 任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真 正被解释执行。
示例:
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;
};
这个就会导致常见js错误,函数未定义错误。
(2)作为值函数
因为 ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以 像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数的一个值。 然后,就可以像下面的例子一样传递函数了。
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1); //20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); //"Hello, Nicholas"
这里的 callSomeFunction()函数是通用的,即无论第一个参数中传递进来的是什么函数,它都 会返回执行第一个参数后的结果。还记得吧,要访问函数的指针而不执行函数的话,必须去掉函数名后 面的那对圆括号。因此上面例子中传递给 callSomeFunction()的是 add10 和 getGreeting,而不 是执行它们之后的结果。
当然,可以从一个函数中返回另一个函数,而且这也是极为有用的一种技术。例如,假设有一个 对象数组,我们想要根据某个对象属性对数组进行排序。而传递给数组 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;
}
};
}
这个函数定义看起来有点复杂,但实际上无非就是在一个函数中嵌套了另一个函数,而且内部函数 前面加了一个 return 操作符。在内部函数接收到 propertyName 参数后,它会使用方括号表示法来 取得给定属性的值。取得了想要的属性值之后,定义比较函数就非常简单了。
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
这里,我们创建了一个包含两个对象的数组 data。其中,每个对象都包含一个 name 属性和一个 age 属性。在默认情况下,sort()方法会调用每个对象的 toString()方法以确定它们的次序;但得 到的结果往往并不符合人类的思维习惯。因此,我们调用 createComparisonFunction("name")方 法创建了一个比较函数,以便按照每个对象的 name 属性值进行排序。而结果排在前面的第一项是 name 为"Nicholas",age 是 29的对象。然后,我们又使用了 createComparisonFunction("age")返回 的比较函数,这次是按照对象的 age 属性排序。得到的结果是 name 值为"Zachary",age 值是 28的 对象排在了第一位。
(3)函数内部属性
在函数内部,有两个特殊的对象:arguments 和 this。其中,arguments 在第 3章曾经介绍过, 它是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 请看下面这个非常经典的阶乘函数:
function factorial(num){ if (num <=1) { return 1; } else { return num * factorial(num-1) } }
IE、Firefox、Chrome和 Safari的所有版本以及 Opera 9.6都支持 caller 属性。 当函数在严格模式下运行时,访问 arguments.callee 会导致错误。ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。定义这个属性是为了分清 arguments.caller 和函数的 caller 属性。以上变化都是为 了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。 严格模式还有一个限制:不能为函数的 caller 属性赋值,否则会导致错误。
(4)函数属性和方法
前面曾经提到过,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 属性的作用是极为重要的。在 ECMAScript 5中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。
每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作 用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个 是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象。
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
注意:
在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。 除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是 undefined。
四、基本包装类型
说到包装类型,在Java中不得不提基本数据类型的包装类。
在此温习下
Java基本数据类型及其对应的包装类
int - Integer
byte - Byte
long - Long
short - Short
char - Char
float - Float
double - Double
boolean - Boolean
在js中包装类型属于引用类型,Java同样如此。
ECMAScript提供了三个包装类,String,Boolean,Number,分别对应的类型,字符类型,布尔类型,数字类型。
对于包装类型不做太多讲解,学过Java的人容易上手,没有学过Java的人理解也不存在困难。
这里讲解是包装类有什么用,最直接直白的作用就是,类型转换非常简单。不需要什么parseInt,toString,parseFloat等等进行调用转换方法。直接var b = new String(“1”),即可将其转为字符串类型。
其他也同理。
String,Boolean,Number我就在此不提太多了。
有一个要提Global,这个要提的原因主要是设计URL编码和解码问题,这个在实际应用中非常广,比如邮件验证,url加密。
编码和解码,可理解为加密和解密的过程,代码如下:
<html> <meta charset="utf-8"> <head> <script> var uri = "http://www.baidu.com/illegal?userId=1"; alert(encodeURI(uri)); alert(encodeURIComponent(uri)); </script> </head> <body> </body> </html>
使用 encodeURI()编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了 %20。而 encodeURIComponent()方法则会使用对应的编码替换所有非字母数字字符。这也正是可以 对整个URI使用encodeURI(),而只能对附加在现有URI后面的字符串使用encodeURIComponent() 的原因所在。
与 encodeURI()和 encodeURIComponent()方法对应的两个方法分别是 decodeURI()和 decodeURIComponent()。其中,decodeURI()只能对使用 encodeURI()替换的字符进行解码。例如, 它可将%20 替换成一个空格,但不会对%23 作任何处理,因为%23 表示井字号(#),而井字号不是使用 encodeURI()替换的。同样地,decodeURIComponent()能够解码使用 encodeURIComponent()编码,的所有字符,即它可以解码任何特殊字符的编码。
示例如下所示:
<html> <meta charset="utf-8"> <head> <script> var uri = "http%3A%2F%2Fwww.baidu.com%2Fillegal%20value.htm%23start"; alert(decodeURI(uri)); alert(decodeURIComponent(uri)); </script> </head> <body> </body> </html>
这里,变量 uri 包含着一个由 encodeURIComponent()编码的字符串。在第一次调用 decodeURI() 输出的结果中,只有%20 被替换成了空格。而在第二次调用 decodeURIComponent()输出的结果中, 所有特殊字符的编码都被替换成了原来的字符,得到了一个未经转义的字符串(但这个字符串并不是一 个有效的 URI)。
注意:
URI方法 encodeURI()、encodeURIComponent()、decodeURI()和 decode- URIComponent()用于替代已经被ECMA-262第3版废弃的escape()和unescape() 方法。URI方法能够编码所有 Unicode字符,而原来的方法只能正确地编码 ASCII字符。 因此在开发实践中,特别是在产品级的代码中,一定要使用URI方法,不要使用 escape() 和unescape()方法。
eval这个方法我常用,主要是将json格式数据解析出来。
另外再提一个Math对象,该对象如其名"数学",主要涉及数学相关的
同时还有min和max方法,最小值和最大值
直接var num = Math.max(3,2,1,0)
var num2 = Math.min(1,2,3,4)
就可以得出最大值和最小值。
加入购物车进行计算涉及该方法
Math.ceil()执行向上舍入,即它总是将数值向上舍入为接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为接近的整数;
对象在 JavaScript 中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象, 现简要总结如下:
引用类型与传统面向对象程序设计中的类相似,但实现不同;
Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为;
Array 类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;
Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
RegExp 类型是 ECMAScript支持正则表达式的一个接口,提供了基本的和一些高级的正则表 达式功能。 函数实际上是 Function 类型的实例,因此函数也是对象;而这一点正是 JavaScript有特色的地 方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。 因为有了基本包装类型,所以 JavaScript 中的基本类型值可以被当作对象来访问。三种基本包装类 型分别是:Boolean、Number 和 String。以下是它们共同的特征:
每个包装类型都映射到同名的基本类型;
在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据 操作;
操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。 在所有代码执行之前,作用域中就已经存在两个内置对象:Global 和 Math。在大多数ECMAScript 实现中都不能直接访问 Global 对象;不过,Web 浏览器实现了承担该角色的 window 对象。全局变 量和函数都是 Globa
Math.round()执行标准舍入,即它总是将数值四舍五入为接近的整数(这也是我们在数学课 上学到的舍入规则)。
继续昨天没总结完了,该篇是昨天和今天的内容补充。