JavaScript函数式编程(1)内置方法灵活使用
JavaScript函数式编程(2)
前面两篇文章有提到几个原则
- 尽量不要定义变量,就算定义也最好使用
const
且保证定义变量后不再更改 - 尽量使用官方自带的API
很多面试者或ES5重度使用者 长期使用for 循环,但是你在阅读一些优质框架源码时会发现基本会被map
filter
reduce
forEach
代替,尽管很多人证明for循环或者多次定义变量对性能并没什么影响,反而函数式编程会牺牲一点性能,并以此为理由排斥函数式编程。
针对以上疑惑,我给自己的解释:
1、js性能很重要,但是没你想的那么重要
现代js 运行环境,无论是手机还是PC,js的性能针对页面微不足道,js是单线程,再看看各位电脑和手机的硬件配置。js所谓影响的一丢丢性能真的是微乎其微。反而是多图或者多http请求导致页面问题。或者你可以试试1000*1000的双for循环 浏览器多长时间跑完。(并不是要建议你可以写双循环)
2、代码少bug,维护性高才是第一追求
当前还是有很多人说微不足道的js性能(其实就是懒得提升自己代码质量)。函数式编程,可以很清晰的跟踪维护核心数据的每一步变动。只要是团队稍微有一些函数式编程的经验加上提交测试前端代码review,维护同事的代码成本一下子降下来。笔者所在的团队,线上bug几乎不会有前端逻辑上或者数据上的问题,只会有一些兼容、逻辑遗漏之类的bug。整体low的bug减少明显,代码维护性高。
以下给出一些案例(前几题太简单可以选择忽略)进行比较:
案例1.最长特殊序列 Ⅰ
下面答案是官方给的答案
var findLUSlength = function(a, b) {
if(a === b) return -1;
return a.length > b.length ? a.length : b.length;
};
一般不建议上面这么写,至少要把a、b的 length存起来 ,因为javaScript中的length是通过计算得出的,每次直接调用都会计算一次。
但其实利用API,可以让代码更通俗易懂,如下:
var findLUSlength = function(a, b) {
if (a === b) {
return -1
}
return Math.max(a.length, b.length)
};
案例2、最后一个单词的长度
以下是官方给出的答案,最明显几个问题
- 1、变量定义太多
- 2、有直接使用循环
- 3、维护难(变量多,代码长)
/**
* @param {string} s
* @return {number}
*/
var lengthOfLastWord = function(s) {
let end = s.length - 1;
while(end >= 0 && s[end] == ' ') end--;
if(end < 0) return 0;
let start = end;
while(start >= 0 && s[start] != ' ') start--;
return end - start;
};
以下利用JS自带的API直接给出答案,如果你稍微熟悉,(身为前端应该保证非常熟悉)可以一眼就看出每个步骤
var lengthOfLastWord = function(s) {
return s.trim().split(" ").reverse()[0].length
};
// 你可以很简单的看出来
//1、去前后空格
//2、转化成数组
//3、翻转取第一个值
//4、给出长度
以下题目基本原创,请勿转载 题目后就是答案,尝试不看答案你自己写出的答案会是什么样子的
1.源数组的每个元素都是一个简单 object,要求根据源数组生成一个新数组,新数组中每个元素在源数组元素的基础上添加 index 属性,值为元素在源数组中的索引(index)(如果源数组元素有这个属性则覆盖)。要求源数组不能改变。
如:
[
{a:1},
{a:2},
{a:1,b:2}
]
=>
[
{a:1,index:0},
{a:2,index:1},
{a:1,b:2,index:2}
]
// 先自己写再看我下面给出的答案
function f(array){
return array.map((v,index)=> Object.assign({},v,{index}));
}
2.反转源数组,要求源数组不能改变。
如:[1,2,3,4,5] => [5,4,3,2,1]
// 先自己写再看我下面给出的答案
function f(array){
return array.reduceRight((t, v) => [...t, v], []);
}
3.源数组的每个元素都是一个简单 object,object 结构:{ id:number, name:string, combineId?:number } combineId 为关联 id(即源数组某一个其他元素的 id,可能没有),要求根据源数组生成一个新数组,在新数组元素 object 中添加 combineName 属性,该属性值为当前元素的 combineId 在数组中通过 combineId==id 查找到的某个元素的 name 值(如找不到则为 undefined)。要求源数组不能改变。
如:
[
{id:1,name:'n1'},
{id:2,name:'n2',combindId:3},
{id:3,name:'n3',combindId:1}
]
=>
[
{id:1,name:'n1',combineName:undefined},
{id:2,name:'n2',combindId:3, combineName:'n3'},
{id:3,name:'n3',combindId:1, combineName:'n1'}
]
// 先自己写再看我下面给出的答案
function f(array){
const nameObj = Object.fromEntries(array.map(({id, name}) => [id, name]));
return array.map((v)=>Object.assign({},v,{combineName:nameObj[v.combineId]}));
}
4.源数组的每个元素都是一个简单 object,object 结构:{ id:number, name:string },id 不可能重复,要求以 id 为 key(键),name 为 value(值)生成一个 object。
如:
[
{id:1,name:'n1'},
{id:2,name:'n2'},
{id:3,name:'n3'}
]
=>
{
'1':'n1',
'2':'n2',
'3':'n3'
}
// 先自己写再看我下面给出的答案
function f(array){
return Object.fromEntries(array.map(({id, name}) => [id, name]));
}
5.源对象 object,由 number 类型的数据做 key,结构为 { name:string, type:string } 的对象做 value,要求写一个方法返回一个新 object,该 object 可以直接使用 number 数字查找到 value 的 name 属性值。源对象 object 不允许修改。
如:
{
'1':{name:'n1', type:'t1'},
'2':{name:'n2', type:'t2'},
'3':{name:'n3', type:'t3'}
}
=>
newObject
console.log(newObject[2]); //n2
console.log(newObject[1]); //n1
// 先自己写再看我下面给出的答案
function f(object){
return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, value.name]));
}
// 也可以使用proxy代理