underscore之防冲突与Utility Functions

原文出至冴羽

防冲突

underscore使用 _ 作为函数的挂载对象,如果页面中已经存在了 _ 对象,underscore就会覆盖对象

1
2
3
4
var _ = {value: 1};

//引入 underscore后
console.log(_.value);// undefined

所以underscore提供了noConflict功能,可以放弃underscore的控制变量 _ ,返回underscore对象的引用

1
2
3
4
5
6
7
8
9
var _ = {value: 1};
//引入 underscore后

//放弃 _ 使用 $
var $ = _.noConflict();
console.log(_.value) // 1;

//使用underscore的方法
$.each([1,2,3],alert);

那么noConflict函数是如何实现的呢?

首先,在underscore执行的时候,会存储之前的 _ 对象,然后当执行noConflict函数的时候,再将之前存储的 _ 对象赋给全局对象,最后返回 underscore对象。这样,我们就可以利用返回的underscore对象使用underscore提供的各种方法。

1
2
3
4
5
6
7
// 源码一开始的时候便储存之前的 _ 对象
var previousUnderscore = root._;

_.noConflict = function() {
root._ = previousUnderscore;
return this;
};

_.identity

这是underscore的一个功能函数

1
2
3
4
// Keep the identity function around for default iteratees.
_.identity = function(value) {
return value;
};

如果我们自己编写一个 _.map函数:

1
2
3
_.map = function(arr, iteratee) {
return arr.map(iteratee)
}

然而当我们这样使用 _.map([1,2,3])时便会报错,因为我们没有传入iteratee函数,然而使用underscore却没有问题,结果时返回一个相同的新数组,原因就在于当iteratee为undefined的时候,underscore视为传入了 _.identity函数。就相当于:

1
2
3
4
_.map = function(arr, iteratee) {
if(!iteratee) iteratee = _.identity;
return arr.map(iteratee);
}

简而言之,如果我们想要复制一个数组:
var cloneArr = [1, 2, 3].map(_.identity) // [1,2,3]

_.constant

1
2
3
4
5
6
// Predicate-generating functions. Often useful outside of Underscore.
_.constant = function(value) {
return function() {
return value;
};
};

该函数传入一个value,然后返回一个函数,该函数返回的就是传入的value,这样做的意义是什么呢?

1
2
3
4
5
6
var value = 1;
var getValue = _.constant(value);
value = 2;

getValue(); // 1
getValue(); // 1

这个很容易让人想到 es6 的const,但其实并不是,它和下面的 _.noop 函数一样可以作为默认函数使用。

1
_.select(collection, filterFunction || function(){ return true; })

我们根据filterFunction筛选collection中符合条件的元素,如果没有传filterFunction,我们就返回所有的元素,如果有 _.constant函数,我们可以将其简化为:

1
_.select(collection, filterFunction || _.constant(true))

尽管没有太大的变化,但是语义更加明确

_.noop

1
_.noop = function(){}

一个空函数,看起来依旧没有什么用…
noop函数可以用于作为默认值,这样就可以省去是否存在的判断了,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不使用 noop
function a(value, callback) {
// 每次使用callback 都要判断一次
_.isFunction(callback) && callback()
}

// 使用 noop
function a(value, callback) {
// 判断一次
if(!_.isFunction(callback)) callback = _.noop;

// 以后都可以直接使用
callback();
}

deepGet

1
2
3
4
5
6
7
8
var deepGet = function(obj, path) {
var length = path.length;
for(var i = 0; i< length; i++) {
if(obj == null) return void 0;
obj = obj[path[i]];
}
return length ? obj : void 0;
}

deepGet用于获得对象深层次的值

1
2
3
4
5
6
7
var obj = {
value: {
deepValue: 2
}
}

console.log(deepGet(obj, ['value', 'deepValue']))

使用这个函数,可以避免深层次取值时,因为没有其中一个属性,导致的报错

shallowPropety

1
2
3
4
5
var shallowPropety = function(value) {
return function(obj) {
return obj == null ? void 0 : obj[value];
};
};

shallowProperty也是用于获取对象的属性,在开发中我们通常都会直接使用 . 来获取对象的属性,为什么这里要多此一举呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取 arr 所有元素的 name 属性
var arr = [
{
value: 1,
name: 'blue',
},
{
value: 2,
name: 'yellow'
}
]

// 普通方式
var names = arr.map(function(item){
return item.name;
})

// 使用 shallowProperty
var names = arr.map(shallowPropety('name'))

_.property

1
2
3
4
5
6
7
8
_.property = function(path) {
if(!_.isArray(path)) {
return shallowPropety(path);
}
return function(obj) {
return deepGet(obj, path);
};
};

_.property 结合了 deepGet 和 shallowProperty,可以获取元素深层次的值,上面一个列子也可以写成:

1
var names = arr.map(_.property('name'));

_.propertyOf

1
2
3
4
5
6
7
8
_.propertyOf = function(obj) {
if(obj == null) {
return function(){};
}
return function(path) {
return !Array.isArray(path) ? obj[path] : deepGet(obj, path);
};
};

_.property返回一个函数,这个函数返回任何传入的对象的指定属性。

_.propertyOf_.property相反,需要一个对象,并返回一个函数,这个函数将返回一个提供的属性的值。

1
2
3
4
5
6
7
8
9
10
11
// 获取 person 对象的所有属性值
var person = {
name: 'blue',
age : '19'
}

// 普通方式
var values = Object.keys(person).map(key => return person[key]); // ['blue', '19']

// 使用 _.propertyOf
var values = Object.keys(person).map(_.propertyOf(person)); //['blue', 19']

_.random

返回一个 min 和 max 之间的随机整数,如果你只传递一个参数,那么将返回0和这个参数之间的整数。

1
2
3
4
5
6
7
_.random = function(min, max) {
if(max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
}