由于这两天, 广州 - 东莞 - 惠州三日游, 所以更新速度有所放慢...
前面我们说过, 简单点理解高阶函数, 则凡是接收一个函数作为参数的函数, 就是高阶函数...
大神说, 高阶函数是一个高度封装的过程, 理解它需要一点想象力. 所以本次就借助几个例子, 来理解高阶函数的封装.
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
来源: https://juejin.im/post/5b27cfdaf265da59a90c14fc