Abo

webpack知识点盲区:resolve以及reduce

补充一下path包中join()跟reslove()以及lodash到es6的演化

wallhaven-132zow

诚如webpack之所说

浅谈一下path中的join跟resolve

1.连接路径:path.join([path1][, path2][, ...])
path.join()方法可以连接任意多个路径字符串。要连接的多个路径可做为参数传入。

path.join()方法在接边路径的同时也会对路径进行规范化。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__dirname
// __dirname返回当前文件所在的绝对路径
const path = require('path');

const path1 = path.join(__dirname, '/foo');
const path2 = path.join(__dirname, './foo/bar');
const path3 = path.join('/foo', 'bar', '/baz/apple', 'aaa', '..'); // ..就是上一级目录
const path4 = path.join('foo', 'bar', 'baz');

console.log(path1);
console.log(path2);
console.log(path3);
console.log(path4);

// 输出结果
/Users/xiao/work/test/foo
/Users/xiao/work/test/foo/bar
/foo/bar/baz/apple
foo/bar/baz

//不合法的字符串将抛出异常
path.join('foo', {}, 'bar')
// 抛出的异常 TypeError: Arguments to path.join must be strings'

2.路径解析:path.resolve([from ...], to)
path.resolve()方法可以将多个路径解析为一个规范化的绝对路径。其处理方式类似于对这些路径逐一进行cd操作,与cd操作不同的是,这引起路径可以是文件,并且可不必实际存在(resolve()方法不会利用底层的文件系统判断路径是否存在,而只是进行路径字符串操作)。例如:

1
path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')

相当于

1
2
3
4
5
cd foo/bar
cd /tmp/file/
cd ..
cd a/../subfile
pwd

例子:

1
2
3
4
5
6
7
8
9
10
path.resolve('/foo/bar', './baz') 
// 输出结果为
'/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/')
// 输出结果为
'/tmp/file'

path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// 当前的工作路径是 /home/itbilu/node,则输出结果为
'/home/itbilu/node/wwwroot/static_files/gif/image.gif'

3.对比

1
2
3
4
5
6
7
8
9
10
const path = require('path'); 
let myPath = path.join(__dirname,'/img/so'); //D:\myProgram\test\img\so
let myPath2 = path.join(__dirname,'./img/so'); //D:\myProgram\test\img\so
let myPath3 = path.resolve(__dirname,'/img/so'); // D:\img\so
let myPath4 = path.resolve(__dirname,'./img/so'); // D:\myProgram\test\img\so
console.log(__dirname); //D:\myProgram\test
console.log(myPath); //D:\myProgram\test\img\so
console.log(myPath2); //D:\myProgram\test\img\so
console.log(myPath3); //D:\img\so<br>
console.log(myPath4); //D:\myProgram\test\img\so

Javascript数组reduce()方法详解及高级技巧

Lodash是一个 JavaScript 的实用工具库, 表现一致性, 模块化, 高性能, 以及 可扩展,且不谈lodash中的reduce(),先来看看js原生里面的reduce()方法

reduce()方法可以搞定的东西,for循环,或者forEach方法有时候也可以搞定,那为啥要用reduce()?这个问题,之前我也想过,要说原因还真找不到,唯一能找到的是:通往成功的道路有很多,但是总有一条路是最捷径的,亦或许reduce()逼格更高…

语法

1
arr.reduce(callback,[initialValue])

reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

1
2
3
4
5
6
7
8
callback (执行数组中每个值的函数,包含四个参数)

1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4array (调用 reduce 的数组)

initialValue (作为第一次调用 callback 的第一个参数。)

实例解析 initialValue 参数

先给出结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。

第一个例子

1
2
3
4
5
6
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
console.log(arr, sum);

打印结果:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

这里可以看出,上面的例子index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。

再看第二个例子:

1
2
3
4
5
6
var  arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0) //注意这里设置了初始值
console.log(arr, sum);

打印结果:
0 1 0
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

这个例子index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次。

注意:如果这个数组为空,运用reduce是什么情况?

1
2
3
4
5
6
var  arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
//报错,"TypeError: Reduce of empty array with no initial value"

但是要是我们设置了初始值就不会报错,如下:

1
2
3
4
5
6
var  arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0)
console.log(arr, sum); // [] 0

所以一般来说我们提供初始值通常更安全

reduce的简单用法

当然最简单的就是我们常用的数组求和,求乘积了。

