详解call、apply、bind方法

call、apply、bind这三个方法都可以显式的指定调用函数的this指向,那么这三个方法会有什么区别呢?本文将会进行详细讲解,通过本文你将学习到如何使用这三个方法,以及这三种方法的源码是怎么实现的

1. call()、apply()、bind()之间的区别

call()apply()它们作用一模一样,都实现改变调用函数的this指向并同时执行该函数,区别在于传入参数的形式的不同

  • call传入的参数不固定,第一个参数是this指向,从第二个开始后面的每个参数都依次传入调用的函数。
  • apply只接受两个参数,第一个参数与call相同,是this指向,第二个参数为带下标的集合,这个集合可以是数组也可以是伪数组,apply方法把这个集合的所有元素作为参数传递给调用的函数。

bind()方法不会执行函数,但是也能改变函数内部this指向,它的语法是:fu.bind(thisArg, arg1,arg2...),第一个参数是函数运行时指定的this的值,后面的参数都是函数运行时的传参,返回由指定this值和初始化采纳数改造的原函数的拷贝

下面看bind()的示例:

let o = {
    name: 'andy'
}
function fn() {
    console.log(this)
}
let f = fn.bind(o)  // 此时函数f的this指向对象o
f()   // {name: 'andy'}
// 返回的是原函数改变this之后产生的新函数
function fn(a, b) {
    console.log(this)
    console.log(a + b)
}
let f = fn.bind(o, 1, 2)  // 传参
f() // {name: 'andy'}  3

下面总结一下

bind apply call
是否执行调用的函数
参数 (this指向,参1,参2…) (this指向,[参数数组]) (this指向,参1,参2…)
用途 改变定时器内部的this指向 跟数组有关系,比如借助于数学对象实现数组最大值最小值 经常用做继承

使用bind改变定时器指向,实现点击按钮后三秒后禁用按钮:

let btn = document.querySelector('button')
btn.onclick = function() {
    this.disabled = true   // 这个this指向btn
    let that = this
    setTimeout(function() {
        // this.disabled = false  // 定时器函数里面的this指向的是window
        // that.disable = false  可行的方法
        this.disabled = false
    }.bind(this), 3000) 
}

使用apply实现求数组最大值最小值:

let arr = [15, 6, 12, 13, 166666]
console.log(Math.max.apply(this, arr))  // 166666
console.log(Math.min.apply(this, arr))  // 6

2. 手撕call、apply及bind方法

call()实现思路:

  1. 上下文不传默认为window
  2. 做防止Function.prototype.myCall()直接调用的判断
  3. this方法(this指向调用bind()的方法)挂载到上下文对象上,需要用Symbol声明一个对象属性
  4. 执行上下文刚挂载的函数,保存返回结果result后,删除挂载的方法
  5. 返回结果result
Function.prototype.myCall = function (context = window, ...args) {
  if (this == Function.prototype) {
    return undefined; // 防止Function.prototype.myCall()直接调用
  }
  const fn = Symbol("fn");
  // this指向的是要被执行的函数,也就是call的第一个参数
  // 其实就是把this挂载到上下文对象上,然后执行它,执行完拿到返回结果后,删除挂载的函数
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};
// 示例
const obj = {
  name: "zhangsan",
  age: 18,
};

let fun = function (mes, sym) {
  console.log(mes + sym, this.age + "岁的" + this.name);
};

fun.myCall(obj, "Hello", "!");  // Hello! 18岁的zhangsan

apply():更call()的实现思路一模一样,只是参数变数组

Function.prototype.myApply = function (context = window, args = []) {
  if (this == Function.prototype) {
    return undefined; // 防止Function.prototype.myApply()直接调用
  }
  const fn = Symbol("fn");
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};
// 示例
const obj = {
  name: "zhangsan",
  age: 18,
};

let fun = function (mes, sym) {
  console.log(mes + sym, this.age + "岁的" + this.name);
};
fun.myApply(obj, ["Hello", "!"]);  // Hello! 18岁的zhangsan

bind():实现思路:

  1. 初始化上下文不传默认为window
  2. 做防止Function.prototype.myBind()直接调用的判断
  3. 保存原函数
  4. 返回一个新函数,新函数返回传的参数整合了原函数的参数原函数.apply()方法
Function.prototype.myBind = function (context = window, ...args) {
  if (this == Function.prototype) {
    return undefined; // 防止Function.prototype.myBind()直接调用
  }
  // 拿到原函数
  const fn = this;
  // 返回一个新的函数,函数的参数整合了原函数+新函数
  return function (...newArgs) {
    return fn.apply(context, args.concat(newArgs));
  };
};

// 示例
const obj = {
  name: "zhangsan",
  age: 18,
};

let fun = function (mes, sym) {
  console.log(mes + sym, this.age + "岁的" + this.name);
};
const f = fun.myBind(obj); // 返回新函数
f("Hello", "!");  // Hello! 18岁的zhangsan

你可能感兴趣的:(JavaScript,面试,javascript,前端,ecmascript,node.js,设计模式)