zoukankan      html  css  js  c++  java
  • 浅析这句经常在框架中出现的JS代码加深对bind的理解

      call、bind这类方法我们虽然在平时开发中用到的不多,但是在看框架源码时,我们会经常看到。比如我们经常在框架级的源码中看到类似如下的一句代码:

    var toStr1 = Function.prototype.call.bind(Object.prototype.toString);

      在这一句代码中既使用call方法,同时也使用bind方法,乍看之下,有点晕!这到底是想干嘛?

      无妨,我们调用看看,传入不同的类型试试,效果如下:

    var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
    console.log(toStr1({}));      // "[object Object]"
    console.log(toStr1([]));      // "[object Array]"
    console.log(toStr1(123));     // "[object Number]"
    console.log(toStr1("abc"));   // "[object String]"
    console.log(toStr1("abc"));   // "[object String]"
    console.log(toStr1(new Date));// "[object Date]"

      从结果中可以看到该方法的主要功能是用于检测对象的类型。但通常类型检测,我们可能更多地看到如下代码实现:

    var toStr2 = obj => Object.prototype.toString.call(obj);
    console.log(toStr2({}));      // "[object Object]"
    console.log(toStr2([]));      // "[object Array]"
    console.log(toStr2(123));     // "[object Number]"
    console.log(toStr2("abc"));   // "[object String]"
    console.log(toStr2("abc"));   // "[object String]"
    console.log(toStr2(new Date));// "[object Date]"

      第二种方法更简洁,仅仅使用一次call就能获得我们想要的功能,且代码逻辑清晰,理解起来更加容易,可在众多框架中为何更多使用第一种呢?

      其实主要的原因是防止原型污染。

      比如我们在业务代码中覆写了Object.prototype.toString方法,第二种写法将得不到正确的结果,而第一种写法仍然可以。我们用代码来来试试:

    var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
    
    var toStr2 = obj => Object.prototype.toString.call(obj);
    
    Object.prototype.toString = function(){
     return'toString方法被覆盖!';
    }
    // 接着我们再调用上述方法
    
    // toStr1调用结果如下:
    console.log(toStr1({}));      // "[object Object]"
    console.log(toStr1([]));      // "[object Array]"
    console.log(toStr1(123));     // "[object Number]"
    console.log(toStr1("abc"));   // "[object String]"
    console.log(toStr1("abc"));   // "[object String]"
    console.log(toStr1(new Date));// "[object Date]"
    
    // toStr2调用结果如下:
    console.log(toStr2({}));      // "toString方法被覆盖!"
    console.log(toStr2([]));      // "toString方法被覆盖!"
    console.log(toStr2(123));     // "toString方法被覆盖!"
    console.log(toStr2("abc"));   // "toString方法被覆盖!"
    console.log(toStr2("abc"));   // "toString方法被覆盖!"
    console.log(toStr2(new Date));// "toString方法被覆盖!"

      结果很明显。第一种方法仍然能正确得到结果,而第二种则不行!那么为什么会这样呢?

      我们知道bind函数返回结果是一个函数,这个函数是函数内部的函数,会被延迟执行,那么很自然联想到这里可能存在闭包!不过在现代版浏览器中call和bind都已经被js引擎内部实现了,我们没有办法调试!但是我们可以通过polly-fill提供的近似实现的源码来理解引擎内部的逻辑,下面是个简单的demo:

    // 模拟实现call
    // ES6实现
    Function.prototype.mycall = function (context) {
     context = context ? Object(context) : window;
     var fn = Symbol();
     context[fn] = this;
    
     let args = [...arguments].slice(1);
     let result = context[fn](...args);
    
     delete context[fn]
     return result;
    }
    // 模拟实现bind
    Function.prototype.mybind = function (context) {
     if (typeof this !== "function") {
       throw new Error("请使用函数对象调用我,谢谢!");
    }
    
     var self = this;
     var args = Array.prototype.slice.call(arguments, 1);
    
     var fNOP = function () { }; // 注意这个是用来处理new的特性
    
     var fBound = function () {
       var bindArgs = Array.prototype.slice.call(arguments);
       return self.myapply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    
     fNOP.prototype = this.prototype;
     fBound.prototype = new fNOP();
     return fBound;
    }
    // 模拟实现apply
    // ES6实现
    Function.prototype.myapply = function (context, arr) {
     context = context ? Object(context) : window;
     context.fn = this;
     let result;
     if (!arr) {
       result = context.fn();
    } else {
       result = context.fn(...arr);
    }
    
     delete context.fn
     return result;
    }
    var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString);
    
    console.log(toStr1({}));      // "[object Object]"
    console.log(toStr1([]));      // "[object Array]"
    console.log(toStr1(123));     // "[object Number]"
    console.log(toStr1("abc"));   // "[object String]"
    console.log(toStr1(new Date));// "[object Date]"

      上述的实现略去一些健壮性的代码,仅保留核心逻辑,具体的实现细节这里不做解释,有兴趣的可以自己研究,从devtools我们看到mybind形成的闭包确实在函数对象toStr1的作用域上!当然如果你对原型链有深刻理解的话,其实这句有趣的代码还可以写成如下方式:

    var toStr3 = Function.call.bind(Object.prototype.toString);
    var toStr4 = Function.call.call.bind(Object.prototype.toString);
    var toStr5 = Function.call.call.call.bind(Object.prototype.toString);

      其实这个代码拆分出来看就好理解了:

    1、Function.prototype.call表示调用函数原型上的call函数,跟调用Array.prototype.slice的方式没什么区别,都是调的原型上的方法;

    2、(Function.prototype.call).bind(Object.prototype.toString); 这样看只是,给call方法做了个bind而已,改变了call函数的this指向为toString;

      作个假设,call函数内部通过调用this取得调用的函数,比如Array.prototype.slice.call则call内部this可以取得slice,

    3、同理,Function.prototype.call 内部通过bind绑定,已经将this上下文绑定了成了toString,文中toStr1({}) 只不过在使用call({}),并且是提前绑定了this上下文为Object.prototype.toString,等价于Object.prototype.toString.call({})。

      可能你会疑惑为什么不写成Function.prototype.call(Object.prototype.toString, {}),别忘了call函数本身第一个参数就是作为参数使用,就像Array.prototype.slice.call({0:'1',1:'2',length:2})这个一样,根本无法改变到call的this,而是改变的slice的this,所以才会出现(Function.prototype.call).bind(Object.prototype.toString)这种写法,通过bind去改变call的this为Object.prototype.toString,这样,就等同于使用(Object.prototype.toString).call了,因为此时call的this指向了Object.prototype.toString。这样用法实际都是设计模式种鸭子模式的使用,只要长的像鸭子,叫声像鸭子,那么就可以当成鸭子用,这也是为什么Array.prototype.slice.call({0:'1',1:'2',length:2}),能够转化类数组的原因,因为有索引,有length,slice内部的this就能像操作数组的索引和length一样去取属性,自然也就能正常运行下去。

      其实就一句话:bind形参刚开始 和 object.prototype.tostring 指向同一块堆,后来改变了object.prototype.tostring指向,但是bind形参没有改变。

    1、call,apply是动态的改变this的指向,即换个对象执行原对象方法的方法,并立即执行;

    2、bind是静态改变this的指向,并返回一个修改后的函数。静态指向之后就不会改变,除非new。

      关于call、apply、bind的一些相关知识,可以详见我之前总结的博客:

      原生JS实现bind()函数

      深入理解this和call、bind、apply对this的影响及用法

      JavaScript中的bind方法及其常见应用

  • 相关阅读:
    【linux磁盘与文件系统管理】8-RAID工作原理和实现
    【linux磁盘与文件系统管理】5,6,7-文件系统使用-管理
    【linux磁盘与文件系统管理】3,4-MBR和GPT分区-分区管理
    【linux磁盘与文件系统管理】2-分区表MBR
    【linux磁盘与文件系统管理】1-磁盘结构和概念
    01学习Vue.js过程总结
    oracle表管理(建表,改表,删表,表数据增删改查)
    oracle数据类型
    Orcal登录密码过期
    基于Container部署的k8s集群
  • 原文地址:https://www.cnblogs.com/goloving/p/14035252.html
Copyright © 2011-2022 走看看