前端进击的巨人(四):略知函数式编程

40次阅读

共计 3858 个字符,预计需要花费 10 分钟才能阅读完成。

系列更文前三篇文章,围绕了一个重要的知识点:” 函数 ”。函数调用栈、函数执行上下文、函数作用域到闭包。可见不理解函数式编程,代码都撸不好。

 函数是一等公民
函数与其它数据类型一样,可以作为值赋给变量,作为参数传递或返回值返回,也可以像对象一样给函数创建属性(不推荐给函数加属性,虽然可用)。
函数在实际开发中应用:

函数声明
函数表达式
匿名函数
自执行函数

// 函数声明
function getName() {
//…
}

// 函数表达式
var getName = function() {
//…
}

// 匿名函数
setTimeout(function(){
//…
}, 1000);

// 自执行函数
(function(){
//…
})();
何为一等:优先级
函数声明在 ” 执行上下文创建阶段 ” 就会进行声明并赋值,而 var 声明变量会初始化为 undefined,实际赋值会等到 ” 执行上下文执行阶段 ”。函数表达式使用 var 来声明,因此它遵循的是变量声明的规则。(如果函数名与变量重名,函数优先赋值)
“ 函数声明优先级高于变量声明,函数表达式,自称一等公民。”
// 代码书写:
console.log(getName);
getName();
var getName;
getName = ‘ 我的名字 ’;
function getName(){
//…
}
console.log(getName);

// 实际执行
var getName; // 变量名与函数名重名,函数优先赋值
function getName() {
//…
}
console.log(getName);
getName();
getName = ‘ 我的名字 ’;
console.log(getName);
函数式编程
函数式编程是一种编程思维方式,它建议我们在程序编写时,对复用性高的功能代码进行函数封装,实现代码的高复用性。
新手朋友往往是一块代码多次出现在不同的地方,常见的例子就是 ajax 请求方法运用,在需要请求后端数据时多次出现一串 ajax 请求代码。
如果想要对 ajax 请求统一做异常处理,或管理后端返回状态码,是不是每处代码都要修改???但是如果把 ajax 请求代码封装成一个函数,接口 url 和数据 data 通过参数传递到函数内部处理,后期扩展维护都方便修改,复用性扩展性都更加优秀。
所以实际敲代码过程中,要经常提醒自己运用函数式编程的思维方式,只要有可能出现多次的业务逻辑代码,那么就要考虑是否封装成函数,以便后续统一调用。
function sumScore(list) {
var totalScore = 0
for (var i = 0; i < list.length; i++) {
totalScore += list[i];
}
return totalScore;
}

var list = [10, 8, 9, 7];
var totalScore = sumScore(list); // 计算总分
TIPS: 函数名建议使用动词,如 addUser(),sumScore(),getUser()…
纯函数
纯函数:相同的输入对应相同的输出,稳定没有副作用(不改变外部变量的值)
相同的输入,相同的输出
相同的参数传入调用,要有相同的结果输出,概念有点绕,上代码栗子:
function getDate() {
return new Date();
}
var dateOne = getDate();
var dateTwo = getDate();
var dateThr = getDate();
上述代码中调用了三次 getDate(),三次返回的值都不一样。相同的输入并没有相同的输出,所以 getDate() 并不是一个纯函数。
TIPS:函数中使用 new Date(),Math.random(), 异步等都可能造成函数不稳定。
没有副作用(不改变外部环境的值)
部分小伙伴的代码,在函数里面直接修改参数的值,这是一种非常不推荐的做法,这样做会造成代码环境不可控制,污染外部变量环境,一旦出现错误排查起来:心累,三个字心好累。
函数有自己的局部作用域,因此函数中,对需要使用到的变量,管控在自身的作用域下。如果需要修改外部参数的值,通过函数返回值返回给函数调用者。修改外部参数值的操作不在函数内进行,确保对外部环境没有副作用。
TIPS:参数为引用类型时,参数复制的是地址指针,避免修改了引用类型中属性值污染外部环境,如需使用建议手动深拷贝赋值。
function getGirlGift(list) {
// 避免污染参数为引用类型的 list,对 list 深拷贝
var newList = JSON.parse(JSON.stringify(list));
newList.map(girl => {
girl.gift = girl.age > 18 ? ‘lipstick’ : ‘chocolates’;
});
return newList; // 返回新值
}

