zoukankan      html  css  js  c++  java
  • 实现 call、apply、bind

    实现 call、apply、bind

    在之前一篇文章写了这三个参数的区别,但是其实面试更常考察如何实现。其实所有的原生函数的 polyfill 如何实现,只需要考虑 4 点即可:

    1. 基本功能
    2. 原型
    3. this
    4. 返回值

    call

    1. call 的基本功能:

      call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    2. 原型
      不涉及原型链的转移,不用管
    3. this
      本质上,call 就是 this 的转移
    4. 返回值

    简单实现:

    Function.prototype.myCall = function(context = window, ...args) {
      context.fn = this; // 先将fn挂在context上,
      var res = context.fn(...args); // 然后通过context调用fn,使得fn中的this指向指到context上
      delete context.fn; // 最后删除掉context上的fn
      return res; // 返回原函数的返回值
    };
    

    上面为了简单,使用了 ES6 的剩余参数和展开语法,基本用这个回答面试官就好了。当然,如果不让使用剩余参数,那就只能使用eval或者new Function的字符串拼接大法了,可以参考这篇模板引擎
    再就是 fn 可能会和 context 重名,整一个不会重名的 uniqueID 挂上去,执行完毕后删除。

    apply

    之前提过 apply 和 call 区别,只有一些入参和性能上的区别。直接上代码:

    Function.prototype.myApply = function(context = window, args) {
      context.fn = this; // 先将fn挂在context上,
      var res = context.fn(...args); // 然后通过context调用fn,使得fn中的this指向指到context上
      delete context.fn; // 最后删除掉context上的fn
      return res; // 返回原函数的返回值
    };
    

    bind

    bind 有点不一样,它会返回一个绑定了 this 的函数。

    bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

    Function.prototype.myBind = function(context, ...args) {
      var fn = this;
    
      var newFn = function(...restArgs) {
        // 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
        return fn.call(context, ...args, ...restArgs);
      };
    
      return newFn;
    };
    

    上面的函数基本上覆盖了大部分场景,但是不能支持new调用——

    绑定函数自动适应于使用 new 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

    如果直接使用我们上面所写的bind,就会返回

    function Person(age, name) {
      this.name = name;
      this.age = age;
    }
    
    var Age18Person = Person.myBind(null, 18);
    
    var a = {};
    var Age20Person = Person.myBind(a, 20);
    
    var p18 = new Age18Person("test18"); // newFn {}
    var p20 = new Age20Person("test20"); // newFn {}
    // a {name: "test20", age: 20}
    // window {name: "test18", age: 18}
    

    显然,返回了以newFn生成的对象,并且,因为传入的是null,所以,对context的赋值转移到了window
    这里需要判断是否被 new 调用,然后丢弃没用的 context。

    Function.prototype.myBind = function(context, ...args) {
      var fn = this;
    
      var newFn = function(...restArgs) {
        // 如果是new构造,则使用new构造的实例
        if (new.target) {
          return fn.call(this, ...args, ...restArgs);
        }
        // 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
        return fn.call(context, ...args, ...restArgs);
      };
    
      return newFn;
    };
    

    再次调用上面的new构造,发现实例的原型不是指向我们希望的 Person

    var Age18Person = Person.myBind(null, 18);
    
    var p18 = new Age18Person("test18"); // newFn {}
    
    p instanceof Person; // false
    p instanceof Age18Person; // false
    

    记录一下原型链,再来一遍

    Function.prototype.myBind = function(context, ...args) {
      var fn = this;
    
      var newFn = function(...restArgs) {
        // 如果是new构造,则使用new构造的实例
        if (new.target) {
          return fn.call(this, ...args, ...restArgs);
        }
        // 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
        return fn.call(context, ...args, ...restArgs);
      };
    
      // 绑定原型链
      newFn.prototype = this.prototype;
    
      return newFn;
    };
    

    但是这里还有个问题,如果改了Age18Personprototype,也会影响到Personprototype
    所以,需要做一个中转——

    Function.prototype.myBind = function(context, ...args) {
      var fn = this;
    
      var newFn = function(...restArgs) {
        // 如果是new构造,则使用new构造的实例
        if (new.target) {
          return fn.call(this, ...args, ...restArgs);
        }
        // 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
        return fn.call(context, ...args, ...restArgs);
      };
    
      var NOOP = function() {};
    
      // 绑定原型链
      NOOP.prototype = this.prototype;
      newFn.prototype = new NOOP();
    
      return newFn;
    };
    

    这样基本上就算完成了,当然更推荐function-bind方案。

  • 相关阅读:
    留言板
    阿里云ECS 个人博客搭建流程
    安卓包 无崩溃文件的崩溃问题解决
    win10家庭版 远程桌面解决方案
    python int(a/b)和//的区别(转)
    git基本操作
    《代码整洁之道》读后总结
    《人月神话》的观点:是与非?
    小白第一次装机体验
    python2和python3之间的差异和区别
  • 原文地址:https://www.cnblogs.com/liuyongjia/p/11861990.html
Copyright © 2011-2022 走看看