• this硬绑定


    一、this显式绑定

    this显式绑定,顾名思义,它有别于this的隐式绑定,而隐式绑定必须要求一个对象内部包含一个指向某个函数的属性(或者某个对象或者上下文包含一个函数调用位置),并通过这个属性间接调用这个函数,从而把this间接/隐式绑定到这个对象上。但是this的隐式绑定存在一个绑定对象丢失问题,如下面代码所示:

    function afun() {
        console.log(this.a);
    }
    
    var obj = {
        a: 1,
        afun: afun
    };
    
    var a = "hello";
    
    setTimeout(obj.afun, 100);//"hello"
    
    

    出人意料的是,控制台打印出来的结果是全局变量a的结果,而不是拥有指向函数属性的对象的a的值,这就是隐式绑定的对象丢失,回调函数丢失绑定对象是非常常见的。

    但是,显式绑定仍然不能解决绑定对象丢失的问题,但是显式绑定的一个变种,即硬绑定可以解决绑定对象丢失。

    在此之前,我们先来看看什么是显式绑定。

    我们可以通过使用函数的call()和apply()方法实现this显式绑定,绝大多数内置函数和自定义函数都可以调用这两种方法。他们均接收两个参数

    • 参数:
      • thisArg: 要绑定到调用者this的对象。
        • arg1, arg2, ...(call):指定的参数列表,即要传给调用者的参数。比如a.call(obj, args)/a.apply(obj, args),args为参数传给调用者函数a作为该函数的实参。
        • argsArray(apply):一个数组或者类数组对象,如果该参数的值为 nullundefined,则表示不需要传入任何参数。
      • 返回值:调用者函数若有返回值则返回该值,没有返回值则返回undefined。

    来看下面的代码:

    function bfun() {
        return this.a;
    }
    
    var obj1 = {
        a: "hello"
    };
    
    console.log(bfun.apply(obj1), bfun.call(obj1));//"hello" "hello"
    

    若传入的第一个参数时一个原始值呢?来看下面的代码:

    function bfun() {
        return this;
    }
    
    
    console.log(bfun.apply(3));//[Number: 2]
    

    没错,原始值被转换成了它的对象形式,也就是new Number(),这被成为"装箱"。

    但是,我们前面提到过,this显式绑定虽然强大,但是仍然不能解决this绑定丢失问题。下面我们来解释硬绑定,即显式绑定的一个变种,它能完美解决this绑定丢失问题。

    二、硬绑定

    先来看看下面的代码:

    function cfun() {
        console.log(this.a)//"hello"
        return this.a;
    }
    
    var obj2 = {
        a: "hello"
    };
    
    var fn = function() {
        return cfun.apply(obj2);
    };
    console.log(fn());//"hello"
    
    setTimeout(fn, 100);//"hello"
    

    可以看到,硬绑定确实解决了this绑定丢失,但值得注意的是,通过apply()绑定的this对象,无法二次更改绑定对象:

    function f() {
    console.log( this.a );
    }
    var obj = {
    a:2
    };
    var b = function() {
    f.call( obj );
    };
    b(); // 2
    
    b.call( window ); // 2
    

    硬绑定的一个典型应用场景是创建一个包裹函数,传入所有的参数给调用者函数并返回接收到的所有值。

    function dfun(v) {
        return (v[0] + this.a);
    }
    
    var obj3 = {
        a: 10
    };
    
    var fn1 = function() {
        return dfun.call(obj3, arguments);
    }
    
    var result = fn1(6);
    console.log(result);//16
    

    对于arguments而言,call()和apply()的不同之处在于他们的参数类型不同:

    function efun(v) {
        console.log(..v)//6 10 11
        return (v);
    }
    
    var obj4 = {
        a: 10
    };
    
    var fn1 = function() {
        return efun.apply(obj3, arguments);
    }
    
    var result = fn1([6, 10, 11]);
    console.log(result);//[6, 10, 11]
    

    也就是说call()的参数类型是Object,它传入的参数列表会被转换为键为'0'(随传入参数数量递增),值为传入参数的对象;而apply()的参数类型则是数组或者类数组。

    function dfun(v) {
        return (v);
    }
    
    var obj3 = {
        a: 10
    };
    
    var fn1 = function() {
        return dfun.call(obj3, arguments);
    }
    
    var result = fn1(6, 19, 1, 1);
    console.log(result);//[Arguments]{'0':6,'1':19,2':1,3':1}
    console.log(typeof result);//'object'
    

    最强大的一种方法是将包裹函数创建为可以重复使用的辅助函数,封装可重复使用的硬绑定。

    function ffun(v) {
        return this.a * v;
    }
    
    var obj5 = {
        a: 5,
    };
    
    var fn2 = function(fn, obj) {
        return function() {
            return fn.apply(obj, arguments);
        }
    }
    
    var bind = fn2(ffun, obj5);
    console.log(bind(10));//50
    

    由于硬绑定十分常用,但通过包裹函数创建可重复使用的硬绑定比较麻烦,所以ES5提供了一个实现相同功能的方法bind()。用法如下:

    function hfun(v) {
        return this.a * v;
    }
    
    var obj6 = {
        a: 6
    };
    
    var bind = hfun.bind(obj6);
    console.log(bind(4));//24
    

    可以看到,我们再无需构建一个包裹函数来手动调用call或apply方法,只需要提供调用者和绑定到调用者this的对象即可。

    我们可能发现了一个奇怪的现象,通过apply()和call()方法绑定的对象在传参给调用者时,需要设置一个参数占位,但bind()方法则不用,这是因为他们的返回值不同,bind()方法会返回一个经过硬编码的新函数,它会把传入参数设置为this的上下文并调用原始函数。可以理解bind使用方法为bind(obj)(args)。而对于call()和apply()方法而言,一旦调用此方法,就会立刻返回调用者函数的返回值,所以此时就需要同时传入参数给方法的参数占用符,然后被函数参数读取。值得注意的是,bind方法的参数和call方法类似。


    __EOF__

  • 本文作者: 墨戈
  • 本文链接: https://www.cnblogs.com/mogebw/p/16768934.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    centos 内核对应列表 内核升级 linux
    数仓范式建模、ER实体建模和维度建模
    分享股票数据的api接口设计文档的代码
    HTML(28)——空间转换
    【老生谈算法】matlab实现Kmeans聚类算法源码——Kmeans聚类算法
    WebUI文件拖拽上传与下载文件
    电话号码的字母组合
    对工厂模式一次感悟
    tcpdump(五)命令行参数讲解(四)
    【数据结构与算法】顺序表的基本操作实现
  • 原文地址:https://www.cnblogs.com/mogebw/p/16768934.html