JS学习4(引用类型)
引用类型是一种数据结构,用于将数据和功能组织在一起。对象是某个特定引用类型的实例。新对象使用new操作符后面跟一个构造函数来创建,构造函数本身就是一个函数,只不过这个函数是出于创建新对象而定义的。
var person = new Object();
这里调用的构造函数是Object(),只为新对象定义了默认的属性和方法。
Object类型
这是ECMAScript中使用的最多的类型,大多数引用类型的实例都是Object类型的实例。
初始化
创建它有两种办法:
//方法1
var person = new Object();
person.name = "Nicholas";
person.age = 29;
//方法2
var person = {
name : "Nicholas",
age : 29
};
//这样也可以~
var person = {}; // new Object()
person.name = "Nicholas";
person.age = 29;
在方法2中注意,属性值是字符串,如果你输入的不是字符串,那也会被转化成字符串。
要注意的是使用字面量来定义对象的时候实际上不会调用Object()构造函数哦
在需要像函数传递大量可选参数的情况下,使用对象字面量来传递是个很好的选择。
访问
有两种方法来访问:
alert(person["name"]); //"Nicholas"
alert(person.name); //"Nicholas"
一般推荐第二种,但是第一种有特殊的用法:
var propertyName = "name"; //需要通过变量来访问属性
alert(person[propertyName]); //"Nicholas"
//属性名里包含非字母非数字等用在.方法里会出错的字符
person["first name"] = "Nicholas";
Array类型
ECMAScript中的数组也与其它语言的数组有很大区别,数组中的每一项都可以保存任何类型的数据,大小也是动态调整的。
初始化
同样的,使用数组字面量初始化数组时不会调用Array的构造函数。
var colors = new Array();
var colors = new Array(20);
var colors = new Array("red", "blue", "green");
var names = new Array("Greg");
var colors = ["red", "blue", "green"];
var names = [];
访问
使用方括号下标的方式来访问。
在别的语言中会有数组越界,在JS里,越界访问返回undefined,越界赋值则会帮你创建这个元素,如果数组原来只有3个元素,你直接设置第100个,数组长度也会变为100,前面那些没赋值的都会变成undefined,虽然这样不报错,但还是不推荐。
length属性始终会返回0或更大的值,而且有趣的是,这个属性并不是只读的,还可以设置长度,通过设置长度就可以移除不要的或添加新项。
这样的特性造成了在最后添加元素很方便:
colors[colors.length] = "black";
检测数组
ECMAScript3中使用value instanceof Array来判断一个对象是不是数组。但是当一个网页中包含多个框架时,就有可能包含多个全局执行环境,包含多个Array的构造函数,在框架之间传递数组时这样判断就会出问题。ECMAScript5为了解决这个问题创建了isArray()方法。
转换方法
alert(colors.toString()); //对数组调用toString时会对数组中的每一个元素调用toString,然后加上逗号拼成一个字符串
alert(colors.toLocaleString());//对数组调用这个方法和调用toString()方法类似,会先调用每一项的toLocaleString()方法
alert(colors.valueOf());//valueOf方法则还是返回数组
alert(colors);//直接将数组传到alert里其实是默认调用了toString再显示出来
alert(colors.join("||")); //这个可以自定义字符串的分隔符,默认使用逗号。red||green||blue
栈方法
使数组模仿栈操作,有push()和pop()方法。push接受任意数量的参数将它们逐个添加到数组的末尾,并返回新的数组长度。pop移除最后一个元素并返回该元素。
队列方法
模仿队列操作,使用push(),shift()方法模仿。shift()方法移除第一项并返回这个元素。
还可以使用pop()和unshift()方法来模拟,unshift是在数组的最前端加上任意个项并返回新的数组长度。
这两种方法模拟队列的方向不一样,我更喜欢第一组。
重排序方法
reverse()方法会直接反转数组。
sort()方法会调用数组中每一项的toString方法,然后根据转换得来的字符串按照升序排列数组。也就是说[0,1,2,10]会被排成[0,1,10,2]。这显然不是我们想要的
sort可以接受一个参数,这个参数是一个比较函数,这个比较函数接受两个参数,如果第一个应该在第二个前面,则返回一个负数,相等返回0,否则返回正数。
function compare(value1, value2) {
if (value1 < value2) {
return -1;
}
else if (value1 > value2) {
return 1;
}
else {
return 0;
}
}
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15
对于数值类型或者valueOf()方法会返回数值的直接用减法代替咯:
function compare(value1, value2){
return value2 - value1;
}
一些方法
concat()
var colors = ["red", "green", "blue"];
var colors2 = colors.concat("yellow", ["black", "brown"]);
alert(colors); //red,green,blue
alert(colors2); //red,green,blue,yellow,black,brown
slice()
var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);
alert(colors2); //green,blue,yellow,purple
alert(colors3); //green,blue,yellow
splice()
var colors = ["red", "green", "blue"];
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red 被删除的项
removed = colors.splice(1, 0, "yellow", "orange"); // 在1的位置插入2项
alert(colors); // green,yellow,orange,blue
alert(removed); // 空的,因为没删除什么
removed = colors.splice(1, 1, "red", "purple"); // 删除1项,插入两项
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow 被删除的项
位置方法
indexOf和lastIndexOf方法用来查找元素在数组中的位置。接收两个参数,起始索引和要查找的元素,这两个的区别就是一个从前开始找一个从后开始找。他们只会返回他们找到的第一个元素的索引。在查找时使用===全等符号来比较。如果找不到则返回-1。
var person = { name: "Nicholas" }; var people = [{ name: "Nicholas" }];
var morePeople = [person];
alert(people.indexOf(person)); //-1 这里两个对象不是一个,并不全等alert(morePeople.indexOf(person)); //0
迭代方法
在ES5中为数组定义了5个迭代方法,这些方法都接收两个参数:要在数组每一项上运行的函数和运行该函数的作用域对象(这个对象会影响函数内this的值)。
对于作为参数被传进去的函数,它会接收到3个参数:数组项的值,数组项的索引,和数组本身。返回值每个函数不一样。
every()
如果函数对每一项都返回true,则every返回true。这个一般用来检测数组的每一项是否满足某个要求。
some()
如果函数对某一项返回true,这个函数就返回true
filter()
它返回函数返回true的项组成的数组
map()
返回在数组原始项上运行传入函数的结果返回的数组
foreach()
木有返回值
归并方法
这也是ES5里的方法:reduce()和reduceRight()。这两个方法一个从前一个从后,迭代数组的所有项,最终返回一个值。他们都接受两个参数:在每一项上调用的函数和初始值(可选)。函数接收4个参数:前一个值,当前项,项的索引和数组对象。且这个函数返回的任何值都会自动的作为下一项的第一个参数。
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){ return prev + cur;
});
alert(sum); //15
Date类型
var now = new Date(); //当前时间
var someDate = new Date(Date.parse("May 25, 2004")); //这个字符串格式没有标准,有可能有的浏览器不支持呢
var someDate = new Date("May 25, 2004"); //这个其实会自动调用parse
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55)); //这个比较保险
var start = Date.now(); //获取当前毫秒数,用来分析代码很有用
Date类型重写了toString(),toLocaleString(),valueOf()方法。前两个每个浏览器实现都不太一样,所以在真正用的时候也不太可能用这些方法将值呈现给用户。valueOf方法则是将时间转换为毫秒值,这个就比较有用了,这就意味着可以直接使用><来判断时间的先后。
var date1 = new Date(2007, 0, 1); //"January 1, 2007"
var date2 = new Date(2007, 1, 1); //"February 1, 2007"
alert(date1 < date2); //true
alert(date1 > date2); //false
那么如果想获得准确的时间信息并显示给用户我们应该怎么办呢
getFullYear()
getMonth()
getDate()
等等。。。
也有设置的方法
setFullYear()
setMonth()
等等
RegExp类型
正则表达式
var expression = / pattern / flags ;
其中pattern可以是任何简单或复杂的正则表达式,flags是标志,标志可以带多个。标志有3个:g表示全局模式,即模式会被应用到所有字符串,不会在找到第一个后就停止;i表示区分大小写;m表示多行,在到达一行文本末尾时会查找下一行。
var pattern1 = /at/g; //匹配字符串中所有at的实例
var pattern2 = /[bc]at/i; //匹配第一个bat或cat,不区分大小写
var pattern2 = new RegExp("[bc]at", "i"); //这样也是可以的
var pattern3 = /.at/gi; //匹配所有以at结尾的3个字符的组合,不区分大小写
在ES3中,使用字面量创建正则表达式与使用new创建正则表达式不一样。使用字面量创建的正则表达式始终共享一个RegExp类型实例。这就有问题了:
var re = null;
var i;
for (i=0; i < 10; i++){
re = /cat/g;
re.test("catastrophe");
}
在上面这段代码中每次循环都使用的是同一个RegExp实例,在第一次查找的过程中,查到了索引为3的地方。在循环到第二次的时候,因为是同一个实例,实例属性不会被重置,于是第二次循环就从第4个字符开始查找了,这样当然就找不到了。
在ES5中,修复了这个不合理的地方。字面量也是直接调用构造函数那样新建一个实例。
实例属性
global:是否设置了g
ignoreCase:是否设置了i
lastIndex:开始搜索下一个匹配项的字符位置
multiline:是否设置m
source:正则表达式的字符串表示
实例方法
exec()专为捕获组设计:
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"
test()它接受一个字符串,在模式与该参数匹配的情况下返回true:
var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
alert("The pattern was matched.");
}
RegExp构造函数属性
这些属性基于所执行的最近的一次正则表达式操作而变化
var text = "this has been a short summer";
var pattern = /(.)hort/g;
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
}
还有9个用于储存捕获组的构造函数属性:RegExp.$1、...RegExp.$2
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类型
在ES里Function实际上是对象!所以每个函数都是Function类型的实例,与其他引用类型一样具有属性和方法,至于函数名,就是一个指向函数对象的指针。所以以下3种方法声明函数都是等价的:
//1
function sum (num1, num2) {
return num1 + num2;
}
//2
var sum = function(num1, num2){
return num1 + num2;
};
//3
var sum = new Function("num1", "num2", "return num1 + num2");
第三种方法可以很明显的看出函数是对象的迹象,但是平时使用的时候并不推荐使用第三种办法,会导致性能问题,因为要解析字符串里的表达式。
对于函数的名字,仅仅是指向函数的指针:
//首先定义了一个名为sum的函数
function sum(num1, num2){
return num1 + num2;
}
alert(sum(10,10)); //20
//这里不使用带圆括号的函数名,意思就是访问函数的指针而不是调用函数
//这里将sum的值赋给了anotherSum
var anotherSum = sum;
//于是anotherSum也指向了sum函数
alert(anotherSum(10,10)); //20
//将sum指向别处
sum = function (num1, num2){
return num1 - num2;
};
//anotherSum还指向原来的sum函数
alert(anotherSum(10,10)); //20
//sum指向新函数
alert(sum(10,10));//0
函数声明和函数表达式
函数声明会被解析器率先获取并放到其所在执行环境的顶部,代码开始执行后随时可用。而函数表达式得在代码执行到其所在的代码行时才被解释。
就这点区别。
这个可以正确执行:
alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}
这个则会报错:
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};
作为值的函数
函数可以作为参数传给另一个函数,也可以作为返回值。
//将一个函数作为参数,并将这个函数的返回值作为返回值
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
//调用时将函数的指针传入
var result1 = callSomeFunction(add10, 10);
alert(result1); //20
直接返回一个函数:
function createComparisonFunction(m) {
return function(o1, o2){
return m+o1+o2;
};
}
var func = createComparisonFunction(1)
alert(func(2,3));
alert(createComparisonFunction(1)(11,0));
函数内部属性
在函数内部有两个特殊对象arguments和this。arguments的主要用途是保存参数,不过他还有一个callee的属性,它是一个指针,指向拥有这个arguments对象的函数。于是就可以这样用:
//递归阶乘,使用callee自己调用自己就不受函数名的限制了
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1);
}
}
在ES5中还有caller属性,返回指向调用这个函数的函数的指针。在全局作用域中调用的函数这个属性是null
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
函数的属性和方法
每个函数都有2个属性:length和prototype。length表示希望接受到的命名参数的个数。prototype以后再细说。
每个函数都包含两个方法:apply()和call()。这两个方法在特定的作用域中调用函数,实际上就是手工设置函数体内this的值。
apply接受两个参数,第一个就是你要设置的作用域,第二个是函数本身要接受的参数,这个参数可以以数组的形式给出,也可以以arguments对象形式。call则是将参数直接列在上面:
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
function callSum1(num1, num2){
return sum.apply(this, arguments);
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);
}
alert(callSum(10,10)); //20
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
ES5新定义了一个方法bind(),这个方法创建一个函数的实例,这个函数的this值会被绑定到bind的参数上。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
基本包装类型
三个基本类型有自己的包装类,Boolean、Number、String。
var s1 = "some text";
var s2 = s1.substring(2);
是不是觉得很奇怪~s1明明是一个String类型的基本类型~怎么会有自己的方法~
在你从内存中读取这个字符串的时候其实发生了一些事情:
- 在你开始读取这个值的时候,ES为你创建了String类型的一个实例。
- 在实例上调用了你想调用的方法
- 销毁这个实例
相当于执行了下面的代码:
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
自动创建的包装类只存在那一瞬间,所以为他添加属性和方法不太可能。
像上面那样显示的声明也是可以的,但是并不推荐也没有必要。对包装类实例调用typeOf会返回object,值转换时永远为true。这点一定要注意。
Object构造函数会自动识别并创建响应的包装类实例:
var obj = new Object("some text");
alert(obj instanceof String); //true
还有一点要注意,包装类的构造函数和名字一样的转型函数要区分:
var value = "25";
var number = Number(value);
alert(typeof number); //"number"
var obj = new Number(value);
alert(typeof obj); //"object"
Boolean类型
重写了valueOf(),返回基本类型的true和false。重写了toString(),返回"true","false"。但是这个类型强烈不推荐使用,因为在转换时永远会被转换为true:
var falseObject = new Boolean(false);
var result = falseObject && true;
alert(result); //true
Number类型
var num = 10; alert(num.toString()); //"10" alert(num.toString(2)); //"1010" alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10" alert(num.toString(16)); //"a"
alert(num.toFixed(2)); //"10.00"
alert(num.toExponential(1)); //"1.0e+1"
var num = 99;
//这个方法会自动帮你选择合适的表示法
alert(num.toPrecision(1)); //"1e+2" alert(num.toPrecision(2)); //"99" alert(num.toPrecision(3)); //"99.0"
String类型
一些基本方法
var stringValue = "hello world"; alert(stringValue.length); //"11"
alert(stringValue.toUpperCase()); //"HELLO WORLD"
alert(stringValue.toLowerCase()); //"hello world"
alert(stringValue.charAt(1)); //"e"
alert(stringValue.charCodeAt(1)); //"101"
alert(stringValue[1]); //"e"
concat()用来拼接字符串,不过实际中使用+更加普遍:
var stringValue = "hello ";
var result = stringValue.concat("world", "!");
alert(result); //"hello world!"
alert(stringValue); //"hello"
还有三个返回子字符串的方法:slice()、substr()、substring()
查找子字符串的方法:indexOf()、lastIndexOf()。和数组那个差不多,可以指定查找的子字符串和起始位置。只会返回查找到的第一个位置。
trim()删除头尾的空格:
var stringValue = " hello world ";
var trimmedStringValue = stringValue.trim(); alert(trimmedStringValue); //"hello world"
匹配模式的方法:
match():与exec()相同,接收一个正则表达式或一个RegExp对象。返回数组。
search():与match参数相同。返回第一个匹配项的索引。
replace():第一个参数可以是一个字符串或一个正则表达式,第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,则只能替换搜索到的第一个字。
var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
alert(result); //"cond, bond, sond, fond"
replace()有一些进阶用法:
var text = "cat, bat, sat, fat";
//这里第二个参数是字符串的时候,有一些特殊的字符序列使你可以使用最近一次匹配结果中的内容$$ $& $' $` $n $nn
result = text.replace(/(.at)/g, "word ($1)");
alert(result); //word (cat), word (bat), word (sat), word (fat)
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match, pos, originalText){
switch(match){
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
alert(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
//<p class="greeting">Hello world!</p>
split()方法
var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"]
var colors2 = colorText.split(",", 2); //["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
localeCompare():按字母表顺序比较字符串
var stringValue = "yellow"; alert(stringValue.localeCompare("brick")); //1 alert(stringValue.localeCompare("yellow")); //0 alert(stringValue.localeCompare("zoo")); //-1
fromCharCode():
接收字符编码初始化字符串
单体内置对象
不依赖宿主环境的对象,比如Object,Array。还有两个Global和Math。
Global
终极兜底儿对象,不属于任何其他对象的属性和方法都是它的。实际上没有全局变量和全局函数,都是它的是它的。比如isNaN()等。
encodeURI()、encodeURIComponent():
这两个是用来对URI进行编码的:
var uri = "http://www.wrox.com/illegal value.htm#start";
//"http://www.wrox.com/illegal%20value.htm#start" alert(encodeURI(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start" alert(encodeURIComponent(uri));
可见encodeURIComponent()只能用来对部分的URI(比如参数部分)来编码。
与之对应的是encodeURI()、encodeURIComponent()。encodeURI()就不会解码%23,只会解码空格这样的。所以用的时候要注意对应。
eval()方法:
执行字符串里的代码
eval("function sayHi() { alert('hi'); }");
sayHi();
像undefined Date NaN RegExp Infinity Error Object EvalError Array RangeError Function ReferenceError Boolean SyntaxError String TypeError Number URIError 都是Global的属性哦。
在浏览器中,Global是作为window对象的一部分来实现的。
Math对象
属性们:Math.E、Math.LN10、Math.LN2、Math.LOG2E、Math.LOG10E、Math.PI、Math.SQRT1_2、Math.SQRT2
var max = Math.max(3, 54, 32, 16);
alert(max); //54
var min = Math.min(3, 54, 32, 16);
alert(min); //3
//配合apply()的使用技巧
var values = [1, 2, 3, 4, 5, 6, 7, 8];
var max = Math.max.apply(Math, values);
alert(Math.ceil(25.9)); //26 向上取整
alert(Math.round(25.9)); //26 四舍五入
alert(Math.floor(25.9)); //25 向下取整
Math.random()方法返回一个0-1的随机数
//1-10的随机整数
var num = Math.floor(Math.random() * 10 + 1);
还有一些方法就不提了正弦什么的。