call、apply、bind的实际应用及实现原理

107 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

语法及使用

call

call 使用一个指定的 this 值和一个或多个参数调用函数,这个函数中的 this 指向 call 方法传递的值。

call 方法 本身无返回值 ,其返回值由 被调用函数 决定。

语法:

function.call(thisArg, arg1, arg2, ...)

用法1:实现继承

在一个子构造函数中,可以通过调用父构造函数的 call 方法来实现继承。


function Person(name, age) {
    this.name = name;
    this.say = function () {
        console.log('my name is ' + this.name)
    }
}

function Male(name) {
    // 通过 Person 构造函数的 call 方法调用Person,Person函数中的 this 指向:使用 new Male 创建出来的实例对象
    Person.call(this, name);
    this.sex = 'male'
}

function Female(name) {
    // 通过 Person 构造函数的 call 方法调用Person,Person函数中的 this 指向:使用 new Female 创建出来的实例对象
    Person.call(this, name);
    this.sex = 'female'
}

// 通过 new 关键字创建实例对象 m f,构造函数中的 this 指向对应的 实例对象
const m = new Male('小明');
const f = new Female('小红');

m.say(); // my name is 小明
f.say(); // my name is 小红

用法2:使用 call 方法调用函数时,第一个参数为非正常类型的值

this 指向默认情况下都是一个 对象 ,但是在使用时也允许 不传递/传递其他类型 的值

  • 不传参:函数中的 this 默认指向 全局对象(window/global,由代码运行环境决定,本文默认运行环境是浏览器)

    :严格模式下,函数中的 this 指向 undefined

  • 第一个参数为原始值:默认会将 原始类型 转换为 包装类型

  • null、undefined:指向 window


function print() {
    console.log(this)
    console.log( Object.getOwnPropertySymbols(this))
}

print.call(); // window
print.call(null); // window
print.call(undefined); // window
print.call(true); // Boolean {true}
print.call(0); // Number {0}
print.call(''); // String {''}

apply

该方法的语法和作用与 call 方法类似,只有一个区别,就是 call 方法接受的是一个参数列表,而 apply 方法接受的是一个包含多个参数的数组

语法:

function.call(thisArg, argsArray)

bind

bind 方法会 返回 一个 新函数,这个 新函数this 指向 bind 方法的第一个参数,而其余参数作为 新函数 的参数。

语法:

const newFunc = function.bind(thisArg[, arg1[, arg2[, ...]]])

用法1:创建绑定函数

bind 最简单的用法就是 创建一个有固定 this 指向的函数

this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;

// 直接调用函数,this 指向 window
retrieveX(); // 9

// boundGetX 就是 将 retrieveX 函数体中的 this 绑定到 module上的一个新函数
var boundGetX = retrieveX.bind(module); 
boundGetX(); // 81
同样可以应用在 setTimeout 的 回调函数中。
this.petalCount = 12

function LateBloomer() {
  this.petalCount = 100;
}


LateBloomer.prototype.bloom = function() {
  // 回调函数中的this默认指向window
  setTimeout(this.declare, 1000); // I am a beautiful flower with 12 petals!
  // 手动修改回调函数的this
  setTimeout(this.declare.bind(this), 1000); // I am a beautiful flower with 100 petals
};

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

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

用法2:快捷调用

为需要特定 this 值的函数创建一个快捷方法,也可以使用 bind

let arr = [1, 2, 3];

let unboundSlice = Array.prototype.slice;

// 这里的 slice 方法,是 Function.prototype.call 函数,且函数体内的 this 已经绑定为 unboundSlice
let slice = Function.prototype.call.bind(unboundSlice);

slice(arr, 0, 1);

// ====> 等同于

Array.prototype.slice.call(arr, 0, 1);

// ===> 等同于

arr.slice(0, 1);

原理及实现

注: 默认代码运行在 浏览器环境

需要判断运行环境可以加上这行代码,然后将函数中的 window 改为 __global

const __global = typeof window === 'undefined' ? global : window;

call

// 定义生成三种包装类型的map
const thisArgMap = {
  'number': (n) => new Number(n),
  'string': (s) => new String(s),
  'boolean': (b) => new Boolean(b)
}
// 自定义 call 方法
Function.prototype.myCall = function(thisArg, ...args) {
  const thisType = typeof thisArg;
  if([undefined, null].includes(thisArg)) { // 传递的是 undefined、null或者为传递参数(未传递默认为undefined)
    thisArg = window;
  } else if(thisType !== 'object') { // 传递的是基本数据类型
    thisArg = thisArgMap[thisType](thisArg); // 生成对应的包装类型
  }

  // 生成唯一属性,避免污染 thisArg 上的原有属性
  const func = Symbol('func');
  
  // 此处 this 指向 调用 该方法的函数, 如:fn.call() this ==> fn
  thisArg[func] = this; // 将原函数引用复制到 thisArg 的唯一属性上
  
  // 通过 obj.fn 的方式调用,则原函数体内的 this 会指向 obj;并将参数展开传入
  const res = thisArg[func](...args); 
  
  // 移除 唯一属性
  delete Object.getOwnPropertySymbols(thisArg);
  
  // 返回结果是:原函数的返回值
  return res;
}

apply

apply 实现原理和 call 也基本一致,区别在于:call 调用原函数时,要将 args 展开传递;apply直接传递整个 args 即可。


// call
// ...
const res = thisArg[func](...args); 
// ...

// apply
// ...
const res = thisArg[func](args);
// ...

bind

  • 记录原函数和需要绑定的 this 指向
  • 返回一个新函数
  • 新函数内部确定 this考虑 普通函数 调用和 new 关键字调用
  • 新函数内部通过 call / apply 方法调用原函数,bind 方法传递的参数和新函数调用时传递的参数一起传递给原函数
Function.prototype.myBind = function(thisArg, ...args1) {
  // 记录调用 myBind 方法的原函数
  const originFunc = this;
  // 记录要绑定的 this 指向
  const _this = thisArg;
  return function newFunc(...args2) {
      // 可能将 myBind 返回的函数当作构造函数使用, 即:new 函数
      const that = this instanceof newFunc ? this : _this;
      originFunc.call(that, ...args1, ...args2)
  }
}