手写apply-call-bind

导读:本篇文章讲解 手写apply-call-bind,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.回顾函数对象的原型关系

当我们创建一个函数, 这个函数就可以使用apply、call、bind方法

function foo() {}
foo.apply()
foo.call()
foo.bind()

我们知道foo的隐式原型是绑定在Function的显式原型上的

function foo() {}
console.log(foo.__proto__ === Function.prototype) // true

显然apply、call、bind方法是来自Function的原型对象

console.log(Function.prototype.apply); // ƒ apply()
console.log(Function.prototype.call); // ƒ call()
console.log(Function.prototype.bind); // ƒ bind()

意味着, 在Function的原型对象上添加的属性或方法, 可以被所有的函数获取

function foo() {}

Function.prototype.address = "成都市"
Function.prototype.bar = function() {
  console.log("bar函数被调用了")
}

console.log(foo.address)
foo.bar()

2.手写apply函数

  1. 获取thisArg, 我们要确保是一个对象类型

    用户在传参时, 可能会传入”abc”, 123, undefined, null, 我们需要对这些进行边界判断

    function foo(name, age) {
      console.log(this, name, age);
    }
    
    Function.prototype.myapply = function (thisArg) {
      
      // 进行边界判断
      thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
    
    };
    
    foo.myapply("abc");
    
  2. 将this绑定到传入的参数thisArg上

    function foo(name, age) {
      console.log(this, name, age);
    }
    
    Function.prototype.myapply = function (thisArg) {
      
      thisArg = thisArg === undefined || thisArg === null ? window : Object(thisArg);
      
      // 当读取thisArg中的fn时返回this 相当于thisArg.fn = this
      Object.defineProperty(thisArg, "fn", {
        configurable: true,
        value: this,
      });
    
      // 通过隐式绑定, 对this进行调用, 让this指向thisArg
      thisArg.fn();
    
      // 删除thisArg中的fn属性
      delete thisArg.fn;
    };
    
    foo.myapply({ name: "kaisa" });
    
  3. 将剩余的其他参数传入thisArg

    function foo(name, age) {
      console.log(this, name, age); // {name: 'kaisa'} 'kaisa' 18
    }
    
    Function.prototype.myapply = function (thisArg, arrArgs) {
      
      thisArg =thisArg === undefined || thisArg === null ? window : Object(thisArg);
    
      Object.defineProperty(thisArg, "fn", {
        configurable: true,
        value: this,
      });
    
      // 通过展开运算符, 将其他参数传入thisArg
      thisArg.fn(...arrArgs);
    
      delete thisArg.fn;
    };
    
    foo.myapply({ name: "kaisa" }, ["kaisa", 18]);
    

3.手写call函数

call函数与apply的实现是类似的, 不同的是call传入的其他参数是参数列表, apply是一个数组

  1. 获取thisArg, 我们要确保是一个对象类型

    function foo(name, age) {
      console.log(this, name, age);
    }
    
    Function.prototype.mycall = function (thisArg) {
      
      // 进行边界判断
      thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
    
    };
    
    foo.mycall("abc");
    
  2. 将this绑定到传入的参数thisArg上

    function foo(name, age) {
      console.log(this, name, age);
    }
    
    Function.prototype.mycall = function (thisArg) {
      
      // 进行边界判断
      thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
    
      thisArg.fn = this
      // 隐式绑定让this指向thisArg
    	thisArg.fn()
    	delete thisArg.fn
    };
    
    foo.mycall("abc");
    
  3. 将剩余的其他参数传入thisArg

    function foo(name, age) {
      console.log(this, name, age); // {name: 'kaisa', fn: ƒ} 'kaisa' 18
    }
    
    Function.prototype.mycall = function (thisArg, ...otherArgs) {
      thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg)
    
      thisArg.fn = this
      // 将剩余参数传入thisArg
      thisArg.fn(...otherArgs)
      delete thisArg.fn
    };
    
    foo.mycall({ name: "kaisa" }, "kaisa", 18);
    

4.封装函数实现apply和call

我们发现实现自己的apply和call函数时, 有许多重复代码, 我们可以将重复代码封装成一个函数(封装思想很重要)

function execFn(thisArg, otherArgs, fn) {
  thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);

  Object.defineProperty(thisArg, "fn", {
    configurable: true,
    value: fn,
  });

  thisArg.fn(...arrArgs);

  delete thisArg.fn;
}

这样我们可以利用封装的函数快速实现自己的apply和call方法

Function.prototype.myapply = function (thisArg, otherArgs) {
  execFn(thisArg, otherArgs, this);
};

Function.prototype.mycall = function (thisArg, ...otherArgs) {
  execFn(thisArg, otherArgs, this);
};

测试代码

function execFn(thisArg, otherArgs, fn) {
  thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);

  Object.defineProperty(thisArg, "fn", {
    configurable: true,
    value: fn,
  });

  thisArg.fn(...otherArgs);

  delete thisArg.fn;
}

Function.prototype.myapply = function (thisArg, otherArgs) {
  execFn(thisArg, otherArgs, this);
};

Function.prototype.mycall = function (thisArg, ...otherArgs) {
  execFn(thisArg, otherArgs, this);
};

function foo(name, age) {
  console.log(this, name, age); // {name: 'kaisa'} 'kaisa' 18
}

function bar(name, age) {
  console.log(this, name, age); // String {'aaa'} 'coder' 15
}

foo.myapply({ name: "kaisa" }, ["kaisa", 18]);
bar.mycall("aaa", "coder", 15);

5.手写bind函数

bind与apply、call的区别是, 调用bind函数会返回一个新的函数, 同时返回的新的函数中依然可以传参数

源代码

function foo(name, age, height) {
  console.log(this, name, age, height);
}

// 封装自己的bind
Function.prototype.mybind = function (thisArg, ...otherArgs) {
  // 边界判断
  thisArg = thisArg === undefined || thisArg === null ? window : Object(thisArg);

  Object.defineProperty(thisArg, "fn", {
    configurable: false,
    value: this,
  });

  // 返回一个新的函数
  return (...newArgs) => {
    var allArgs = [...otherArgs, ...newArgs];
    thisArg.fn(...allArgs);
  };
};

var newFoo = foo.mybind({ name: "kaisa" }, "coder", 18);
newFoo(1.88);

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120136.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!