apply/call/bind及手写实现

73 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

apply call bind使用

call、applybind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数。这三个方法都是改变func中的this指向,调用后this指向传入的第一个参数。

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)

image-20220210162536789.png

this指向的情况

  • call:①使用call会立即调用函数;②函数中this指向call的第一个参数;③call接收两个以上实参,可以将第二个及以后的参数传给函数
  • apply:①使用apply会立即调用函数;②函数中this指向apply的第一个参数;③apply接收两个实参,第二个参数是个数组,apply将第二个参数的数组按顺序拆散成多个实参传给函数。
  • bind:①创建一个跟原函数一样的新函数,但不执行;②永久的替换新函数中的this指向;③bind可以接收其他实参做永久替换部分形参变量为固定的实参值

关于绑定规则:

  • call、apply、bind中的this被强绑定在指定的那个对象上;
let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}
getValue.call(a, 'poe', '24')
getValue.apply(a, ['poe', '24'])
  • 如果把 nullundefined 作为 this 的绑定对象传入 callapplybind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
var a = 'hello'
function foo() {
    console.log(this.a)
}
foo.call(null)  // 'hello'
// 此时this指向window
  • 被bind()永久绑定的this,即便用call,也无法修改this的指向了,即多次bind绑定也只有第一次的生效
var obj1 = {a: 'obj1'}
var obj2 = {a: 'obj2'}
function foo() {
    console.log(this.a)
}
var bindFun1 = foo.bind(obj1)
bindFun1.call(obj2)   // 输出:obj1(bind后再使用call或apply不生效)
var bindFun2 = foo.bind(obj1).bind(obj2)
bindFun2()  // 输出:obj1(多次bind只有第一个生效)

方法的应用场景示例

  1. 判断数据类型

Object.prototype.toString 来判断类型是最合适的,借用它我们几乎可以判断所有类型的数据

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {
    return type;
  }
  return Object.prototype.toString.call(obj).replace(/^$/, '$1');
}
  1. 类数组借用方法

类数组因为不是真正的数组,所有没有数组类型上自带的种种方法,所以我们就可以利用一些方法去借用数组的方法,比如借用数组的 push 方法,看下面的一段代码。

var arrayLike = { 
  0: 'a',
  1: 'b',
  length: 2
} 
Array.prototype.push.call(arrayLike, 'c', 'd'); 
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "a", 1: "b", 2: "c", 3: "d", length: 4}

call 的方法来借用 Array 原型链上的 push 方法,可以实现一个类数组的 push 方法,给 arrayLike 添加新的元素

  1. 获取数组的最大 / 最小值

我们可以用 apply 来实现数组中判断最大 / 最小值,apply 直接传递数组作为调用方法的参数,也可以减少一步展开数组,可以直接使用 Math.max、Math.min 来获取数组的最大值 / 最小值,示例:

let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr); 
const min = Math.min.apply(Math, arr);
 
console.log(max);  // 16
console.log(min);  // 6

手写实现

对于这几个函数改变this指向的实现思路:

  1. 不传入第一个参数,那么默认为 window
  2. 改变了 this 指向
  3. 新的对象可以执行该函数

那么我们可以给新的对象添加一个函数,然后在执行完以后删除

实现一个 bind 函数

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

实现一个 call 函数

Function.prototype.myCall = function (context) {
  var context = context || window
  // 给 context 添加一个属性
  // getValue.call(a, 'pp', '24') => a.fn = getValue
  context.fn = this
  // 将 context 后面的参数取出来
  var args = [...arguments].slice(1)
  // getValue.call(a, 'pp', '24') => a.fn('pp', '24')
  var result = context.fn(...args)
  // 删除 fn
  delete context.fn
  return result
}

实现一个 apply 函数

Function.prototype.myApply = function(context = window, ...args) {
  // this-->func  context--> obj  args--> 传递过来的参数
​
  // 在context上加一个唯一值不影响context上的属性
  let key = Symbol('key')
  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法
  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组
  
  let result = context[key](args); // 这里和call传参不一样
  delete context[key]; // 不删除会导致context属性越来越多
  return result;
}
// 使用
function f(a,b){
 console.log(a,b)
 console.log(this.name)
}
let obj={
 name:'张三'
}
f.myApply(obj,[1,2])  //arguments[1]