js自实现bind、apply、call方法

本文介绍了如何使用ES6扩展运算符简化Function.prototype.bind的实现,并探讨了arguments对象特性、参数传递与上下文处理,包括自定义apply和call方法。特别提到了ES11的globalThis和处理undefined或null上下文的技巧。
摘要由CSDN通过智能技术生成

前言

看了两篇文章,对于其中难以理解的部分使用es6的扩展运算符稍作修改,并且进行了一些扩展,总结得到这篇文章

https://github.com/lin-xin/blog/issues/7
https://blog.csdn.net/lovefengruoqing/article/details/80186401

1 实现bind

  • 1 bind创建了一个新的函数
  • 2 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  • 3 所有的函数都是Function的实例,所以均可以访问Function原型上的方法和属性,所以将binding方法定义在其原型上
Function.prototype.binding = function () {
  var self = this; // this就是调用binding方法的函数自身
  // 第一个参数作为上下文保存
  var context = [...arguments].shift();
  // 如果binding的第一个参数时undefined或者Null,则令其为window
  if(context === undefined || context === null){
  	  context = 'window';  //或者globalThis   es11中引入了globalThis,在nodejs12中也认可了这个对象指代全局对象global
  }
  // 保存剩余的参数
  var args = [...arguments].slice(1);
  return function () {
    // 
    return self.apply(context, [...args, ...arguments]);
  };
};

2 注意点

2.1 arguments是类数组对象

arguments是一个对象,但是它不是Array的实例,它只是一个数组对象

typeof arguments             //  "object"
arguments instanceof Array   //   false

下面是输出的一个例子:
在这里插入图片描述
所以如果我们需要使用数组的方法对其进行操作就需要将其转换为数组,es6中提供的扩展运算符可以很方便地将类数组对象转换为数组对象。

需要注意的是:[...arguments]等同于开辟了一个新的堆内存,产生了一个新数组对象,然后对该新对象执行shift操作,所以它不会修改arguments的结构,在保存剩余参数时[...arguments]仍然包含全部的输入实参,所以需要使用slice(1),保存除了第一个参数外的剩余实参

// 第一个参数作为上下文保存
var context = [...arguments].shift();
// 保存剩余的参数
var args = [...arguments].slice(1);
2.2 参数的传递

调用bind产生新函数时除了传递指定的上下文对象外,还可以传递其他参数args1,供新函数调用时使用
新函数调用时同样可以传递参数args2,但是这些参数一定是在args1的后面的
如果args1占据了所有的形参位,则args2不会对参数进行覆盖

var obj = { a: 100};
var a = 1000;
function f(a,b,c){
	return this.a+b+c;
}

console.log(f(1,2,3));    // 1005
var f1 = f.binding(obj,10,20);
console.log(f1(30,40,50));   // 150

上例中,实际传入f1中的参数有10,20,30,40,50,但是形参位只有3个,所以a=10,b=20,c=30,而由于this指向obj,所以this.a+b+c = 150

2.3 上下文对象为undefined或者null时将其指定为全局对象window

es11中引入了globalThis指代全局对象window,在nodejs12中也认可了这个对象指代全局对象global

if(context === undefined || context === null){
	context = 'window';  // globalThis
}

3 不借助于apply方法实现

Function.prototype.binding = function () {
  var self = this; // this就是调用binding方法的函数自身
  // 第一个参数作为上下文保存
  var context = [...arguments].shift();
  if(context === undefined || context === null){
  	  context = 'window';  // globalThis
  }
  // 保存剩余的参数
  var args = [...arguments].slice(1);
  return function () {
  	// 这也是自实现apply方法的方式
    // 将原函数绑定到预定的上下文对象上
    context[self] = self;
    // 使用预定的上下文对象.原函数的方式调用,则内部this就会指向该对象
    var res = context[self](...args, ...arguments);
    // 删除为该对象添加的方法
    delete context[self];
    // 返回结果
    return res;
  };
};

4 apply自实现

apply方法总共传入两个参数,一个指定上下文对象,一个是参数组成的数组,所以直接使用arguments[0],arguments[1]获取即可

Function.prototype.apply = function () {
  var self = this; // this就是调用binding方法的函数自身
  // 第一个参数作为上下文保存
  var context = arguments[0];
  if (context === undefined || context === null) {
    context = "window"; // globalThis
  }
  // 保存剩余的参数
  var args = arguments[1];
  context[self] = self;
  // 使用预定的上下文对象.原函数的方式调用,则内部this就会指向该对象
  var res = context[self](...args);
  // 删除为该对象添加的方法
  delete context[self];
  // 返回结果
  return res;
};

5 call自实现

call方法传入的第一个参数是一个指定上下文对象,其余是函数的参数,所以不能直接使用arguments[0],arguments[1]获取

Function.prototype.call1 = function () {
  var self = this; // this就是调用binding方法的函数自身
  // 第一个参数作为上下文保存
  var context = [...arguments].shift();
  if (context === undefined || context === null) {
    context = "window"; // globalThis
  }
  // 保存剩余的参数
  var args = [...arguments].slice(1);
  console.log(args);
  context[self] = self;
  // 使用预定的上下文对象.原函数的方式调用,则内部this就会指向该对象
  var res = context[self](...args);
  // 删除为该对象添加的方法
  delete context[self];
  // 返回结果
  return res;
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值