call, apply, bind的分析与实现

本文介绍了如何改变JavaScript函数的this指向,并详细解析了call、apply、bind的区别与用法。通过自定义实现myBind、myCall、myApply函数,深入理解它们的工作原理。在实现过程中,特别处理了thisArg的类型转换,确保了函数的正常执行。最后,通过示例展示了自定义函数的正确使用方式。
摘要由CSDN通过智能技术生成

如何改变函数的this指向

如果是函数声明,那么函数内部的this指向全局对象或者调用该函数的对象
箭头函数的this绑定的是父级作用域内的this

使用callapplybind可以绑定this指向。其中bind是永久改变this指向并且不会立即执行applycall临时改变this指向并且立即执行

applycall不同之处在于调用的参数形式:

  • call(thisArg, arg1?, arg2?, arg3?...)第一个参数代表this指向,剩下的参数就是原函数的参数。
  • apply(thisArg, argArray?)第一个参数代表this指向,第二个参数是以数组的形式传递参数。
/* bind, call, apply调用示例 */
var obj = {
	x: 'obj',
	getX: function() {
		// 注意这里不能使用箭头函数
		return this.x;
	}
};
// 调用 obj 对象上的函数属性, this就会绑定为obj
// 'obj'
console.log(obj.getX());

var x = 'window';
function target(a, b, c) {
	console.log(a, b, c);
	return this.x;
}

/* bind */
// undefined undefined undefined
// 'window'
console.log(target());

const targetBound = target.bind(obj, 1); // 此时targetBound就是一个改变了this指向的target

// 1 2 3
// 'obj'
console.log(targetBound(2, 3));

/* call */
// 4 5 6
// 'obj'
console.log(target.call(obj, 4, 5, 6));

/* apply */
// 7 8 9
// 'obj'
console.log(target.apply(obj, [7, 8, 9]));

自定义实现callapplybind

这三个函数都有很强的错误处理功能,假如传入的thisArg是一个基本类型,也会被使用包装类替换,虽然不会报错,但是不推荐这样写,尽量传递复杂类型变量作为thisArg
自定义实现myBindmyCallmyApply

// 先定义一个函数将任意类型都转换成对象,用于指向this
function anyToObj(value) {
  // symbol, bigint 就不判断了,直接在default中
  switch (typeof value) {
    case 'boolean':
      return new Boolean(value);
    case 'number':
      return new Number(value);
    case 'string':
      return new String(value);
    case 'undefined':
      return (this || window || global);
    case 'object':
      if (value === null) return (this || window || global); // 这里的this就指向了全局对象
      return value;
    default:
      return value;
  }
}
/* myBind */
Function.prototype.myBind = function (thisArg, ...argArray) {
  // 防止出现 const myBind = Function.prototype.myBind; myBind();
  if (typeof this !== 'function') {
    throw new TypeError('myBind must be called on a function');
  }
  const that = this; // this 就指向 f.myBind() 的 f
  const thatArg = anyToObj(thisArg); // 处理一下thisArg
  const myBound = function (...args) {
    // 指定唯一的键
    const tempKey = Symbol('__innerFunction__');
    thatArg[tempKey] = that; // 将 that 函数赋值给一个对象的属性
    let ret;
    if (this instanceof myBound) {
      // 调用 myBind 之后返回的是 myBound,假如调用 new myBound(),就会进这个分支
      ret = new thatArg[tempKey](...argArray.concat(args));
    } else {
      // 这里是调用 myBound(),这样调用 tempKey 方法里的 this 就指向了 thatArg
      ret = thatArg[tempKey](...argArray.concat(args));
    }
    // 不会对对象造成污染
    delete thatArg[tempKey];
    return ret;
  };
  return myBound;
};

/* myCall */
// 与 myBind 相比直接调用了
Function.prototype.myCall = function (thisArg, ...argArray) {
  if (typeof this !== 'function') {
    throw new TypeError('myCall must be called on a function');
  }
  const thatArg = anyToObj(thisArg);
  const tempKey = Symbol('__innerFunction__');
  thatArg[tempKey] = this;
  const ret = thatArg[tempKey](...argArray);
  delete thatArg[tempKey];
  return ret;
};

/* myApply */
// 与 myCall 相比,第二个参数希望是数组形式,多了个 CreateListFromArrayLike 用于转化 argArray
Function.prototype.myApply = function (thisArg, argArray) {
  if (typeof this !== 'function') {
    throw new TypeError('myApply must be called on a function');
  }
  const CreateListFromArrayLike = function (val) {
    // 同样缺乏 bigint 的处理,直接放在 default 中
    switch (typeof val) {
      case 'string':
      case 'number':
      case 'boolean':
      case 'symbol':
        throw new TypeError('CreateListFromArrayLike called on non-object');
      case 'object':
        if (Array.isArray(val)) return val;
        if (val === null) return [];
        return Array.from(val);
      default:
        return [];
    }
  };
  // 检测 argArray 的类型
  const tempArgArray = CreateListFromArrayLike(argArray);
  const thatArg = anyToObj(thisArg);
  const tempKey = Symbol('__innerFunction__');
  thatArg[tempKey] = this;
  const ret = thatArg[tempKey](...tempArgArray);
  delete thatArg[tempKey];
  return ret;
};

// 这样 myBind,myCall,myApply就完成了,下面进行测试
var obj = {
	x: 'obj',
	getX: function(a, b, c) {
		console.log(a, b, c);
		return this.x;
	}
}
var x = 'window';
// 1 2 3
// 'obj'
console.log(obj.getX(1, 2, 3));

// target变成一个全局函数,this 指向全局对象
const target = obj.getX;
// 1 2 3
// 'window'
console.log(target(1, 2, 3));

/* myBind */
const targetBound = target.myBind(obj, 1);
// 1 2 3
// 'obj'
console.log(targetBound(2, 3));

/* myCall */
// 4 5 6
// 'obj'
console.log(target.myCall(obj, 4, 5, 6));

/* myApply */
// 7 8 9
// 'obj'
console.log(target.myApply(obj, [7, 8, 9]));

运行结果
这样自定义实现就完成了,大家可以试着看看怎么用才能报错。
如果有好的改进意见也可以评论留言,我会修改的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值