补充一下path包中join()跟reslove()以及lodash到es6的演化
诚如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 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' )
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/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' ); let myPath2 = path.join(__dirname,'./img/so' ); let myPath3 = path.resolve(__dirname,'/img/so' ); let myPath4 = path.resolve(__dirname,'./img/so' ); console .log(__dirname); console .log(myPath); console .log(myPath2); console .log(myPath3); console .log(myPath4);
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 (当前元素在数组中的索引) 4 、array (调用 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; })
但是要是我们设置了初始值就不会报错,如下:
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);
所以一般来说我们提供初始值通常更安全
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 ); console .log( mul );
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);
(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);
**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);
连接三个数组
以下代码将三个数组合并为一个新数组:
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);
将值连接到数组
以下代码将三个值连接到数组:
1 2 3 4 5 6 var alpha = ['a' , 'b' , 'c' ];var alphaNumeric = alpha.concat(1 , [2 , 3 ]);console .log(alphaNumeric);
合并嵌套数组
以下代码合并数组并保留引用:
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);var nums2=num1.concat(4 ,num3);console .log(nums2)num1[0 ].push(4 ); console .log(nums);
(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);
(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));
(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)
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 ); _.reduce({ 'a' : 1 , 'b' : 2 , 'c' : 1 }, function (result, value, key ) { (result[value] || (result[value] = [])).push(key); return result; }, {});
其实是先有的lodash,后有的es6新语法,但是并不能完全取代lodash
为什么不用 ES6 完全替换 Lodash Lodash 是一款非常知名的 JavaScript 工具库,能够让开发者十分便捷地操纵数组和对象。我则是非常喜欢用它提供的函数式编程风格来操作集合类型,特别是链式调用和惰性求值。然而,随着 ECMAScript 2015 Standard (ES6) 得到越来越多主流浏览器的支持,以及像 Babel 这样,能够将 ES6 代码编译成 ES5 从而在旧浏览器上运行的工具日渐流行,人们会发现许多 Lodash 提供的功能已经可以用 ES6 来替换了。然而真的如此吗?我认为,Lodash 仍然会非常流行,因为它可以为程序员提供更多的便利,并且优化我们编程的方式。
_.map
和 Array#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) }) [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 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 }]arr.map((obj ) => obj.n) _.map(arr, 'n' )
当对象类型的嵌套层级很多时,Lodash 的快捷方式就更实用了:
1 2 3 4 5 6 7 8 let arr = [ { a : [ { n : 1 } ]}, { b : [ { n : 1 } ]} ] arr.map((obj ) => obj.a[0 ].n) _.map(arr, 'a[0].n' )
可以看到,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 } ]; users.filter((o ) => o.active) _.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) })
解构赋值和箭头函数 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 _.head([1 , 2 , 3 ]) _.tail([1 , 2 , 3 ]) const [head, ...tail] = [1 , 2 , 3 ]let say = _.rest((who, fruits ) => who + ' likes ' + fruits.join(',' ))say('Jerry' , 'apple' , 'grape' ) say = (who, ...fruits ) => who + ' likes ' + fruits.join(',' ) say('Mary' , 'banana' , 'orange' ) _.constant(1 )() _.identity(2 ) (x => (() => x))(1 )() (x => x)(2 ) let add = (a, b ) => a + blet add1 = _.partial(add, 1 )add1 = b => add(1 , b) let curriedAdd = _.curry(add)let add1 = curriedAdd(1 )curriedAdd = a => b => a + b add1 = curriedAdd(1 )
对于集合类操作,我更倾向于使用 Lodash 函数,因为它们的定义更准确,而且可以串联成链;对于那些可以用箭头函数来重写的用例,Lodash 同样显得更简单和清晰一些。此外,参考资料 里的几篇文章中也提到,在函数式编程场景下,Lodash 提供了更为实用的柯里化、运算符函数 、FP 模式 等特性。
总结 Lodash 为 JavaScript 语言增添了诸多特性,程序员可以用它轻松写出语义精确、执行高效的代码。此外,Lodash 已经完全模块化 了。虽然它的某些特性最终会被淘汰,但仍有许多功能是值得我们继续使用的。同时,Lodash 这样的类库也在不断推动 JavaScript 语言本身的发展。