web开发Web前端之路首页投稿(暂停使用,暂停投稿)

深入理解 JavaScript 方法集的特性与最佳实践

2017-10-12  本文已影响37人  deniro

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。

以下是特殊情况:

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 ]

我们可以使用自定义的比较函数来解决数字排序的问题。比较函数接受两个参数:

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.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 &amp; beans');
console.log(b);//true

test 可以像这样实现:

RegExp.method('test2', function (string) {
    return this.exec(string) !== null;
});
console.log(/&.+;/.test2('frank &amp; 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 标识来决定如何匹配:

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 = {
        '<': '&lt;',
        '>': '&gt;',
        '&': '&amp;',
        '"': '&quot;'
    };

    return function () {
        return this.replace(/[<>&"]/g, function (c) {
            return character[c];
        });
    };
}());

console.log("entityify:"+("<&>".entityify()));//&lt;&amp;&gt;

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~

上一篇下一篇

猜你喜欢

热点阅读