var girlList = [
{name: ‘Kelly’, age: 20},
{name: ‘Alic’, age: 16},
{name: ‘Moon’, age: 23},
{name: ‘Nana’, age: 17}
];

var girlGiftList = getGirlGift(girlList);
girlList // 原用 girlList 不变
girlGiftList // 每个 girl 多了 gift 属性
Array 对象的函数(纯与不纯)
// 不纯的函数
array.push(); // 数组尾部插入
array.pop(); // 删除并返回数组最后一个元素
array.unshift(); // 数组头部插入
array.shift(); // 删除并返回数组第一元素
array.splice(); // 删除元素,并向数组添加元素
array.reverse(); // 颠倒数组元素的顺序
array.sort(); // 排序数组元素

// 纯函数
array.slice(); // 数组中返回选定的元素
array.concat(); // 连接数组,并发挥新数组
array.join(); // 按分隔符连接数组,返回字符串
>> 更多 Array 对象方法,参考 W3C
纯函数的应用:状态管理 Redux,Vuex
流行框架中状态管理就是纯函数的实践应用,引用 redux 的应用,reducer 中返回新的状态数据 state,但不能去直接去修改 state 数据,以下为 redux 中 reducer 的例子代码:
export default (state = defaultState, action) => {
let newState = JSON.parse(JSON.stringify(state));

switch (action.type) {
case DELETE_TODO_ITEM:
newState.list.splice(action.value, 1);
break;
case ADD_TODO_ITEM:
if (newState.inputValue.trim().length) {
newState.list.push(newState.inputValue);
}
newState.inputValue = ”;
break;
case INIT_LIST_ACTION:
newState = action.data
break;
default:
break;
}

return newState;
}
“ 自执行函数 + 闭包 ” 实现模块化
模块化包括:

私有变量
私有方法
公有变量
公有方法

上篇中《前端进击的巨人(三):从作用域走进闭包》我们讲解了作用域、闭包的原理机制。
“ 自执行函数可实现块级作用域,而闭包则可实现外部环境对函数作用域内部数据的访问。”
// 自执行函数 + 闭包实现模块化
(function MakeModule(window) {
var name = ‘ 以乐之名 ’;
var age = 28;
var job = ‘ 程序员 ’;

function changeJob(newJob) {
job = newJob;
}

function getName() {
return name;
}

window.modulePublic = {
changeJob: changeJob,
getName: getName
}
})(window);

window.modulePublic.getName();
window.modulePublic.changeJob(‘ 产品经理 ’);
对作用域,以及闭包知识还没掌握的小伙伴,可回阅《前端进击的巨人(三):从作用域走进闭包》。
高阶函数
高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回
JavaScript 中常用的高阶函数:

Array.prototype.map(映射遍历)

Array.prototype.filter(过滤)

Array.prototype.reducer(累计)

除了内置的高阶函数,我们实际开放中,高阶函数应用的最多就是回调函数了。
function getOrder(url, datas, callBack) {
return $.post(url, datas, callBack(orderInfo));
}

// getOrder 就是一个高阶函数,接收 callBack 函数作为参数
高阶函数的概念很简单,” 本身是函数,参数是函数,或返回值是函数 ””。

参考文档:

JavaScript 利用闭包实现模块化
【翻译】– 浅谈 JavaScript 中的高阶函数

系列更文请关注专栏:《前端进击的巨人》,不断更新中。。。
本文首发 Github,期待 Star!https://github.com/ZengLingYong/blog
作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。

正文完
 0