js中中引用类型一共有以下的几种:
- Object
- Array
- Date
- RegExp
- Function
- 基本类型的包装类
- 单体内置对象
使用对象字面量传递参数的模式最适合需要向函数传入大量可选参数的情形。一般来讲,命名参数虽然容易处理,但在有多个可选参数的情况下就会显示不够灵活。最好的做法是对那些必需值使用命名参数,而使用对象字面量来封装多个可选参数。
Array类型数组的检测
如何判断某个对象是不是数组是一个经典问题.于一个网页,或者一个全局作用域而言,使用instanceof
操作符就能得到满意的结果:
if (value instanceof Array){
//对数组执行某些操作
}
除此之外ES5新增了Array.isArray()
方法.
使用数组模拟栈和队列
LIFO:push(),pop()
FIFO:push(),shift()
除此之外数组还提供了方便在前面进行添加元素的unshift()
方法.基于这个方法我们还可以模拟反向队列(即在对头插入,队尾删除),也就是unshift()
和pop()
.
重排序
注意sort()
方法比较的依据是数组中每个元素的toString()
后得到的值(即使数组中的每个元素都是数值比较的依然是字符串).参看如下的例子:
var arr = [0, 1, 5, 10, 15];
arr.sort(); // [ 0, 1, 10, 15, 5 ]
以上的比较并不是我们需要的结果,因此sort()`方法可以接收一个比较函数作为参数.
function compare(value1, value2) {
return value1 - value2;
}
var values = [0, 1, 5, 10, 15];
values.sort(compare); // [ 0, 1, 5, 10, 15 ]
以上函数可以适用于大多数情况,如果我们需要逆序只需要颠倒比较函数中的两个参数即可.
除了sort()
函数,js也提供了方便的reverse()
函数用于反转数组.
日期的比较
由于Date对象的valueOf()
方法返回的是毫秒数,所以我们可以直接进行日期的比较:
var d1 = new Date(2016, 1, 28);
var d2 = new Date(2016, 1, 30);
d1 > d2; // false
RegExp
exec
该方法是用来捕获组的.
pattern.exec(str); // pattern为正则,str为需要应用模式的字符串
返回值是包含第一个匹配项的数组或者null
.返回的数组除了是Array
的实例,还包含2个额外的属性index
(匹配项在字符串中的位置)和input
(应用正则的字符串).在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)
var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
console.log(matches);
/*
[ 'mom and dad and baby',
' and dad and baby',
' and baby',
index: 0,
input: 'mom and dad and baby' ]
*/
这个例子中的模式包含两个捕获组。最内部的捕获组匹配 "and baby" ,而包含它的捕获组匹配"anddad" 或者 "and dad and baby" 。当把字符串传入 exec() 方法中之后,发现了一个匹配项。因为整个字符串本身与模式匹配,所以返回的数组 matchs 的 index 属性值为 0。数组中的第一项是匹配的整个字符串,第二项包含与第一个捕获组匹配的内容,第三项包含与第二个捕获组匹配的内容。
对于该方法而言,即使在模式串上设置了全局标志(g),它每次也只会返回一个匹配项.在不设置全局标志的情况下,在同一个字符串上多次调用exec()
将始终返回第一个匹配项的信息.而在设置全局标志的情况下,每次调用exec()
则会在字符串中继续查找新匹配项.
var text = "cat, bat, sat, fat";
var pattern1 = /.at/;
var matches = pattern1.exec(text);
console.log(matches.index); //0
console.log(matches[0]); //cat
console.log(pattern1.lastIndex); //0
matches = pattern1.exec(text);
console.log(matches.index); //0
console.log(matches[0]); //cat
console.log(pattern1.lastIndex); //0
var pattern2 = /.at/g;
var matches = pattern2.exec(text);
console.log(matches.index); //0
console.log(matches[0]); //cat
console.log(pattern2.lastIndex); //3
matches = pattern2.exec(text);
console.log(matches.index); //5
console.log(matches[0]); //bat
Function第一个模式 pattern1 不是全局模式,因此每次调用 exec() 返回的都是第一个匹配项( "cat" )。而第二个模式 pattern2 是全局模式,因此每次调用 exec()都会返回字符串中的下一个匹配项,直至搜索到字符串末尾为止。此外,还应该注意模式的 lastIndex 属性的变化情况。在全局匹配模式下,lastIndex 的值在每次调用 exec() 后都会增加,而在非全局模式下则始终保持不变。
函数实际上是对象,每个函数都是Function
类型的实例,并且和其他引用类型一样存在属性和方法.函数名实际上是指向函数对象的指针,不会和某个函数绑定.以下的例子虽然并不推荐使用,但是对于理解函数是对象,函数名是指向函数对象的指针非常有用:
var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
console.log(sum(1, 2)); // 3
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字:
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); //20
var anotherSum = sum;
console.log(anotherSum(10, 10)); //20
sum = null;
console.log(anotherSum(10, 10)); //20
console.log(sum(10,10)); // TypeError
知道了函数名实际上是指向函数的指针对于我们理解为什么js中没有重载有很大帮助:
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
函数内部属性
arguemnts
对象的主要用途是保存函数参数,但是它还有一个名叫callee
的属性,它是一个指针,指向拥有这个arguments
对象的函数.请看以下的非常经典的阶乘函数:
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
// 以上的代码中函数的执行与函数名 factorial 紧密耦合,可通过arguments.callee解耦
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1)
}
}
var anotherFunction = factorial;
factorial = function() {
return false;
};
console.log(anotherFunction(5)); // 120
console.log(factorial(5)); // false
函数对象的另外一个属性是caller
,这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null 。
function outer() {
inner();
}
function inner() {
console.log(inner.caller); // [Function: outer]
// 为了实现更松散的耦合,可以采用arguments.callee.caller
}
outer();
因为 outer() 调用了 inter() ,所以inner.caller 就指向 outer().
值得注意的是,严格模式下caller
和calee
将报错,加入这两个属性是用来加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
函数的属性和方法
函数有length
和prototype
属性.length指出了命名参数的个数.
function fun1() {};
function fun2(arg) {};
function fun3(arg1, arg2) {};
console.log(fun1.length, fun2.length, fun3.length); // 0 1 2
对于引用类型来讲,prototype
属性保存了所有的实例方法,该属性是不可枚举的,无法使用for-in
遍历.
Object 构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。
var obj = new Object("some text");
console.log(obj instanceof String); // true
把字符串传给
Object
构造函数,就会创建String
的实例;而传入数值参数会得到Number
的实例,传入布尔值参数就会得到Boolean
的实例。
Number中有用的方法:
- num.toString(radix).得到数字的特定进制
- num.toFixed(scale).使用特定的小数位格式化数字,具有自动舍入的功能.
- num.toExponential(scale).科学技术法
String中replace
方法的妙用,replace方法的第二个参数可以是一个函数:在只有一个匹配项(即与模式匹配的字符串)的情况下,会向这个函数传递 3 个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串。在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配......,但最后两个参数仍然分别是模式的匹配项在字符串中的位置和原始字符串。这个函数应该返回一个字符串,表示应该被替换的匹配项使用函数作为 replace() 方法的第二个参数可以实现更加精细的替换操作,请看下面这个例子。
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function(match, pos, originalText) {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
// <p class="greeting">Hello world!</p>