深入理解 JavaScript 方法集的特性与最佳实践
JavaScript 包含了一套小型的、可用在标准类型上的方法集。下面我们对这些方法一一进行分析。
1 Array
1.1 array.concat(item...)
concat 方法会产生一个新数组,它是数组的浅复制,并把一个或多个 item 附加在其后。如果 item 是一个数组,那么它的每一个元素都会被添加:
var a = ['a', 'b', 'c'];
var b = ['x', 'y', 'z'];
var c = a.concat(b, true);
console.log(c);//[ "a", "b", "c", "x", "y", "z", true ]
1.2 array.join(separator)
join 方法会把一个 array 构造成一个字符串。它先把 array 中的每一个元素都构造成一个字符串,然后再用 separator 分隔符把它们连接起来。默认的 separator 是逗号 ','。如果想要实现无间隔连接,可以把 separator 设置为空字符串。
var a = ['a', 'b', 'c'];
a.push('d');
console.log(a.join(''));//abcd
也可以使用 + 运算符连接这些字符串。目前主流的浏览器(包括 IE8 之后的版本)都对 + 运算符做了特别的优化,所以它的性能已经显著高于 Array.join() 咯,在大多数情况下,如果需要连接字符串,建议首选 + 运算符哦O(∩_∩)O~
1.3 array.pop()
pop 与 push 方法可以使得数组像堆栈一样工作。pop 方法会移除 array 中的最后一个元素并返回这个元素。如果 array 是空数组,那么会返回 undefined:
var a = ['a', 'b', 'c'];
console.log(a.pop());//c
pop 可以像这样实现:
Array.method('pop2', function () {
return this.splice(this.length - 1, 1)[0];
});
console.log(a.pop2());//b
1.4 array.push()
push 方法把一个或多个参数 item 附加到一个数组的尾部。它会修改 array。如果 item 是一个数组,它会把这个数组作为单个元素添加到数组中,并返回这个 array 的新长度值:
var a = ['a', 'b', 'c'];
var b = ['x', 'y', 'z'];
console.log(a.push(b, true));//5
console.log(a);//[ "a", "b", "c", Array[3], true ]
push 可以像这样实现:
Array.method('push2', function () {
//[this.length,0] 中的 length 指定 slice 的初始索引;0 表示执行不删除
this.splice.apply(this, [this.length, 0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
console.log(a.push2(b, true));//7
console.log(a);//[ "a", "b", "c", Array[3], true, Array[3], true ]
1.5 array.reverse()
这个方法会反转 array 里元素的顺序,并返回 array 本身:
var a = ['a', 'b', 'c'];
var b = a.reverse();
console.log(a);//[ "c", "b", "a" ]
console.log(b);//[ "c", "b", "a" ]
1.6 array.shift()
shift 会移除数组 array 中的第 1 个元素并返回该元素。如果 array 是空数组,那么会返回 undefined。shift 通常比 pop 慢的多:
var a = ['a', 'b', 'c'];
console.log(a.shift());//a
console.log(a);//[ "b", "c" ]
shift 可以像这样实现:
Array.method('shift2', function () {
return this.splice(0, 1)[0];
});
console.log(a.shift2());//b
console.log(a);//[ "c" ]
1.7 array.slice(start, end)
slice 方法会对 array 中的一段进行浅复制。从 array[start] 一直复制到 array[end]。end 参数是可选的,默认为数据的长度 array.length。
以下是特殊情况:
- 如果两个参数中任意一个是负数,这个负值的参数会与 array.length 相加,试图让它变为非负数。
- 如果 start 大等于 array.length ,将会得到一个空数组。
var a = ['a', 'b', 'c'];
console.log(a.slice(0, 1));// [ "a" ]
console.log(a.slice(1));//[ "b", "c" ]
console.log(a.slice(1, 2));
1.8 array.sort(comparefn)
sort 方法会对 array 的内容进行排序,默认的比较函数是把要排序的元素视为字符串,所以它不能正确地给一组数字排序:
var n = [4, 8, 29, 33, 48, 50, 98];
n.sort();//默认要排序的元素都为字符串
console.log(n);//[ 29, 33, 4, 48, 50, 8, 98 ]
我们可以使用自定义的比较函数来解决数字排序的问题。比较函数接受两个参数:
- 如果这两个参数相等,则返回 0。
- 如果第一个参数排在前面,则返回负数。
- 如果第二个参数排在前面,则返回正数。
n.sort(function (a, b) {
return a - b;
});
console.log(n);//[ 4, 8, 29, 33, 48, 50, 98 ]
上面的这个比较函数可以给数字排序,但不能为字符串排序。我们可以定义一个可以给任意简单值的数组进行排序的比较函数:
var m = ['a', 'cc', 'aa', 4, 8, 29, 33, 48, 50, 98];
m.sort(function (a, b) {
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
});
console.log(m);//[ 4, 8, 29, 33, 48, 50, 98, "a", "aa", "cc" ]
如果大小写不重要,应该在比较前,先把待比较的值都转化为小写。
现在编写一个更智能的比较函数,它能够对对象数组排序:
/**
* 为对象数组排序
* @param name 以对象中的哪一个成员名排序
* @return 可进行排序的比较函数
*/
var by = function (name) {
return function (o, p) {
var a, b;
if (typeof o === 'object' && typeof p === 'object' && o && p) {
a = o[name];
b = p[name];
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
} else {
throw{
name: 'Error',
message: 'Expected an object when sorting by ' + name
}
}
};
};
var s = [
{first: 'Joe', last: 'Besser'},
{first: 'Hoe', last: 'Howard'},
{first: 'Joe', last: 'Derita'},
{first: 'Shemp', last: 'Howard'},
{first: 'Larry', last: 'Fine'},
{first: 'Curly', last: 'Howard'}
];
s.sort(by('first'));
console.log(s);
sort 方法是不稳定的,即排序后会改变相等值的相对位置。所以如果想基于多个键值进行排序,需要修改这个比较函数,让它可以接受第 2 个参数,当主要的键值产生了一个匹配时,另一个 compare 方法可以被调用,以决定最终的顺序:
/**
*
* @param name 以对象中的哪一个成员名排序
* @param minor 次要比较函数(可选)
* @returns {Function} 可进行排序的比较函数
*/
var by = function (name, minor) {
return function (o, p) {
var a, b;
if (o && p && typeof o === 'object' && typeof p === 'object') {
a = o[name];
b = p[name];
if (a === b) {
return typeof minor === 'function' ? minor(o, p) : 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
} else {
throw{
name: 'Error',
message: 'Expected an object when sorting by ' + name
}
}
};
};
s.sort(by('last', by('first')));
console.log(s);
1.9 array.splice(start, deleteCount, item...)
注意:splice 与 slice 方法名很容易混淆,请注意区分。如果不记得 slice 方法了,可以把页面拉上去再看看哦O(∩_∩)O~
splice 方法从 array 中移除一个或多个元素,并用新的 item 来替换这些元素。参数 start 是需要移除元素的起始位置。deleteCount 是要移除元素的个数。如果有额外的参数,那么 item 会被插入到被移除元素的位置上。这个方法会返回一个包含被移除元素的数组。
splice 主要用途是从数组中删除元素:
var a = ['a', 'b', 'c'];
var r = a.splice(1, 1, 'deniro', 'li');
console.log(a);// [ "a", "deniro", "li", "c" ]
console.log(r);// [ "b" ]
splice 可以像这样实现:
//start:需要移除元素的索引位置
//deleteCount:要移除元素的个数
Array.method('splice2', function (start, deleteCount) {
var max = Math.max,//最大值函数
min = Math.min,//最小值函数
delta,//增量
element,
insertCount = max(arguments.length - 2, 0),//需要插入的元素个数
k = 0,
len = this.length,//数组长度
new_len,//新数组长度
result = [],//返回结果集
shift_count;
start = start || 0;//设置需要移除元素的索引位置的默认值为 0
if (start < 0) {
start += len;//纠正起始索引为负值的情况
}
start = max(min(start, len), 0);//纠正起始索引为非正常值的情况
deleteCount = max(min(typeof deleteCount === 'number' ? deleteCount : len, len - start), 0);//纠正要移除元素的个数为非正常值的情况
delta = insertCount - deleteCount;
new_len = len + delta;
//把需要删除的元素放入返回结果集
while (k < deleteCount) {
element = this[start + k];
if (element !== undefined) {
result[k] = element;
}
k += 1;
}
shift_count = len - start - deleteCount;//原数组元素被替换的个数
if (delta < 0) {//数组变短
k = start + insertCount;
while (shift_count) {//从要删除的最后一个元素的下一个元素开始,都向前移动
this[k] = this[k - delta];
k += 1;
shift_count -= 1;
}
this.length = new_len;
} else if (delta > 0) {//数组变长
k = 1;
while (shift_count) {//从起始位置的下一元素开始到数组结尾,从后往前,把这些元素都向后移动
this[new_len - k] = this[len - k];
k += 1;
shift_count -= 1;
}
this.length = new_len;
}
//插入新元素
for (k = 0; k < insertCount; k += 1) {
this[start + k] = arguments[k + 2];
}
return result;
});
var a = ['a', 'b', 'c'];
var r = a.splice2(1, 1, 'deniro', 'li');
console.log(a);// [ "a", "deniro", "li", "c" ]
console.log(r);// [ "b" ]
1.10 array.unshift(item...)
unshift 方法会把元素添加到数组的首部,并返回 array 新的 length:
var a = ['a', 'b', 'c'];
var r = a.unshift('?', '@');
console.log(a);//[ "?", "@", "a", "b", "c" ]
console.log(r);//5
unshift 可以像这样实现:
Array.method('unshift2', function () {
this.splice.apply(this, [0, 0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
var a = ['a', 'b', 'c'];
var r = a.unshift2('?', '@');
console.log(a);//[ "?", "@", "a", "b", "c" ]
console.log(r);//5
2 function.apply(thisArg, argArray)
使用 array 方法调用 function,会传递一个绑定到 this 上的对象和一个可选的数组作为参数:
Function.method('bind2', function (that) {
var method = this;
return function () {
return method.apply(that, arguments);
};
});
var x = function () {
return this.value;
}.bind2({value: 666});
console.log(x());
3 Number
3.1 number.toExponential(fractionDigits)
toExponential 方法会把 number 转换为一个指数形式的字符串。可选参数 fractionDigits 控制小数点后的数字位数,它的值范围是 0 ~ 20:
console.log(Math.PI.toExponential(0));//3e+0
console.log(Math.PI.toExponential(2));//3.14e+0
console.log(Math.PI.toExponential(7));//3.1415927e+0
console.log(Math.PI.toExponential(16));//3.1415926535897931e+0
console.log(Math.PI.toExponential());//3.141592653589793e+0
3.2 number.toFixed(fractionDigits)
toFixed 方法会把 number 转换为一个十进制数形式的字符串。可选参数 fractionDigits 控制小数点后的数字位数,它的值范围是 0 ~ 20,默认为 0:
console.log(Math.PI.toFixed(0));//3
console.log(Math.PI.toFixed(2));//3.14
console.log(Math.PI.toFixed(7));//3.1415927
console.log(Math.PI.toFixed(16));//3.1415926535897931
console.log(Math.PI.toFixed());//3
3.3 number.toPrecision(precision)
toPrecision 方法会把 number 转换为一个十进制数形式的字符串。可选参数 precision 控制数字的精度,它的值的范围是 0 ~ 21:
console.log(Math.PI.toPrecision(2));//3.1
console.log(Math.PI.toPrecision(7));//3.141593
console.log(Math.PI.toPrecision(16));//3.141592653589793
console.log(Math.PI.toPrecision());//3.141592653589793
3.4 number.toString(radix)
toString 方法会把 number 转换为一个字符串。可选参数 radix 控制基数,它的值的范围是 2 ~ 36。默认的 radix 是 10。radix 一般是整数,但也可以是任意数字。可以把 number.toString() 简写为 String(number)。
console.log(Math.PI.toString(2));//11.001001000011111101101010100010001000010110100011
console.log(Math.PI.toString(8));//3.1103755242102643
console.log(Math.PI.toString(16));//3.243f6a8885a3
console.log(Math.PI.toString());//3.141592653589793
4 object.hasOwnProperty(name)
如果 object 中包含名为 name 的属性(不检查原型链中的同名属性),这个方法就返回 true。
Object.create = function (o) {
var F = function () {
};
F.prototype = o;
return new F();
}
var a = {member: true};
var b = Object.create(a);
console.log(a.hasOwnProperty('member'));//true
console.log(a.hasOwnProperty('false'));//false
console.log(b.hasOwnProperty('member'));//false
console.log(b.member);//true
5 RegExp
5.1 regexp.exec(string)
exec 方法是正则表达式中最强大、也是运行最慢的方法。如果它成功匹配了 regexp 和 string,它就会返回一个数组 array。array[0] 包含 regexp 匹配的子字符串。array[1] 是分组 1 捕获的文本,array[2] 是分组 2 捕获的文本,以此类推。如果匹配失败,会返回 null。
如果 regexp 带有 g 标识(全局标识),那么查找会从 regexp.lastIndex(初始值为 0)位置开始。如果匹配成功,那么 regexp.lastIndex 会被设置为这个匹配后的第一个字符位置。匹配不成功,会重置 regexp.lastIndex 为 0。利用这样的设计,就可以循环调用 exec 来查询一个匹配模式在一个字符串中发生了几次。但要注意两点:
- 如果提前退出了循环,再次进入这个循环之前,必须把 regexp.lastIndex 重置为 0。
-
^
仅匹配 regexp.lastIndex 为 0 的情况。
/**
* regexp.text(string)
*/
var b = /&.+;/.test('frank & beans');
console.log(b);//true
/**
* 把 HTML 文本分解为标签和文本
* 产生这样一个数组:
* [0] 匹配的标签和文本
* [1] /(斜杠);如果有
* [2] 标签名
* [3] 属性;如果有
*/
var text = '<html><body bgcolor="linen"><p>This is <b>bold</b>!</p></body></html>';
var tags = /[^<>]+|<(\/?)([A-Za-z]+)([^<>]*)>/g;
var a, i;
while ((a = tags.exec(text))) {
for (i = 0; i < a.length; i += 1) {
console.log((('// [' + i + '] ' + a[i])));
}
}
分解为标签和文本的结果
5.2 regexp.test(string)
test 方法是使用正则表达式最简单也是最快的方法。如果 regexp 匹配 string,它会返回 true;否则返回 false。
var b = /&.+;/.test('frank & beans');
console.log(b);//true
test 可以像这样实现:
RegExp.method('test2', function (string) {
return this.exec(string) !== null;
});
console.log(/&.+;/.test2('frank & beans'));//true
6 String
6.1 string.charAt(pos)
charAt 方法会返回 string 中 pos 位置处的字符。如果 pos 小于 0 或大等于 string 的长度,这个方法会返回空字符串。JavaScript 没有字符类型,所以这个方法返回的结果是字符串:
var name = "Deniro";
console.log(name.charAt(0));//D
charAt 可以像这样实现:
String.method('charAt2', function (pos) {
return this.slice(pos, pos + 1);
});
console.log(name.charAt2(0));//D
6.2 string.charCodeAt(pos)
charCodeAt 方法会返回 string 中 pos 位置处的字符所对应的字符码位(整数形式)。如果 pos 小于 0 或大等于 string 的长度,这个方法会返回 NaN。
var name = "Deniro";
console.log(name.charCodeAt(0));//68
6.3 string.concat(string...)
concat 方法会把其他字符串连接起来,构造一个新的字符串。它很少使用,因为使用 + 运算符更方便。
console.log('C'.concat('a', 't'));//Cat
6.4 string.indexOf(searchString,position)
indexOf 方法在 string 中查找字符串 searchString。如果被找到,就返回第一个匹配字符的位置,否则返回 -1。可选参数 position 可设置从 string 的某个指定位置开始查找:
var text = 'Denironi';
console.log(text.indexOf('ni'));//2
console.log(text.indexOf('ni', 4));//6
console.log(text.indexOf('ni', 8));//-1
6.5 string.lastIndexOf(searchString,position)
lastIndexOf 方法和 indexOf 方法类似,只是它是从字符串的末尾开始查找的:
var text = 'Denironi';
console.log(text.lastIndexOf('ni'));//6
console.log(text.lastIndexOf('ni', 4));//2
console.log(text.lastIndexOf('ni', 8));//6
6.6 string.localeCompare(that)
localeCompare 方法比较两个字符串。类似 array.sort 比较函数的约定:
var m = ['AAA', 'A', 'aa', 'a', 'Aa', 'aaa'];
m.sort(function (a, b) {
return a.localeCompare(b);
});
console.log(m);//[ "a", "A", "aa", "Aa", "aaa", "AAA" ]
6.7 string.match(regexp)
这个方法根据 g 标识来决定如何匹配:
- 如果没有 g 标识 - 那么调用 string.match(regexp) 的结果与调用 regexp.exec(string) 的结果相同。
- 如果带有 g 标识 - 那么这个方法会返回一个包含所有匹配(除了捕获分组之外)的数组。
var text = '<html><body bgcolor="#faf0e6"><p>This is <b>bold</b>!</p></body></html> ';
var tags = /[^<>]+|<(\/?)([A-Za-z]+)([^<>]*)>/g;
var a, i;
a = text.match(tags);
for (i = 0; i < a.length; i += 1) {
console.log('[' + i + '] ' + a[i]);
}
6.8 string.replace(searchValue, replaceValue)
replace 方法会对 string 进行查找与替换,并返回一个新的字符串。参数 searchValue 可以是一个字符串或一个正则表达式的对象。如果它是一个字符串,那么 searchValue 只会在第 1 次出现的地方被替换:
console.log("mother_in_law".replace('_', '-'));//mother-in_law
这恐怕不是你想要的结果哦o( ̄︶ ̄)o
replaceValue 可以是字符串或函数。如果是字符串,那么字符 $
有着特别的含义:
美元符号序列 | 替换对象 |
---|---|
$$ |
$ |
$^ |
整个匹配的文本 |
$number |
分组捕获的文本 |
$' |
匹配后的文本 |
$`:匹配之前的文本
因为符号太特殊咯,所以不得不写在这里O(∩_∩)O~
var oldareacode = /\((\d{3})\)/g;
console.log('(555)666-1212'.replace(oldareacode, '$1-'));//555-666-1212
如果 replaceValue 是一个函数,那么每遇到一次匹配,就会调用一次函数,该函数的返回值将会被作为替换文本。传递给这个函数的第一个参数是整个被匹配的文本,第二个参数是分组 1 捕获的文本,第三个参数是分组 2 捕获的文本,以此类推。
String.method('entityify', function () {
var character = {
'<': '<',
'>': '>',
'&': '&',
'"': '"'
};
return function () {
return this.replace(/[<>&"]/g, function (c) {
return character[c];
});
};
}());
console.log("entityify:"+("<&>".entityify()));//<&>
6.9 string.search(regexp)
search 方法与 indexOf 方法类似,但它只接受一个正则表达式对象作为参数。如果找到,会返回第一个匹配的首字符位置;如果没有找到,就返回 -1。该方法会忽略 g 标识,它也没有 position 参数哦O(∩_∩)O~
var text = 'and in it he says "Any damn fool could';
console.log(text.search(/["']/));//18
6.10 string.slice(start, end)
slice 方法会复制 string 的一部分来构造新的字符串。如果 start 是负数,那么它会与 string.length 相加,试图让它变为正数。end 参数是可选的,默认值是 string.length。如果 end 是负数,那么它也会与 string.length 相加。end 参数是你要取得的最后一个字符的位置值加 1。如果想要取从位置 p 开始的 n 个字符,那么可以这样做:string.slice(p, p+n)。
console.log(text.slice(18));//"Any damn fool could
console.log(text.slice(0, 3));//and
console.log(text.slice(-5));//could
console.log(text.slice(19, 32));//Any damn fool
6.11 string.split(separator, limit)
split 方法会把 string 分割成片段,然后返回这些片段组成的数组。可选参数 limit 会限制被分割的片段数量。separator 参数可以是字符串,也可以是正则表达式。
如果 separator 参数是一个空字符,那么这个方法会返回一个单字符的数组:
var digits = '0123456789';
console.log(digits.split('', 5));//[ "0", "1", "2", "3", "4" ]
这个方法会忽略 g 标识,并把分隔符两边的文本都复制到要返回的数组中:
console.log('192.168.0.1'.split('.'));//[ "192", "168", "0", "1" ]
console.log('|a|b|c|'.split('|')); //[ "", "a", "b", "c", "" ]
var text = 'last, first, middle';
console.log(text.split(/\s*,\s*/));//[ "last", "first", "middle" ]
注意:来自分组捕获的文本也会被包含在被分割后的数组中:
console.log(text.split(/\s*(,)\s*/));//[ "last", ",", "first", ",", "middle" ]
当 separator 是一个正则表达式时,老的浏览器(比如 IE8)会在输出数组中排除掉空字符串。新的浏览器就会保留:
console.log('|a|b|c|'.split(/\|/));//[ "", "a", "b", "c", "" ]
6.12 String.fromCharCode(char...)
这个方法会根据数字编码返回一个字符串:
console.log(String.fromCharCode(67,97,116));//Cat
6.13 string.substring(start, end)
substring 方法与 slice 相同,但它不能处理负数的参数,所以请使用 slice 方法哦O(∩_∩)O~