资讯专栏INFORMATION COLUMN

《JS编程全解》—— 回调函数

mj / 487人阅读

摘要:事件驱动正是一种回调函数设计模式。由于不支持多线程,所以为了实现并行处理,不得不使用回调函数,这逐渐成为了一种惯例。上面的回调函数只是单纯的函数而不具有状态。如果回调函数具有状态,就能得到更为广泛的应用。

回调函数模式 回调函数与控制反转

回调函数是程序设计的一种方法。这种方法是指,在传递了可能会进行调用的函数或对象之后,在需要时再分别对其进行调用。由于调用方与被调用方的依赖关系与通常相反,所以也成为控制反转(IoC,Inversion of Control)。
由于历史原因,在JavaScript开发中我们常常会用到回调函数这一方法,这是多种因素导致的。第一个原因是在客户端JavaScript中基本都是GUI程序设计。GUI程序设计是一种很适合使用所谓事件驱动的程序设计方式。事件驱动正是一种回调函数设计模式。客户端JavaScript程序设计是一种基于DOM的事件驱动式程序设计。
第二个原因是,源于客户端无法实现多线程程序设计(最近HTML5 Web Works支持多线程了)。而通过将回调函数与异步处理相结合,就能够实现并行处理。由于不支持多线程,所以为了实现并行处理,不得不使用回调函数,这逐渐成为了一种惯例。最后一个原因与JavaScript中的函数声明表达式和闭包有关。

JavaScript与回调函数
    var emitter = {
        // 为了能够注册多个回调函数而通过数组管理
        callbacks:[],
        // 回调函数的注册方法
        register:function (fn) {
            this.callbacks.push(fn);
        },
        // 事件的触发处理
        onOpen:function () {
            for (var f in this.callbacks) {
                this.callbacks[f]();
            }
        }
    };
    emitter.register(function () {alert("event handler1 is called");})
    emitter.register(function () {alert("event handler2 is called");})
    
    emitter.onOpen();    
    // "event handler1 is called"
    // "event handler2 is called"

定义的两个匿名函数就是回调函数,它们的调用由emitter.onOpen()完成。

 对emitter来说,这仅仅是对注册的函数进行了调用,不过根据回调函数的定义,更应该关注使用了emitter部分的情况。从这个角度来看,注册过的回调函数与之形成的是一种调用与被调用的关系。

上面的回调函数只是单纯的函数而不具有状态。如果回调函数具有状态,就能得到更为广泛的应用。下面我们把回调方改为了对象,于是emitter变为了接受方法传递的形式。

    function MyClass(msg) {
        this.msg = msg;
        this.show = function () {alert(this.msg+" is called");}
    }
    // 将方法注册为回调函数
    var obj = new MyClass("listener1");
    var obj2 = new MyClass("listener2");
    emitter.register(obj.show);
    emitter.register(obj2.show);
    
    emitter.onOpen();
    // undefined is called
    // undefined is called

我们发现,调用回调函数无法正确显示this.msg,错误原因在于JavaScript内的this引用。解决方法有两种,一种是使用bind,一种是不使用方法而是用对象进行注册。后者在JavaScript中并不常用。

    emitter.register(obj.show.bind(obj));
    emitter.register(obj2.show.bind(obj2));
    
    emitter.onOpen();
    // "listener1 is called"
    // "listener2 is called"

bind是ES5新增的功能,是Function.prototype对象的方法。bind的作用和apply与call相同,都是用于明确指定出方法调用时的this引用。对于函数来说,调用了bind之后会返回一个新函数,新的函数会执行与原函数相同的内容,不过其this引用是被指定为它的第一个参数的对象。在调用apply与call时将会立即调用目标函数,而在调用bind时则不会如此,而是会返回一个函数(闭包)。
如果使用了apply或call,就能对bind进行独立的实现。事实上在ES5才推出之前,在prototype.js等知名的库中就通过apply/call提供了bind自己的实现。

脑补的bind的内部实现?

    Function.prototype.bind = null;
    Function.prototype.bind = function (obj) {
        var f = this;
        return function () {
            f.call(obj);
        }
    }
    var obj = {
        x:"这是    obj.x    !!!",
        fn:function () {
            alert(this.x);
        }
    };
    var obj2 = {x:"obj2.x    对啦!!!"};
    
    var testfn = obj.fn.bind(obj2);
    testfn();    // "obj2.x    对啦!!!"

闭包与回调函数

    emitter.register(
        (function () {
            var msg = "closure1";
            return function () {alert(msg+" is called;")};
        }())
    );
    emitter.register(
        (function () {
            var msg = "closure2";
            return function () {alert(msg+" is called;")};
        }())
    )
    
    emitter.onOpen();
    // "closure1 is called"
    // "closure2 is called"

借助闭包,前面繁复的说明仿佛不在存在,可以很轻松的实现回调函数,并且还能像对象一样具有状态。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83473.html

相关文章

  • JavaScript编程全解 —— 基础

    摘要:函数式编程最后介绍一下函数式编程。函数式编程是一种历史悠久,而又在最近颇为热门的话题。函数式编程在面向对象一词诞生以前就已经存在,不过它在很长一段时间里都被隐藏于过程式编程面向对象也是过程式编程的一种的概念之下。 2.1 JavaScript特点 总结以下几个特点: 解释型语言 类似与C和Java的语法结构 动态语言 基于原型的面向对象 字面量的表现能力 函数式编程 解释型语言:...

    CoreDump 评论0 收藏0
  • JavaScript 中优雅的实现顺序执行异步函数

    摘要:顺序执行异步函数异步为带来非阻塞等优势的同时,同时也在一些场景下带了不便,如顺序执行异步函数,下面总结了一些常用的方法。 火于异步 1995年,当时最流行的浏览器——网景中开始运行 JavaScript (最初称为 LiveScript)。 1996年,微软发布了 JScript 兼容 JavaScript。随着网景、微软竞争而不断的技术更新,在 2000年前后,JavaScript ...

    monw3c 评论0 收藏0
  • html-webpack-plugin用法全解

    摘要:所有的这些都是的功劳。默认为根据自己的指定的模板文件来生成特定的文件。最终在文件夹内会生成一个和文件。属性值为文件所在的路径名。默认值为不对生成的文件进行压缩。选项的作用主要是针对多入口文件。不用说,按照不同文件的依赖关系来排序。 本文只在个人博客和 SegmentFault 社区个人专栏发表,转载请注明出处 个人博客: https://zengxiaotao.github.io ...

    JinB 评论0 收藏0
  • 前端20个真正灵魂拷问,吃透这些你就是中级前端工程师 【上篇】

    摘要:还是老规矩,从易到难吧传统的定时器,异步编程等。分配对象时,先是在空间中进行分配。内存泄漏内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 showImg(https://segmentfault.com/img/bVbwkad?w=1286&h=876); 网上参差不弃的面试题,本文由浅入深,让你在...

    mdluo 评论0 收藏0
  • 前端20个真正灵魂拷问,吃透这些你就是中级前端工程师 【上篇】

    摘要:还是老规矩,从易到难吧传统的定时器,异步编程等。分配对象时,先是在空间中进行分配。内存泄漏内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 showImg(https://segmentfault.com/img/bVbwkad?w=1286&h=876); 网上参差不弃的面试题,本文由浅入深,让你在...

    leap_frog 评论0 收藏0

发表评论

0条评论

mj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<