深入理解javascript系列(十六):深入高阶函数

由于这两天,广州-东莞-惠州三日游,所以更新速度有所放慢...

前面我们说过,简单点理解高阶函数,则凡是接收一个函数作为参数的函数,就是高阶函数...

大神说,高阶函数是一个高度封装的过程,理解它需要一点想象力。所以本次就借助几个例子,来理解高阶函数的封装。

1.  数组map方法封装的思考过程

我们知道数组有一个map(映射)方法,它对数组中的每一项运行给定的函数,返回每次函数调用的结果并组成数组。简单来说,就是遍历数组的每一项,并且在map的第一个参数中进行运算处理后返回计算结果,最终返回一个由所有计算结果组成的新数组。

//声明一个遍历的数据array
var array = [1,2,3,4];

//map方法第一个参数为一个回调函数,该函数拥有三个参数
//第一个参数表示array数组中的每一项
//第二个参数表示当前遍历的索引值
//第三个参数表示数组本身
//该函数中的this指向map方法第二个参数,若该参数不存在,则this指向丢失

var newArray = array.map(function(item, i, array) {
    console.log(item, i, array, this) //这个this是什么?
    return item   1;
}, {a: 1})

//newArray为一个新数组,有map遍历的结果组成
console.log(newArray);

在上面的例子中,我们详细分析了map的所有细节。现在需要思考的是,如果要我们自己来封装这样一个方法,应该怎么办?

因为所有的数组遍历方法,其实都是在for循环基础之上封装的,因此我们可以从for循环开始考虑。

当然,一个for循环的过程其实很好封装,其难点在于,for循环里面对数组每一项所做的事情很难用一个固定的模式把他封装起来,在不同的场景下,for循环对数据的处理肯定是不一样的。那么应该怎么办呢?

在封装函数时,对于一个不确定的变量,我们可以用往函数中传入参数的方式来指定。如:

function add(a) {
    return a   10;
}

同样的道理,对于一个不确定的处理过程,我们可以用往函数中传入另外一个函数的方式来自定义这个处理过程。因此,基于这个思路,我们可以按照如下方式来封装map方法。

Array.prototype._map = function(fn, context) {
    //首先定义一个数组来保存每一项的运算结果,最后返回
    var temp = [];
    if(typeof fn == 'function') {
        var k = 0;
        var len = this.length;
        //封装for循环过程
        for(; k<len; k  ) {
            //将每一项的运算操作丢进fn里
            //利用call方法指定fn的this指向与具体参数
            temp.push(fn.call(context, this[k], k, this))
        }
    }else {
        console.error('TypeError: '  fn  ' is not a function.');
    }

    //返回每一项运算结果组成的新数组
    return temp;
}
 
var newArr = [1,2,3,4]._map(function(item) {
    return item   1;
})

回过头反思map方法的封装过程可以发现,其实我们封装的是一个数组的for循环过程。每一个数组在使用for循环遍历时,虽然无法确认在for循环中到底发生了什么,但是可以确定的是,它们一定会使用for循环。

因此我们把“都会使用for循环”这个公共逻辑封装起来,而具体要做什么事,则以一个函数作为参数的形式,来让使用者自定义。这个被作为参数传入的函数,就可以称之为基础函数。而我们封装的map方法,就可以称之为高阶函数。

高阶函数的使用思路正在于此,它其实是一个封装公共逻辑的过程。

假设我们正在做一个音乐社区的项目。

很显然,在进入这个项目的每一个页面时,都必须判断当前用户是否已经登录。因为登录与未登录所展示的页面肯定是有很多差别的。不仅如此,在确认用户登录之后,还需得到用户的具体信息,如昵称、姓名、VIP等级、权限范围等。

因此用户状态的判断逻辑,是每个页面都必须要做的一个公共逻辑,那么在学习了高阶函数之后,我们就可以用高阶函数来做这件事。

为了强化模块化思维,我们继续使用模块化的方式来完成这个例子。在这里,我们可以利用自执行函数来划分模块。

首先需要一个高阶函数来专门处理获取用户状态的逻辑,因此可以单独将这个高阶函数封装为一个独立的模块。

//高阶函数withLogin,用来判断当前用户状态
(function() {
    
    //用随机数的方式来模拟一个获取用户信息的方法
    var getLogin = function() {
        var a = parseInt(Math.random() * 10).toFixed(0));
        if(a % 2 == 0) {
            return {login: false}
        }
        
        return {
            login: true,
            userinfo: {
                nickname: 'pan',
                vip: 1,
                userid: 'music1111'
            }
        }
    }
    
    var withLogin = function(basicFn) {
        var loginInfo = getLogin();
        
        //将loginInfo以参数的形式传入基础函数中
        return basicFn.bind(null, loginInfo);
    }

    window.withLogin = withLogin;
})();

假设我们要展示主页,则可以通过renderIndex的方法来渲染。当然,渲染主页仍然是一个单独的模块。

(function() {
    var withLogin = window.withLogin;

    var renderIndex = function(loginInfo) {
        //这里处理index页面的逻辑

        if(loginInfo.login) {
            //处理已经登录之后的逻辑
        }else {
            //这里处理未登录的逻辑
        }
    }
    
    //对外暴露接口时,使用高阶函数包一层,来判断当前页面的登录状态
    window.renderIndex = withLogin(renderIndex);
})();

同样的道理,当我们想要展示其它页面,例如个人主页时,则可以使用renderPersonal方法,如下所示。

(function() {
    var withLogin = window.withLogin;
    var renderPersonal = function(loginInfo) {
        if(loginInfo.login) {
            //do something
        }else {
           // do other something
        }
    }
    
    window.renderPersonal = withLogin(renderPersonal);
})();

当我们使用高阶函数封装每个页面的公共逻辑之后,会发现我们的代码逻辑变得非常清晰,而且更加统一。当再写新的页面逻辑时,就在此基础之上完成即可,而不用再去考虑已经封装过的逻辑。

最后,在合适的时机使用这些渲染函数即可。

(function() {
    window.renderIndex();
})();

这些都是我以往的学习笔记。如果您看到此笔记,希望您能指出我的错误。有这么一个群,里面的小伙伴互相监督,坚持每天输出自己的学习心得,不输出就出局。希望您能加入,我们一起终身学习。欢迎添加我的个人微信号:Pan1005919589



更多专业前端知识,请上 【猿2048】www.mk2048.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值