js 高程 22.1.4 函数绑定 bind() 封装分析

js 高程 书中原话(斜体表示):

22.1.4 函数绑定
另一个日益流行的高级技巧叫做函数绑定。函数绑定要创建一个函数,可以在特定的this 环境中
以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量
传递的同时保留代码执行环境。请看以下例子:

var handler = {
    message: "Event handled",
    handleClick: function(event) {
        console.log(this);           //改编,方便测试,原文是下面一句
        //alert(this.message);        
    }
};
document.body.onclick=handler.handleClick;       //改编,方便测试,原文是后面两句
//var btn = document.getElementById("my-btn");
//EventUtil.addHandler(btn, "click", handler.handleClick);

看起来,当我们点击 body 的时候,应该输出 handler 这个对象,但是实际上输出的是 body 元素

image

为什么呢?书上原话:

这个问题在于没有保存 handler.handleClick() 的环境,所以 this 对象最后是指向了 DOM 按钮而非 handler

我理解就是就是,上面的事件绑定代码其实就相当于:

document.body.onclick = function(event) {
    console.log(this);
};

那么这个地方,事件绑定在谁身上, this 当然就指向谁(事件处理程序中的 this 不清楚的点这里),所以这里输出 body 对象也是很合理的,如果要输出 handler 对象,单独执行 handler.handleClick() 就可以了

image

这里也很好理解,handleClick 作为 handler 对象的方法执行的,那么其中的 this 当然指向的是 handler,结合这种情况,要改上面的代码就要这样:

document.body.onclick = function(event) {
    console.log(this);  //这里的 this 当然还是 body
    handler.handleClick(); // 但是这里面执行的 this 就是 handler 对象了
};

image

但是这样写的缺点很明显,原本就是想通过下面一行代码实现的,如图:

document.body.onclick=handler.handleClick; //最初
document.body.onclick = function(event) {  //现在
     handler.handleClick();
};

现在明显繁琐了些,而且最初的一行代码理论上也应该是可以实现的,所以现在的代码需要优化封装下,来达到同样的效果,封装的时候,要实现两个目的

  1. 调用函数的时候执行 handler.handleClick(); 且要保存它的执行环境(也就是要确定执行时的 this ,指向的是 handler,而不会变成其他的对象)
  2. 要把 event 对象传递过去

看到需要改变 this 和保存执行环境的时候,应该想到 js 的函数都有内置的 apply(),call()方法,且 arguments 对象都保存了函数的参数(这个不理解可以看 这里),所以这里封装函数也不是很难了,假设封装的函数名叫 bind ,整个封装逻辑就是下面这样:

    //执行 bind 函数需要返回的匿名函数
    function(event) {
        handler.handleClick();
    };
    //抽象化
    function(event) {
        fn();
    }
    //推算 bind 函数的原型(此原型非js中的原型)
    function bind(fn) {
        return function(event) {
            fn();
        };
    }
    //为了保证 this 的正确传递,需要用 apply 方法,这里用法不理解,请看 这里
    function bind(fn,context) {
        return function(event) {
            fn.apply(context,参数);
        };
    }    
    //为了传递 event 参数,这里的参数不太好理解,需要好好想想
    function bind(fn,context) {
        return function() {
            fn.apply(context,arguments);
        };
    }

书上原文的结果也是一样的:

image

bind 函数 推导出来后,就简单了,原来的代码就变成了

document.body.onclick=handler.handleClick; //最初错误代码
document.body.onclick = function(event) {  //后来改善代码
     handler.handleClick();
};
document.body.onclick = bind(handler.handleClick,handler); //最后封装 bind 函数

点击 body 后,就返回的 handler 对象,这样就可以访问 handler 的属性了;

image

不过,ES5 已经为所有函数定义了一个原生的 bind() 方法,进一步简单了操作,像上面的代码用 ES5 实际上就是一句

document.body.onclick = handler.handleClick.bind(handler); 

所以前面都是废话,- -

原文地址:https://www.cnblogs.com/xianshenglu/p/8044012.html