引用类型类似于Java中的类,但和传统的面向对象语言所支持的类和接口大相径庭,本章介绍ECMAScript提供的原生引用类型。
Object类型
引用类型的值一般均被认为是Object类型的实例,Object类型也是ECMAScript中使用最多的类型。
创建Object实例的方法有两种,第一种是使用new操作符后跟Object构造函数:
var person = new Object(); person.name = "XXX"; person.age="22";
另一种方式是使用对象字面量表示法:
var person = { name: "XXX", age: 22 };
对参数的访问可以使用点表示法,也可以是用方括号表示法。
var name = person.name; var age = person["age"];
使用方括号的优点是方括号中的字符串可以用变量表示。
一般使用点表示法更直观一些。
hivar value = numbers.reduce(function(prev, cur, index, array){ var res = prev + cur; console.log(prev + " + " + cur + " = " + res); return res; }); console.log("result = " + value); /** 1 + 2 = 3 3 + 3 = 6 6 + 4 = 10 10 + 5 = 15 15 + 6 = 21 21 + 7 = 28 result = 28 */
Array类型
创建数组的基本方式也有两种。第一种是使用Array构造函数:
var colors = new Array(); //创建一个数组var colors = new Array(3); //创建一个包含三个元素的数组
也可以想Array构造函数传递应该包含的项目
var colors = new Array("red", "blue", "green"); //创建一个包含三个元素的数组
以上两段代码中的new操作符可以省略。
第二种方法是使用数组字面量表示法。
var colors = ["red", "blue", "green"]; //创建一个包含三个字符串的数组 var names = []; //创建一个空数组 var values =[1, 2, ]; //不要这样,这样会差ungjianyige2或3个元素的数组 var options = [,,,,]; //不要这样,这样会创建一个5-6个元素的数组
数组的长度存放在数组对象的length属性中。
var colors = ["red", "blue", "green"]; console.log(colors.length); //3 colors[99] ="black"; console.log(colors.length); //100
检测一个数组对象是不是数组类型,可以使用instanceof操作符,也可以使用Array.isArray(value)方法。
Array的toString和toLocalString方法,首先调用各个元素的toString和toLocalString方法,然后用逗号拼接成字符串返回。
使用join方法可以使用其他字符串拼接。
var colors = ["red", "blue", "green"];console.log(colors.join("||")); //red||blue||greenconsole.log(colors.join()); //red,blue,green
Array还提供了栈和队列使用的方法。
栈使用push和pop方法,只在数组末尾进行操作。
队列使用push和shift方法,在队尾进队头出。队列还提供了unshift方法,在对头推入元素。
数组的反序使用reverse方法,将元素倒序排列。
默认情况是数组排序都是先调用各个元素的toString方法,然后对字符串进行排序。这样排列数值数组就会出现问题:
var array = [1,2,4,13,25]; var temp = array.sort(); console.log(array); //[ 1, 13, 2, 25, 4 ] console.log(temp); //[ 1, 13, 2, 25, 4 ]
sort方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值前面。
var array = [1,2,4,13,25]; array.sort(compare); console.log(array); //[ 1, 2, 4, 13, 25 ] function compare(v1, v2){ return v1 - v2; }
concat方法将参数加入到当前数组,并返回增加后的数组。当前数组内容不变。
var array = [1,2,4,13,25]; var a2 = array.concat([9,4,[6,7,1]]); console.log(a2.length); //8 console.log(array.length); //5 console.log(a2); //[ 1, 2, 4, 13, 25, 9, 4, [ 6, 7, 1 ] ]
slice()方法接收一个或两个参数,返回子数组,第一个参数表示开始位置,第二个参数表示结束位置。 slice方法不影响原数组。
var array = [1,2,4,13,25]; var subArray = array.slice(2, 4); var subArray2 = array.slice(2); console.log(subArray); //[ 4, 13 ] console.log(subArray2); //[ 4, 13, 25 ]
splice()方法,可以用来删除、插入、替换。删除时,接收两个参数,第一个参数表示要删除的位置,第二个参数表示要删除的项数;插入时接收三个参数:起始位置、要删除的项数(0)和要插入的项。
var colors = ["red", "blue", "green"]; var removed = colors.splice(0, 1); console.log(colors); //[ ‘blue‘, ‘green‘ ] console.log(removed); //[ ‘red‘ ] removed = colors.splice(1, 0, "yellow", "gray"); console.log(colors); //[ ‘blue‘, ‘yellow‘, ‘gray‘, ‘green‘ ] console.log(removed); //[] removed = colors.splice(1, 1, "red", "pink"); console.log(colors); //[ ‘blue‘, ‘red‘, ‘pink‘, ‘gray‘, ‘green‘ ] console.log(removed); //[ ‘yellow‘ ]
indexOf()方法和ilastIndexOf()方法,都接收两个参数:要查找的项和表示查找起点位置的索引(可选)。indexOf从头开始查找,lastIndexOf从末尾开始向前查找。
这两个方法返回要查找的项在数组中的位置,没有找到的情况下返回-1。比较过程中使用的是全等操作符(===)。
数组的迭代方法有:
- every():对数组中每一项运行给定的函数,如果该函数对每一项都返回true,则返回true。
- some():对数组中每一项运行给定的函数,如果该函数对任何一项返回true,则返回true。
- filter():对数组中每一项运行给定函数,返回该函数会返回true的项组成的数组。
- forEach():对数组中每一项运行给定的函数,这个函数没有返回值。
- map():对数组中每一项运行给定函数,返回每次函数调用的结果组成的数组。
var numbers = [1, 2, 3, 4, 5, 6, 7]; var everyResult = numbers.every(function(item, index, array){ return (item > 2); }); console.log(everyResult); //false var someResult = numbers.some(function(item, index, array){ return (item > 2); }); console.log(someResult); //true
var numbers = [1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 1, 2]; var filterResult = numbers.filter(function(item, index, array){ return (item > 2); }); console.log(filterResult); //[ 3, 4, 5, 6, 7, 7, 6, 5, 4 ]
var numbers = [1, 2, 3, 4, 5, 6, 7]; var mapResult = numbers.map(function(item, index, array){ return (item * 2); }); console.log(mapResult); //[ 2, 4, 6, 8, 10, 12, 14 ]
ECMAScript5还提供了两个归并数组的方法:reduce()和reduceRight()函数,这两个函数都会迭代数组中所有的项,然后构建一个最终返回的值。其中reduce()从左向右逐个遍历,而reduceRight()从右向左逐个遍历。
两个方法都接收两个参数:一个在每一项上调用的函数(四个参数:前一个值、当前值、项的索引和数组对象),另一个是作为归并基础的初始值(可选)。
函数返回的值会作为第一个参数自动传给下一个函数执行,第一次执行的时候,前一个值(第一个参数)是数组第一个元素,当前值(第二个参数)是数组第二个元素。
var numbers = [1, 2, 3, 4, 5, 6, 7]; var value = numbers.reduce(function(prev, cur, index, array){ var res = prev + cur; console.log(prev + " + " + cur + " = " + res); return res; }); console.log("result = " + value); /** 1 + 2 = 3 3 + 3 = 6 6 + 4 = 10 10 + 5 = 15 15 + 6 = 21 21 + 7 = 28 result = 28 */
Date类型
ECMAScript中的Date类型是在早期Java中的java.util.Date类型的基础上构建的。为此,Date类型使用自UTC 1970年1月1日0时开始经过的毫秒数来保存日期。Date类型保存的日期能够精确到1970年1月1日之前或之后的285616年。
创建日期对象可以使用new操作符,也可以用Date.now()返回当前的毫秒数(一般用于判断一段代码的执行时间,startTime endTime)。
var date = Date.now(); var date1 = new Date(Date.parse("2016-01-06")); var date2 = new Date(); console.log(date); //1452065871699 console.log(date1); //Wed Jan 06 2016 08:00:00 GMT+0800 (CST) (因为是东8时区所以是8点) console.log(date2); //Wed Jan 06 2016 15:37:51 GMT+0800 (CST)
RegExp类型
ECMAScript通过RegExp类型来支持正则表达式。创建正则表达式也有两种方法,第一种是字面量表达式 var expression = /pattern/ flags; 其中,flag可取的值有三个:
- g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
- i:表示不区分大小写(case-insensitive)模式,即在确定匹配项是忽略模式与字符串的大小写;
- m:表示多行(multiline)模式,即在到达一行文本末尾时,还会继续查找下一行中是否存在与模式匹配的项。
模式中所有的元字符必须转义。需要转义的元字符有: ()[]{}\^$|?*+.
第二种创建正则表达式的方法是使用new操作符 var expression = new RegExp(pattern, flag); 。
var re = null, temp = null, i; for(i=0; i <10; i++){ re = /cat/g; temp = re.test("catastrophe"); console.log(temp); //true } temp = re.test("catophetast"); console.log(temp); //false for(i=0; i<10; i++){ re = new RegExp("cat", "g"); temp = re.test("catophetast"); console.log(temp); //true } temp = re.test("catophetast"); console.log(temp); //false
RegExp有下列属性,用于判断模式的信息。(这些属性实际上没什么用,因为你声明的时候已经设置了这些信息)
- global:布尔值,表示是否设置了g标志。
- ignoreCase:布尔值,表示是否设置了i标志。
- multiline:布尔值,表示是否设置了m标志。
- lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0开始算。
- source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
RegExp实例方法中主要的方法是exec(),该方法是专门为捕获组而设计的。exec()接收一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配性的时候返回null。
返回的虽然是Array实例,但包含两个额外的属性:index和input。其中index表示匹配项在字符串中的位置,而input表示应用正则表达式的字符串。
在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组中只包含一项)。
var text = "grandpa and mom and dad and baby"; var pattern = /mom( and dad( and baby)?)?/gi; var matches = pattern.exec(text); console.log(matches.index); //12 console.log(matches.input); //grandpa and mom and dad and baby console.log(matches); /* [ ‘mom and dad and baby‘, ‘ and dad and baby‘, ‘ and baby‘, index: 12, input: ‘grandpa and mom and dad and baby‘ ] */
RegExp中第二个方法是test(),它接收一个字符串参数,在模式与该参数匹配的情况下返回true,否则返回false。一般用于if语句。
var text = "010-6812-0917"; var pattern = /\d{3}-\d{4}/ if(pattern.test(text)){ console.log("The pattern was matched."); //The pattern was matched. }
Function类型
ECMAScript中的函数实际上是对象类型的实例,因此函数名实际上一个指向函数对象的指针,不会与某个函数绑定。因此下面两个函数声明的方式很类似。
function sum(num1, num2){ //函数声明 return num1 + num2; } var sum = function(num1, num2){ //函数表达式 return num1 + num2; }
区别在于解析器会率先读取函数声明,并使其在任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被执行。
console.log(sum(5, 6)); //11 function sum(num1, num2){ return num1 + num2; }
console.log(sum(5, 6)); //11 var sum = function(num1, num2){ return num1 + num2; } /* TypeError: undefined is not a function at Object.<anonymous> (/home/zzl/Study/nodejs/studyjs/functionjs.js:1:75) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Function.Module.runMain (module.js:497:10) at startup (node.js:119:16) at node.js:902:3 */
另外,定义函数还有另一种方法: var sum = new Function("num1", "num2", "return num1 + num2"); 。这种语法对于理解“函数是对象,函数名是指针”的概念倒是非常直观,但是不推荐使用这种方式,因为解析器需要解析传入构造函数中的字符串,最后一个字符串被认为是函数体,前面的字符串都被认为是参数。
理解函数名是变量对与js至关重要,因为是变量,所以可以作为其他函数的参数或者返回值返回。
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 array = [{name:"zhou", age:18},{name:"liu", age:28}]; array.sort(createComparisonFunction("name")); console.log(array[0].name); //liu array.sort(createComparisonFunction("age")); console.log(array[0].name); //zhou
函数作为一个Function的类型,包含两个特殊的对象:arguments 和 this。
arguments是一个类数组的对象,包含着所有传入函数的参数。arguments 还有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。该指针对于递归调用尤其重要。
function factorial(num){ if(num <= 1){ return 1; }else{ //return num * factorial(num-1); return num * arguments.callee(num-1); } } temp = factorial; factorial = null; console.log(temp(4)); //24
函数内部另一个特殊对象是this,其行为和Java大致相同。也就是说,this引用的是函数据以执行的环境对象。
ECMAScript 5也规范化了另一个函数对象的属性:caller。该属性保存者调用当前函数的函数引用,如果是在全局作用域中调用当前函数,它的值返回null。在caller后加括号就相当于调用了outer函数,立刻会引起栈溢出。
function outer(){ inner(); } function inner(){ console.log(inner.caller; //[Function: outer] console.log(arguments.callee.caller); //[Function: outer] } outer();
在严格模式下,caller 和 callee 都是不允许的,因为侵犯到了别的函数的空间。
ECMAScript中函数对象还有length 和 prototype 属性,其中length属性表示函数希望接收的函数参数的个数。
对于引用类型而言,prototype属性是保存他们说有实例方法的所在。也就是说,对象的 toString() valueOf()等函数,实际上是保存在prototype名下的,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype属性的作用是极为重要的。
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组(可以是Array类型的实例,也可以是arguments对象)。才`
function sayHello(name){ console.log("Hello " + name + ". Your age is " + this.age); } var o = {age:28}; global.age = 15; console.log(this.age); //undefined console.log(age); //15 sayHello.call(this, "LiLei"); //Hello LiLei. Your age is undefined sayHello.call(o, "LiLy"); //Hello LiLy. Your age is 28 sayHello.apply(global, ["Polly"]); //Hello Polly. Your age is 15 sayHello.apply(o, ["Tom"]); //Hello Tom. Your age is 28
从上面的代码里可以得出,在nodejs中在全局函数中传this并不是传递的global(类似于浏览器中的window对象)所以函数中调用this.age为undefined。
ECMAScript 5中还定义了bind()方法 用于绑定作用域。
function sayHello(name){ console.log("Hello " + name + ". Your age is " + this.age); } var o = {age:28}; global.age = 15; var sayHelloToObject = sayHello.bind(o); sayHelloToObject("HanMeimei"); //Hello HanMeimei. Your age is 28 sayHello.bind(o)("HanMeimei2"); //Hello HanMeimei2. Your age is 28
基本包装类型
基本包装类型包括:Boolean、Number、String
每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。过程如下:
- 创建String类型的一个实例;
- 在实例上调用指定的方法;
- 销毁这个实例。
引用类型与基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。
而自动创建的基本包装类型的对象,则之存在与一行代码的执行瞬间,然后立即被销毁。
Object构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。
var obj = new Object("lalala"); console.log(obj instanceof String); //true var str = "hello"; var strObj = new String("lilili"); var tempStrObj = String(str); console.log(typeof tempStrObj); //string console.log(typeof strObj); //object
一般来说,不建议显示地创建基本包装类型的对象,在基本数据类型的变量上可以直接调用包装类型的方法。
Boolean类型 是与布尔值对应的引用类型。但是作为Boolean类型的对象,是用Object方式来判断真假的。
var booleanObje = new Boolean(false); if(booleanObje){ console.log("true"); }else{ console.log("false"); }
上面代码返回的true,所以建议永远不要使用Boolean对象。
Number类型 是与数字值对应的引用类型。
toFixed()方法会按照制定的小数位返回数值的字符串表示。按照四舍五入保留到小数点后某位。
var num = 5.12521212; console.log(num.toFixed(2)); //5.13
toExponential()方法返回以指数表示法表示的数值字符串形式。
var num = 123456.78 console.log(num.toExponential(2)); //1.23e+5
toPrecision()方法可以根据数值的大小判断返回固定大小的格式还是科学表示法的形式的字符串。
var num = 99; console.log(num.toPrecision(1)); //1e+2 console.log(num.toPrecision(2)); //99 console.log(num.toPrecision(3)); //99.0
String类型 是字符串的对象包装类型。
String类型的每一个实例都包含一个length属性,表示字符串中包含了多少个字符。
concat()方法用于将多个字符串拼接起来,返回拼接以后的新字符串,和字符串的(+)操作符一样。
substr()、substring()、slice()三个函数都是用来返回子字符串的,后两个方法是一样的,substr()方法如果有第二个参数,则表示子字符串的长度,其他两个的第二个参数表示结束位置后面的位置。
var str = "helloworld!"; console.log(str.substr(3)); //loworld! console.log(str.substring(3)); //loworld! console.log(str.slice(3)); //loworld! console.log(str.substr(3,5)); //lowor console.log(str.substring(3,5)); //lo console.log(str.slice(3,5)); //lo
模式匹配方法,match()方法(同RegExp的exec()方法),match()方法只接收一个参数,要么是个正则表达式,要么是个RegExp对象。
var text = "grandpa and mom and dad and baby"; var pattern = /mom( and dad( and baby)?)?/gi; var matches1 = text.match(pattern) var matches = pattern.exec(text); console.log(matches.index); //12 console.log(matches.input); //grandpa and mom and dad and baby console.log(matches); /* [ ‘mom and dad and baby‘, ‘ and dad and baby‘, ‘ and baby‘, index: 12, input: ‘grandpa and mom and dad and baby‘ ] */ console.log("==============================================="); console.log(matches1.index); //undefined console.log(matches1.input); //undefined console.log(matches1); //[ ‘mom and dad and baby‘ ]
由上面的代码可以看出,match 和 exec 方法返回的值并不一致,exec 方法返回的信息更详尽并且有捕获组,而match没有捕获组的内容。
另一个查找模式的方法是search()。参数也是正则表达式或RegExp对象,返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1.
var text = "cat, bat, sat, fat"; var reg = /at/g; var res; res = text.search(reg); console.log(res); //1 res = text.search(reg); console.log(res); //1
可见search 方法始终从字符串开头向后查找,即使指定了g也是如此。
replace()方法,用于简化替换子字符的操作,接受两个参数,第一个参数可以是一个RegExp 对象或者一个字符串,第二个参数可以是一个字符串或者函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要制定全局(g)标志。
var text = "cat, bat, sat, fat"; var result; result = text.replace(/.at/, "word"); console.log(result); //word, bat, sat, fat result = text.replace(/.at/g, "word"); console.log(result); //word, word, word, word
第二个参数是函数时,函数接受三个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串。
function htmlEscape(text){ return text.replace(/[<>"&]/g, function(match, pos, originalText){ switch(match){ case "<": return "<"; case ">": return ">"; case "&": return "&"; case "\"": return """; } }); } //<p class="test">Hello World!</p> console.log(htmlEscape("<p class=\"test\">Hello World!</p>"));
另外还有split()、indexOf()、trim()、charAt()等方法同Java,就不一一列举了。
单体内置对象
Global对象,是ECMAScript中是作为一个终极的“兜底儿对象”,不属于任何其他对象的属性和方法,最终都是他的属性和方法,所有在全局作用于中定义的属性和函数,都是Global对象的属性。比如isNaN()、isFinite()、parseInt()、parseFloat()实际上都是Global 对象的方法。
encodeURI() 和 encodeURIComponent() 方法可以对URI进行编码,以便发送给浏览器。有效的URI中不可以包含某些字符,例如空格。这两个方法可以就URI进行编码,他们用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解。
var uri = "http://www.baidu.com/illegal value.html#start"; console.log(encodeURI(uri)); //http://www.baidu.com/illegal%20value.html#start var temp = encodeURIComponent(uri); console.log(temp); //http%3A%2F%2Fwww.baidu.com%2Fillegal%20value.html%23start console.log(decodeURI(temp)); //http%3A%2F%2Fwww.baidu.com%2Fillegal value.html%23start console.log(decodeURIComponent(temp)); //http://www.baidu.com/illegal value.html#start
对应的解码的方法是decodeURI()、decodeURIComponent()。
eval()方法接受一个字符串,即要执行的ECMAScript字符串。
eval(console.log("hello")); //hello
ECMAScript没有指出如何直接访问Global对象,但是Web浏览器都是将这个全局对象作为window对象的一部分加以实现的。因此在全局作用于中生命的所有变量和函数,都成为window对象的属性。
Math对象,min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都可以接受任意多个数值参数。
舍入方法:Math.ceil()向上舍入、Math.floor()向下舍入、Math.round()四舍五入。
random()方法返回大于0小于1的一个随机数。
小结
- 引用类型与传统面向对象程序设计中的类相似,但实现不同;
- Object 是一个基础类型,其他所有类型都从Object 继承了基本的行为;
- Array 类型是一组值的有序列表,同事还提供了操作和转换这些值的功能;
- Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
- RegExp 类型是ECMAScript支持正则表达式的一个借口,提供了最基本的和一些高级的正则表达式功能。