在阅读 underscore 的过程当中,发现了它的一些小技巧,对咱们平时的编程颇有用。在这里向你们介绍一二javascript
首先,看源码: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 的保留字,因此不可被更改。编程
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; }
其实,咱们能够利用数组的 slice
和 concat
方法返回新的数组这个特性,来实现复制数组的功能;性能
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]
实际业务代码,除非对性能要求极高,不然仍是推荐 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
当咱们编写两个功能很是相近的函数时,例如,实现复制一个数组的功能,分别是正序和倒序,咱们可能会这样子实现(这里只是为了举例子,复制数组推荐第二点提到的使用slice
或concat
):
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); };
这样子实现会有什么问题呢?
其实对copyDesc
,copyAsce
,来讲,只有 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
这种写法比较优雅一点,你以为呢?
这里只举两个例子,isString
,isArray
,其余的例如 isArguments
, isFunction
, 因为有些浏览器兼容问题须要特殊处理,这里就不细说了。
而像isNull
,isUndefined
,这些比较简单的,这里也不细说了:)
咱们知道: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]'; }