underscore中的小技巧

在阅读 underscore 的过程当中,发现了它的一些小技巧,对咱们平时的编程颇有用。在这里向你们介绍一二javascript

void 0 代替 underfined

首先,看源码:java

_.isUndefined = function(obj) {
    return obj === void 0;
};

这里为啥要用 obj === void 0, 而不是 obj === undefined 呢?
由于,在 js 中,undefined 并非相似关键字(js 关键字有 function,return ...),因此,理论上是能够更改的。
事实上,在 IE8 上也的确是能够被更改的,chrome

var undefined = 1;
alert(undefined); // 1 -- IE8, undefined --- chrome

而在 chrome 或高版本的 IE 中,并不能更改全局的 undefined 。可是,局部的 undefined 仍然能够被改变。例如:express

(function() {
    var undefined = 1;
    alert(undefined); // 1 -- chrome
})();

因此, undefined 并不十分可靠,因此才须要 void 0 , void 是 js 的保留字,因此不可被更改。编程

在 MDN 上定义是:

The void operator evaluates the given expression and then returns undefined.
翻译:void 操做符会对 void 后面的表达式进行运算,而后返回 undefined

因此,使用void会一直返回 undefined ,因此,能够用void 0替代undefined.数组

复制数组

Array.prototype.slice.call(array); 可用来复制一个数组,或将类数组转换为数组浏览器

在 js 中,若是咱们想复制一个数组,要如何复制呢?也许你会这样作:函数

function copy(array) {
    var copyArray = [];
    for (var i = 0, len = array.length; i < len; i++) {
        copyArray.push(array[i]);
    }
    return copyArray;
}

其实,咱们能够利用数组的 sliceconcat 方法返回新的数组这个特性,来实现复制数组的功能;性能

var newArray = Array.prototype.slice.call(array);
var newArray2 = Array.prototype.concat.call(array);

并且,性能方面, slice 以及 concat 比单纯使用 for 循环还要更加高效测试

var array = _.range(10000000); //_.range,是undescore一个方法,用于生成一个从0到10000000的数组

console.time('for copy push');
var copyArray1 = [];
for (var i = 0, length = array.length; i < length; i++) {
    copyArray1.push(array[i]);
}
console.timeEnd('for copy push');

console.time('slice');
var copyArray2 = Array.prototype.slice.call(array);
console.timeEnd('slice');

console.time('concat');
var copyArray3 = Array.prototype.concat.call(array);
console.timeEnd('concat');
//结果
//for copy push: 379.315ms
//slice: 109.300ms
//concat: 92.852ms

另外,也是经过 slice , call 将类数组转换为数组

function test() {
    console.log(Array.prototype.slice.call(arguments));
}
test(1, 2, 3); //输出[1, 2, 3]

使用 Array[length]=value 代替 push 添加元素

实际业务代码,除非对性能要求极高,不然仍是推荐 push,毕竟更符合习惯

首先看源码 _.values()

_.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = Array(length); //等同于new Array(length)
    for (var i = 0; i < length; i++) {
        values[i] = obj[keys[i]];
    }
    return values;
};

一开始看这种写法,并不习惯,咱们大多数人可能更习惯这样写(使用 push ):

_.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = []; //
    for (var i = 0; i < length; i++) {
        values.push(obj[keys[i]]); //使用push
    }
    return values;
};

实际测试中,第一种写法会比第二种更快。

关键在于,咱们事先知道要填充的数组 values 的长度,而后预先生成一个对应长度的数组,以后只须要给对应的位置赋值。而第二种在 push 的时候,除了给对应位置赋值,还须要改变 values 数组的 length。

因此,建议在已知长度的状况下,使用第一种,而不知道长度的状况下,使用第二种。

适当的使用 return function

当咱们编写两个功能很是相近的函数时,例如,实现复制一个数组的功能,分别是正序和倒序,咱们可能会这样子实现(这里只是为了举例子,复制数组推荐第二点提到的使用sliceconcat):

function copyArray(array, dir) {
    var copyArray = [];
    var i = dir > 0 ? 0 : array.length - 1;

    for (; i >= 0 && i < array.length; i += dir) {
        copyArray.push(array[i]);
    }
    return copyArray;
}

var copyDesc = function(array) {
    return copyArray(array, 1);
};

var copyAsce = function(array) {
    return copyArray(array, -1);
};

这样子实现会有什么问题呢?

其实对copyDesccopyAsce,来讲,只有 dir 是不一样的而已,可是,这种方式实现,却须要将 array 也做为参数传递给 `copyArray。

copyDesc,copyAsce其实只是一个转发的做用而已。

咱们能够继续优化:

function copyArray(dir) {
    return function(array) {
        var copyArray = [];
        var i = dir > 0 ? 0 : array.length - 1;
        for (; i >= 0 && i < array.length; i += dir) {
            copyArray.push(array[i]);
        }
        return copyArray;
    };
}
var copyDesc = copyArray(1);
var copyAsce = copyArray(-1);

我以为 return function 这种写法比较优雅一点,你以为呢?

类型判断,使用 Object.prototype.toString()来判断

这里只举两个例子,isString,isArray,其余的例如 isArgumentsisFunction , 因为有些浏览器兼容问题须要特殊处理,这里就不细说了。

而像isNullisUndefined,这些比较简单的,这里也不细说了:)

咱们知道:
typeof 可能的返回值有:

类型 结果
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol(ES6 新增) "symbol"
宿主对象(由 JS 环境提供) Implementation-dependent
函数对象( [[Call]]) "function"
任何其余对象 "object"

可是, typeof 却有下面这种问题

typeof "test" ---> "string"
typeof new String("test") ---> "object"
typeof 123 -----> "number"
typeof new Number(123) --->"object"

跟咱们的指望不太同样,Object.prototype.toString 则没有这问题。

Object.prototype.toString.call('test'); //"[object String]"
Object.prototype.toString.call(new String('test')); //"[object String]"
Object.prototype.toString.call(123); //"[object Number]"
Object.prototype.toString.call(new Number(123)); //"[object Number]"

因此,咱们能够经过Object.prototype.toString来进行类型判断

function isNumber(obj) {
    return Object.prototype.toString.call(obj) === '[object Number]';
}

function isString(obj) {
    return Object.prototype.toString.call(obj) === '[object String]';
}

待续。。。

相关文章
相关标签/搜索