1
2
3
4
5
var  arr = [1, 2, 3, 4];
var sum = arr.reduce((x,y)=>x+y)
var mul = arr.reduce((x,y)=>x*y)
console.log( sum ); //求和,10
console.log( mul ); //求乘积,24

reduce的高级用法

(1)计算数组中每个元素出现的次数

1
2
3
4
5
6
7
8
9
10
11
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}

(2)数组去重

1
2
3
4
5
6
7
8
9
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]

**concat()** 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

节外生枝:说一下js中的concat()方法💀

语法如下:

1
var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

连接两个数组

以下代码将两个数组合并为一个新数组:

1
2
3
4
5
var alpha = ['a', 'b', 'c'];
var numeric = [1, 2, 3];

alpha.concat(numeric);
// result in ['a', 'b', 'c', 1, 2, 3]

连接三个数组

以下代码将三个数组合并为一个新数组:

1
2
3
4
5
6
7
8
var num1 = [1, 2, 3],
num2 = [4, 5, 6],
num3 = [7, 8, 9];

var nums = num1.concat(num2, num3);

console.log(nums);
// results in [1, 2, 3, 4, 5, 6, 7, 8, 9]

将值连接到数组

以下代码将三个值连接到数组:

1
2
3
4
5
6
var alpha = ['a', 'b', 'c'];

var alphaNumeric = alpha.concat(1, [2, 3]);

console.log(alphaNumeric);
// results in ['a', 'b', 'c', 1, 2, 3]

合并嵌套数组

以下代码合并数组并保留引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var num1 = [[1]];
var num2 = [2, [3]];
var num3=[5,[6]];

var nums = num1.concat(num2);

console.log(nums);
// results is [[1], 2, [3]]

var nums2=num1.concat(4,num3);

console.log(nums2)
// results is [[1], 4, 5,[6]]

// modify the first element of num1
num1[0].push(4);

console.log(nums);
// results is [[1, 4], 2, [3]]

(3)将二维数组转化为一维

1
2
3
4
5
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]

(3)将多维数组转化为一维

1
2
3
4
5
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]

(4)对象里的属性求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var result = [
{
subject: 'math',
score: 10
},
{
subject: 'chinese',
score: 20
},
{
subject: 'english',
score: 30
}
];

var sum = result.reduce(function(prev, cur) {
return cur.score + prev;
}, 0);
console.log(sum) //60

Lodash里面的reduce()方法

.reduce(collection, [iteratee=.identity], [accumulator])

1
2
3
4
5
6
7
8
9
10
_.reduce([1, 2], function(sum, n) {
return sum + n;
}, 0);
// => 3

_.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
(result[value] || (result[value] = [])).push(key);
return result;
}, {});
// => { '1': ['a', 'c'], '2': ['b'] } (无法保证遍历的顺序)

其实是先有的lodash,后有的es6新语法,但是并不能完全取代lodash

为什么不用 ES6 完全替换 Lodash

Lodash 是一款非常知名的 JavaScript 工具库,能够让开发者十分便捷地操纵数组和对象。我则是非常喜欢用它提供的函数式编程风格来操作集合类型,特别是链式调用和惰性求值。然而,随着 ECMAScript 2015 Standard (ES6) 得到越来越多主流浏览器的支持,以及像 Babel 这样,能够将 ES6 代码编译成 ES5 从而在旧浏览器上运行的工具日渐流行,人们会发现许多 Lodash 提供的功能已经可以用 ES6 来替换了。然而真的如此吗?我认为,Lodash 仍然会非常流行,因为它可以为程序员提供更多的便利,并且优化我们编程的方式。

_.mapArray#map 是有区别的

在处理集合对象时,_.map_.reduce_.filter、以及 _.forEach 的使用频率很高,而今 ES6 已经能够原生支持这些操作:

1
2
3
4
5
6
7
8
9
10
_.map([1, 2, 3], (i) => i + 1)
_.reduce([1, 2, 3], (sum, i) => sum + i, 0)
_.filter([1, 2, 3], (i) => i > 1)
_.forEach([1, 2, 3], (i) => { console.log(i) })

// 使用 ES6 改写
[1, 2, 3].map((i) => i + 1)
[1, 2, 3].reduce((sum, i) => sum + i, 0)
[1, 2, 3].filter((i) => i > 1)
[1, 2, 3].forEach((i) => { console.log(i) })

但是,Lodash 的 _.map 函数功能更强大,它能够操作对象类型,提供了遍历和过滤的快捷方式,能够惰性求值,对 null 值容错,并且有着更好的性能。

