最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • this指向初探,实现new bind apply call

    正文概述 掘金(violetrosez)   2021-03-28   475

    前言

    this指向分为默认绑定,隐式绑定,显式绑定,new操作,箭头函数这几类,经常使用的改变this指向的方法就是call,apply, bind,这些我们经常用到的方法你是否对其实现方式好奇呢?其实搞懂这些并不难,我认为首先必须知道函数内部做了什么事情,他解决的是什么问题,然后我们才能针对性地去探索。

    new

    首先是最常见的new,new并不是一个函数,是一个运算符,用于创建一个对象的实例,这应该是大家熟知的。用法,new后面跟一个构造函数:

    new constructor[([arguments])]
    

    看一下MDN上的解释 new 关键字会进行如下的操作:

    1. 创建一个空的简单JavaScript对象(即{});
    2. 链接该对象(设置该对象的constructor)到另一个对象 ;
    3. 将步骤1新创建的对象作为this的上下文 ;
    4. 如果该函数没有返回对象,则返回this。

    哟西,既然如此我们就围绕上面四点,动手肝一个myNew函数

    function myNew() {
      // 创建一个空的简单JavaScript对象(即{});
      let obj = new Object()
      // 链接该对象(设置该对象的constructor)到另一个对象 --> 就是改变obj原型的指向
      // arguments是一个类数组,所以要这么调shift
      let con = [].shift.call(arguments)
      obj.__proto__ = con.prototype
      // 将步骤1新创建的对象作为this的上下文 ;
      // 如果该函数没有返回对象,则返回this。
      let res = con.apply(obj, arguments)
      // 如果构造函数person return了一个对象类型,就取return的值
      return typeof res == 'object' ? res : obj
    }
    
    function person(name) {
      this.name = name
    }
    
    let p = myNew(person, 'jack')
    
    1. 没有单独的this
    2. 不绑定arguments
    3. 箭头函数没有prototype属性

    结合上述的代码实现,是不是清晰了一些呢?

    我们还可以对代码进行优化,使用这种方式来改变和继承属性是对性能影响非常严重的,还会影响到所有继承来自该[[Prototype]]的对象,更优的方案是: Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

    let obj = Object.create(con.prototype) //
    

    instanceOf

    引用自mdn:

    instanceof的判断逻辑是: 从当前引用的proto一层一层顺着原型链往上找,能否找到对应的prototype。找到了就返回true。挺简单就可以实现一个简易的instanceof

    /*obj 实例  con 构造函数*/
    function _instanceOf(obj,con) {
        let _obj = obj.__proto__
        let _con = con.prototype
        while(true) {
            if(_obj === null) {
                return false
            }
    
            if(_obj === _con) {
                return true
            }
    
            _obj = _obj.__proto__
        }
    }
    

    缺点: 不能完全精确的判断复杂类型的具体数据类型

    • [] instanceof Array; //true
    • [] instanceof Object; //true

    Bind

    先看看官方的例子:

    const module = {
      x: 42,
      getX: function() {
        return this.x;
      }
    };
    
    const unboundGetX = module.getX;
    console.log(unboundGetX()); // The function gets invoked at the global scope
    // expected output: undefined
    
    const boundGetX = unboundGetX.bind(module);
    console.log(boundGetX());
    // expected output: 42
    

    由上述例子我们可以知道,使用bind作用有二:

    1. 返回一个新的函数,给函数添加运行时参数,能使一个函数拥有预设的初始参数,倒是有点像es6的默认参数的作用。
    2. 显示绑定this值 ,这解决了绑定隐式丢失问题, 即函数中的 this 丢失绑定对象。例如:使用setTimeout,常见情况时执行时this指向 window。当使用对象的方法时,需要 this 引用对象,你可能需要显式地把 this 绑定到回调函数以便继续使用对象。例如: setInterval(obj.fn.bind(obj), 1000);

    基此,我们可以尝试实现一个简单的bind:

    function _bind() {
        let fn = this //需要绑定的函数
        let args = Array.from(arguments) //类数组 -> 数组
        let obj = args.shift() //绑定的对象
    
        return function () {
            fn.apply(obj, Array.from(args).concat(Array.from(arguments)))
        }
    }
    Function.prototype._bind = _bind
    function fn(a, b) {
        console.log(this);  // obj
        console.log(a + b); // 3
    }
    let obj = {
        name: 'violetrosez'
    }
    let _fn = fn._bind(obj, 1)
    _fn(2)
    

    上述代码,看似满足了需求,但是当返回的函数被作为构造函数时,原函数调用的this指向了bind显示指定的对象,不能根据new的调用而绑定到new创建的对象。当被作为构造函数调用时,我们需要将绑定函数的作用域赋给新对象,并设置绑定函数继承目标函数的原型。修改后如下:

    function _bind() {
        let fn = this //函数
        if (typeof fn !== 'function') {
            throw new TypeError('what is trying to be bound is not callable');
        }
        let args = Array.from(arguments)
        let obj = args.shift()
    
        let fNOP = Object.create(fn.prototype)
        let fBound = function () {
            //如果没有判断,构造函数test在执行时也指向obj,而不会指向新建的实例p,此时p.name == undefined
            fn.apply(this instanceof fn ? this : obj, Array.from(args).concat(Array.from(arguments)))
        }
        //使fBound.prototype是fN的实例,返回的fBound若作为new的构造函数,新对象的__proto__就是fN的实例
        fBound.prototype = fNOP
        return fBound
    }
    
    //测试
    function test(name) {
        this.name = name
    }
    let obj = {}
    let _fn = test._bind(obj)
    _fn('violetrosez')
    console.log(obj.name);  // violetrosez
    let p = new _fn('zzzzz') 
    console.log(obj.name); // violetrosez
    console.log(p.name);  // zzzzz
    
    

    call

    已经有了上面的基础,我们再写这个有点得心应手了,过程都是一个思路,对参数进行处理

    function _call(ctx, ...args) {
        if (typeof this !== 'function') {
            throw new TypeError('what is trying to be bound is not callable');
        }
        ctx = ctx || window
        ctx.fn = this
    
        let res = ctx.fn(...args)
        delete ctx.fn //删除掉引用
        return res
    }
    
    Function.prototype._call = _call
    

    上面的做法其实是将 this 的默认绑定改为隐式绑定,ctx不存在的时候,我们使ctx指向全局对象,然后将函数作为要绑定的对象的一个方法执行,用完后删掉。这次我们使用es6剩余操作符处理参数,写法比上文更简洁了一些。

    apply

    apply和call唯一不同的就是他的剩余参数接收的是一个数组,所以把上面的代码改造一下即可。之前一直记混哪个是接受数组,后面干脆把apply的首字母a当成array去记忆了..

    function _apply(ctx, args = []) {
        if (typeof this !== 'function') {
            throw new TypeError('what is trying to be bound is not callable');
        }
        if(args && !Array.isArray(args)) {
            throw new TypeError('apply need accept array object');
        }
        ctx = ctx || window
        ctx.fn = this
    
        let res = ctx.fn(...args)
        delete ctx.fn //删除掉引用
        return res
    }
    
    Function.prototype._apply = _apply
    

    后记

    至此,我们完成了实现call、apply 和 bind的过程,其实这些本质上都是要改变 this 的指向,在实现过程中一定要时刻搞清楚this的指向,写代码的过程中,什么时候可以用箭头函数,什么时候需要显示绑定this,一定要心中有数。理解了原理之后,就能明白 this 的绑定顺序为什么是 new > 显示绑定 > 隐式绑定 > 默认绑定。 如有疑问或者错误,请各位批评指正,共同进步。求点赞三连QAQ


    起源地下载网 » this指向初探,实现new bind apply call

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元