JavaScript设计模式与开发实践:高阶函数(一)

是什么

高阶函数是指至少满足下列条件之一的函数:

  1. 函数可以作为参数被传递:回调函数、Array.prototype.sort
  2. 函数可以作为返回值输出:判断数据的类型、getSingle

判断数据类型

在这个例子里,要判断一个数据的类型,使用Object.prototype.toString进行计算。该函数的返回值如下:

Object.prototype.toString.call([6,6,6])  // "[object Array]"
Object.prototype.toString.call("str")  // "[object String]"
Object.prototype.toString.call(66)  // "[object Number]"

可以使用循环语句,来批量注册判断类型的isType函数,如下:

var Type = {};
for(var i = 0, type; type=['String', 'Array', 'Number'][i++]; ) {
    (function(type) {
        Type['is'+type] = function(obj) {
            return Object.prototype.toString.call(obj) === '[object ' + type +']';
        } 
    })(type)
};
console.log(Type.isArray([])); //true
console.log(Type.isString("str")); //true

简单解释一下这个程序,type是数组['String', 'Array', 'Number']中的第i个元素,并作为参数传递到循环内立即执行的函数表达式。这个函数表达式的每次执行都给Type对象注册一个['is'+type]的函数。

getSingle

这是个单例模式的例子,后续的设计模式系列博客里会介绍,这里只简单说一说。

var getSingle = function(fn) {
    var ret;
    return function() {
        return ret || (ret = fn.apply(this, arguments));
    };
};

//效果
var getScript = getSingle(function() {
    return document.createElement("script");
});
var script1 = getScript();
var script2 = getScript();
console.log(script1 === script2);

在getSingle这个高阶函数中,既把函数当做参数传递,有在函数执行后返回另一个函数。

在这个函数里,有几个问题需要明确:

  1. 函数里的this指的是什么:我们在getSingle中显示返回了一个对象(函数),返回的这个函数作为普通函数被调用时,this指向全局对象,在浏览器里就是window.
  2. arguments到底是哪个函数的参数:arguments是getSingle里被返回的那个函数的参数,在这个程序里,getScript指向被返回的函数,所以arguments是getScript函数被调用执行的时候的参数。
  3. 两次执行getScript函数得到的结果分别是什么:
    1. 首先明确的是这是一个闭包结构,所以getScript的每次执行都能访问到函数定义时所在的作用域里的ret变量。
    2. 函数第一次执行的时候,由于ret是undefined的,所以执行的是逻辑表达式(||)的第二部分,也就是作为参数传递进去的fn函数被执行,并把执行结果报存在ret里。
    3. 所以,函数第二次执行的时候,由于ret里已经有值了,所以会直接返回ret的值,而不会再次执行fn函数。当然,以后的每次执行也都是直接返回ret里已经报错的值。

高阶函数实现AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能(日志记录、安全控制、异常处理等)抽离出来,再以“动态织入”的方式掺入业务逻辑模块中。这样可以保持业务逻辑模块的纯净和高内聚性,而且可以很方便的复用日志记录能功能模块。

在JS中实现AOP,是指把一个函数“动态织入”另一个函数中,下面是通过扩展Function.prototype来实现的方法,这是一种装饰者模式,以后也会介绍。

Function.prototype.before = function(beforefn) {
    var _self = this; //保存原函数的引用
    return function() {  //返回包含了原函数和新函数的“代理”函数
        beforefn.apply(this, arguments);  //执行新函数,修正this
        return _self.apply(this, arguments); //执行原函数
    }
};

Function.prototype.after = function(afterfn) {
    var _self = this;
    return function() {
        var ret = _self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
};

var func = function() {
    console.log(2);
};

func = func.before(function() {
    console.log(1);
}).after(function() {
    console.log(3);
});

func();

需要明确的问题

在这个函数里,我们需要弄清楚before和after里的_self分别指什么,返回的函数是什么样的,最后的func函数又变成了什么样。

  1. _self的指向

    1. before函数里,_self指向before函数的调用者(this作为对象的方法调用时的绑定规则),也就是原始的func函数,如下:

      function() {
        console.log(2);
      };
      
    2. 同理,after函数里,_self也指向after函数的调用者,也就是before函数返回的结果,详见下面的分析。

  2. 返回的函数

    1. before函数的返回函数如下:

      function () { 
        beforefn.apply(this, arguments); //this指向window
         return _self.apply(this, arguments); 
      }
      //由于_self执行原始的func函数,所以相当于:
      function () { 
        beforefn.apply(this, arguments); //console.log(1)
        return func.apply(this, arguments);  //console.log(2)
      }
      
    2. after函数的返回函数如下:

      function () {
        var ret = _self.apply(this, arguments); //_self指向before的返回函数
        afterfn.apply(this, arguments); //console.log(3)
        return ret;
      }
      
  3. func函数

    根据上面的分析可知,func函数最后会变成下面的样子:

    function () { 
     beforefn.apply(this, arguments); //console.log(1)
     func.apply(this, arguments);  //console.log(2)
         afterfn.apply(this, arguments); //console.log(3)
    }
    
  4. 最后的疑问?

    不明白为什么before函数和after函数里最后都有一个return语句,如果有明白的朋友,请告诉我,不胜感激!

    我测试过了,去掉return语句之后,函数的执行结果并不会改变,而且我觉得语义还更清晰了呢。


笔者才疏学浅,如有错误,欢迎联系我批评指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容