ES6 中有以下几种方式来遍历对象:

1
2
3
for (let key in obj) { console.log(obj[key]) }
for (let key of Object.keys(obj)) { console.log(obj[key]) }
Object.keys(obj).forEach((key) => { console.log(obj[key]) })

Lodash 中则有一个统一的方法 _.forEach

1
_.forEach(obj, (value, key) => { console.log(value) })

虽然 ES6 的 Map 类型也提供forEach 方法,但我们需要先费些功夫将普通的对象类型转换为 Map 类型:

1
2
// http://stackoverflow.com/a/36644532/1030720
const buildMap = o => Object.keys(o).reduce((m, k) => m.set(k, o[k]), new Map());

遍历和过滤的快捷方式

比如我们想从一组对象中摘取出某个属性的值:

1
2
3
4
5
let arr = [{ n: 1 }, { n: 2 }]
// ES6
arr.map((obj) => obj.n)
// Lodash
_.map(arr, 'n')

当对象类型的嵌套层级很多时,Lodash 的快捷方式就更实用了:

1
2
3
4
5
6
7
8
let arr = [
{ a: [ { n: 1 } ]},
{ b: [ { n: 1 } ]}
]
// ES6
arr.map((obj) => obj.a[0].n) // TypeError: 属性 'a' 在 arr[1] 中未定义
// Lodash
_.map(arr, 'a[0].n') // => [1, undefined]

可以看到,Lodash 的快捷方式还对 null 值做了容错处理。此外还有过滤快捷方式,以下是从 Lodash 官方文档中摘取的示例代码:

1
2
3
4
5
6
7
8
9
10
let users = [
{ 'user': 'barney', 'age': 36, 'active': true },
{ 'user': 'fred', 'age': 40, 'active': false }
];
// ES6
users.filter((o) => o.active)
// Lodash
_.filter(users, 'active')
_.filter(users, ['active', true])
_.filter(users, {'active': true, 'age': 36})

链式调用和惰性求值

Lodash 的这一特性就非常有趣和实用了。现在很流行使用短小可测的函数,结合链式调用和惰性求值,来操作集合类型。大部分 Lodash 函数都可以进行链式调用,下面是一个典型的 WordCount 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let lines = `
an apple orange the grape
banana an apple melon
an orange banana apple
`.split('\n')

_.chain(lines)
.flatMap(line => line.split(/\s+/))
.filter(word => word.length > 3)
.groupBy(_.identity)
.mapValues(_.size)
.forEach((count, word) => { console.log(word, count) })

// apple 3
// orange 2
// grape 1
// banana 2
// melon 1

解构赋值和箭头函数

ES6 引入了解构赋值、箭头函数等新的语言特性,可以用来替换 Lodash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Lodash
_.head([1, 2, 3]) // => 1
_.tail([1, 2, 3]) // => [2, 3]
// ES6 解构赋值(destructuring syntax)
const [head, ...tail] = [1, 2, 3]

// Lodash
let say = _.rest((who, fruits) => who + ' likes ' + fruits.join(','))
say('Jerry', 'apple', 'grape')
// ES6 spread syntax
say = (who, ...fruits) => who + ' likes ' + fruits.join(',')
say('Mary', 'banana', 'orange')

// Lodash
_.constant(1)() // => 1
_.identity(2) // => 2
// ES6
(x => (() => x))(1)() // => 1
(x => x)(2) // => 2

// 偏应用(Partial application)
let add = (a, b) => a + b
// Lodash
let add1 = _.partial(add, 1)
// ES6
add1 = b => add(1, b)

// 柯里化(Curry)
// Lodash
let curriedAdd = _.curry(add)
let add1 = curriedAdd(1)
// ES6
curriedAdd = a => b => a + b
add1 = curriedAdd(1)

对于集合类操作,我更倾向于使用 Lodash 函数,因为它们的定义更准确,而且可以串联成链;对于那些可以用箭头函数来重写的用例,Lodash 同样显得更简单和清晰一些。此外,参考资料里的几篇文章中也提到,在函数式编程场景下,Lodash 提供了更为实用的柯里化、运算符函数FP 模式等特性。

总结

Lodash 为 JavaScript 语言增添了诸多特性,程序员可以用它轻松写出语义精确、执行高效的代码。此外,Lodash 已经完全模块化了。虽然它的某些特性最终会被淘汰,但仍有许多功能是值得我们继续使用的。同时,Lodash 这样的类库也在不断推动 JavaScript 语言本身的发展。