JavaScript call,callee,caller,apply,bind之间的区别

(现实是此岸,梦想是彼岸,中间隔着湍急的河流,行动则是架在河上的桥梁。——克雷洛夫)

在这里插入图片描述

call

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

call方法可以将一个对象属性作为另一个对象方法的上下文来使用,比如以下代码。
const obj = {
  name: '张三',
  age: 20,
  getData: function () {
    return `${this.name}的年龄是${this.age}`
  }
};
const obj2 = {
  name: '李四',
  age: 50,
  getData: function () {
    return `${this.name}的年龄已经${this.age}岁了`
  },
  getData2: function (sex, address) {
    return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
  }
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.call(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.call(obj, '男', '北京市')); // 将obj作为obj2.getData2方法的上下文来执行
call方法可以将一个函数的上下文作为另一个函数的上下文来使用,达到继承的目的
const Father = function () {
  this.name = '父亲';
  this.age = 60;
  this.address = '北京市';
  this.sex = '男';
  this.getData = function () {
    return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex}`;
  };
  this.getData2 = function (hobby) {
    return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex} - 爱好:${hobby}`;
  };
};
const father = new Father();
console.log(father.getData()); // 输入父函数自己的上下文
const Daughter = function () {
  // 此处call要写在第一行,这样避免其他的初始化被call覆盖
  // 此处call用意为继承Father对象的上下文
  Father.call(this);
  this.name = '女儿';
  this.age = 12;
  this.address = '河南';
  this.sex = '女';
};
const daughter = new Daughter();
console.log(daughter.getData()); // 虽然Daughter函数没有getData方法,但因为使用call,this指向了Father,所以就可以使用父函数的getData方法

call方法可以将一个对象上下文作为另一个函数的上下问来使用
const obj = {
  name: '张三',
  getName: function () {
    return this.name;
  }
};
const Fun = function () {
  const data = ['姓名', this.getName()].join('');
  console.log(data);
};
Fun.call(obj); // 姓名张三

callee

MDN链接
callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。

假如我们要写一个递归计算函数,如下代码,随着函数名称的更改,内部代码的函数名也要更改。并且如果这是一个匿名函数,那么将无法进行递归。

const fun = function (x) {
  if (x === 0) {
    return;
  } else {
    return fun(x - 1);
  }
};

所以callee出现了,它可以引用该函数的函数体内当前正在执行的函数上下文,比如以下代码,通过记录内部上下文也可以达到递归的目的。

const fun2 = function (x) {
  if (x === 0) {
    return;
  } else {
    return arguments.callee(x - 1);
  }
};

但为什么callee在es5严格模式中被禁用了呢?

const global = this;
const sillyFunction = function (recursed) {
  if (!recursed)
    return arguments.callee(true);
  if (this !== global)
    console.log("This is: " + this);
  else
    console.log("This is the global");
}
sillyFunction();

通过以上的代码结果得知,每次callee得到的this都不相同,并且如果一个业务中有多个不同的arguments.callee嵌套,那么维护的成本也是巨大的。
callee的危害
所以最好的办法还是通过命名函数的方式来进行业务操作,虽然会麻烦一些,但对后期代码的维护成本大大降低,性能也更高。

caller

返回调用指定函数的函数,比如如下代码。


const Fun = function () {
  this.fun2 = function () {
    console.log(this.fun2.caller.name);
  }
};
(function Test () {
  new Fun().fun2(); // Test
})();

caller的通常在追踪业务调用链很有用,它可以将函数名和函数文本打印出来

apply

apply的作用和call相同,只不过第二个参数是数组而非多个。

const obj = {
  name: '张三',
  age: 20,
  getData: function () {
    return `${this.name}的年龄是${this.age}`
  }
};
const obj2 = {
  name: '李四',
  age: 50,
  getData: function () {
    return `${this.name}的年龄已经${this.age}岁了`
  },
  getData2: function (sex, address) {
    return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
  }
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.apply(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.apply(obj, ['男', '北京市'])); // 将obj作为obj2.getData2方法的上下文来执行

bind

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

MDN链接

global.x = -1;
const data = {
  x: 42,
  getX: function () {
    return this.x;
  }
};
console.log(data.getX()); // 42
const unboundGetX = data.getX;
console.log(unboundGetX());
// undefind 因为函数被赋值给unboundGetX时并没有执行,再次执行时,使用的上下文就是全局的了

const boundGetX = unboundGetX.bind(data);
// 那么为了解决this指向的问题,就可以使用.bind 通过指定参数的形式来指定this指向

console.log(boundGetX()); // 42
bind在计时器中的应用
function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后,调用 'declare' 方法

bind在异步promise中的应用

假如有异步并行查询的业务场景,那么为了控制频率,减少内存和cpu占用。所以需要将异步待执行函数单独抽离出来,放入异步池中执行。

const queryAll = async () => {
  const dbCollection = {}; // mysql or mongodb
  const getUser = async function () { dbCollection.get };
  const getMobile = async function  () { dbCollection.get };
  const getAddress = async function () { dbCollection.get };
  const getSex = async function () { dbCollection.get };
  const getHeight = async function () { dbCollection.get };
  const getWeight = async function () { dbCollection.get };
  // 每两个一组放入异步池,实际情况可能需要遍历并编写分组代码
  const pool= [
    [
      getUser.bind(this),
      getMobile.bind(this)
    ],
    [
      getAddress.bind(this),
      getSex.bind(this)
    ],
    [
      getHeight.bind(this),
      getWeight.bind(this),
    ]
  ];
  for (const item of pool) {
    const result = await Promise.all(item);
    // ....一些集合处理
  }
};
bind参数传递

bind和call一样都可以进行参数传递。

const data = {
  x: 42,
  getX: function (y, z) {
    return this.x + (y || 0) + (z || 0);
  }
};
const data2 = {
  x: 10,
  getX: function (y, z) {
    return this.x + (y || 0) + (z || 0);
  }
};
const unboundGetX = data.getX;
const boundGetX = unboundGetX.bind(data2, 1, 2);
console.log(boundGetX()); // 45
bind解决普通函数this指向问题

const Persion = function () { //定义构造函数
  this.age = 0; // 定义age
  setInterval(function () {
    this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(this.age);
  }, 1000)

}
new Persion();

// 那么为了解决this问题,就可以使用提前赋值this的方式

const Persion2 = function () { //定义构造函数
  this.age = 0; // 定义age
  const that = this;
  setInterval(function () {
    that.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(that.age);
  }, 1000)

}
new Persion2();

// 也可以使用bind的方式
const Persion3 = function () { //定义构造函数
  this.age = 0; // 定义age
  setInterval(function () {
    this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(this.age);
  }.bind(this), 1000);
}
new Persion3();
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值