手写call、apply、bind、new

116 阅读2分钟

call apply bind new的手写实现,带有测试用例和注释
不熟悉this指向的问题,可以先看:this绑定的四种方式:new,显式,隐式,默认

call

...
    <script src="./fn.js"></script>
    <script>
        // 普通函数
        function test(age) {
            console.log(this.name + " " + age);
        }
        // 自定义对象
        var obj1 = {
            name: '小明'
        }
        var obj2 = {
            name: '小李'
        }
        // 调用函数的 _Call 方法
        test._Call(obj1, 11)
        test._Call(obj2, 22)
    </script>
...
// this 为调用的函数
// context 是参数对象
Function.prototype._Call = function (context) {
  context = context || window;
  /** 1.将this作为context的一个属性,由于隐式绑定,this会指向context,
      所以能够f访问到context对象内的属性 **/
  context.f = this;
  args = Array.from(arguments).slice(1); //除context的其它参数
  //2.把参数传入调用对象
  let result = context.f(...args);
  //3.删除context.f
  delete context.f;
  //4.返回执行结果
  return result;
};

apply

    <script src="./fn.js"></script>
    <script>
        // 普通函数
        function test(age) {
            console.log(this.name + " " + age);
        }
        // 自定义对象
        var obj = {
            name: '小明'
        }
        test._Apply(obj, [22])
    </script>
// this 为调用的函数
// context 是参数对象
Function.prototype._Apply = function (context,args) {
  context = context || window
   /** 1.将this作为context的一个属性,由于隐式绑定,this会指向context,
      所以能够f访问到context对象内的属性 **/
  context.f = this
  //2.把参数传入调用对象
  let result  = context.f(...args)
  //3.删除context.f
  delete context.f
  //4.返回执行结果
  return result
};

bind

<script src="./fn.js"></script>
    <script>
        // 普通函数
        function test() {
            //这里的bind接收函数,如果是直接调用,this指向了obj,打印出小明;
            //如果是new出来的函数调用,this指向test(),打印undefined
            console.log(this.name,'this.name'); 
            
            console.log([...arguments]);
        }
        // 自定义对象
        var obj = {
            name: '小明'
        }

        let F = test._Bind(obj, 1, 2, 3);
        console.log(F,'F');

        //直接调用方式,this指向obj
        F(5, 6); //这里有柯里化,相当于test._Bind(obj, 1, 2, 3)(5, 6)

        // new方式
        // new出来的bind接收函数,不会改变this指向,只会带上bind()里除第一个参数外的参数;这里依旧指向test()
        let newObj = new F(7, 8);
        // console.log(newObj,'newObj');
    </script>
// this 为调用的函数
// context 是参数对象
Function.prototype._Bind = function (context) {
  // 判断调用者是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 截取传递的参数
  const args = Array.from(arguments).slice(1);
  // 用_this保存this,因为调用bind接收函数时,执行环境变了
  const _this = this;
  console.log(_this, "_this"); // 调用bind的函数
  // 返回一个函数等待接收
  return function F() {
    // 返回了一个函数,可以直接调用F(),也可以 new F(),所以需要判断

    // 对于 new 的情况来说,不会被任何方式改变 this
    if (this instanceof F) {
      console.log(this, "this"); // 这里的this指向F
      return new _this(...args, ...arguments);
    } else {
      console.log(this, "this"); // 这里的this指向window
      //直接调用,则把this指向context
      //这里的arguments是接收bind的函数所传的参数(柯里化)
      return _this.apply(context, args.concat(...arguments));
    }
  };
};

new

    <script src="./fn.js"></script>
    <script>
        function Foo() {
            this.name = '小明'
            this.arg = arguments[0]
        }
        Foo.prototype.callName = function () {
            console.log(this.name)
        }
        // 测试
        let test = _New(Foo, '学生', '男')
        test.callName()
        console.log(test)
    </script>
function _New(Ctor) {
  //==>1.创建一个新的对象(未来的实例对象)
  let obj = {};
  //==>2.将构造函数的prototype和实例对象的__proto__建立联系
  obj = Object.create(Ctor.prototype); //给实例对象指定__proto__为Ctor.prototype
  // 相当于obj.__proto__ =  Ctor.prototype
  let args = Array.prototype.slice.call(arguments, 1); // 获取除去Ctor之外的参数
  // 相当于 let args = Array.from(arguments).slice(1);
  //==>3.改变构造函数的this指向,指向实例对象;因此,实例对象obj就可以访问到构造函数内的属性和方法了
  let result = Ctor.call(obj, ...args);
  console.log(result, "result");
  //==>4.返回实例对象
  // 判断构造函数是否存在返回值,且返回值为引用类型(基本类型无效),
  // 如果不存在则返回创建的新对象obj
  return typeof result === "object" || result instanceof Function
    ? result
    : obj;
}