call,apply和bind

240 阅读2分钟

基本语法

funthis指向thisArg对象

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

联想记忆:apply首字母'a'代表Array

返回值

call/apply:fun执行的结果,即改变了函数的上下文候马上执行函数 bind:返回fun的拷贝,并拥有指定的this值和初始参数,即改变了函数的上下文,但是不执行

核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存

应用场景

  1. 判断类型
Object.prototype.toString.call(data)

借用它我们几乎可以判断所有类型的数据

'[object String]': 'string',
'[object Number]': 'number',
'[object Boolean]': 'boolean',
'[object Null]': 'null',
'[object Undefined]': 'undefined',
'[object Object]': 'object',
'[object Array]': 'array',
'[object Function]': 'function',
'[object Date]': 'date', // Object.prototype.toString.call(new Date())
'[object RegExp]': 'regExp',
'[object Map]': 'map',
'[object Set]': 'set',
'[object HTMLDivElement]': 'dom', // document.querySelector('#app')
'[object WeakMap]': 'weakMap',
'[object Window]': 'window',  // Object.prototype.toString.call(window)
'[object Error]': 'error', // new Error('1')
'[object Arguments]': 'arguments'
  1. 类数组借用数组的方法
var arrayLike = {
  0: 'OB',
  1: 'Koro1',
  length: 2
}
Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2');
console.log(arrayLike) // {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4}
  1. apply获取数组最大值最小值
const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6
  1. 通过借用父类的构造方法来实现父类方法/属性的继承
// 父类
function supFather(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green']; // 复杂类型
}
function sub(name, age) {
    // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上
    supFather.call(this, name);
    this.age = age;
}
  1. bind的应用场景

(1). 保存变量

for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

因为bind返回是一个函数,这个函数也就是闭包

(2) 解决回调函数this丢失

this.pageClass = new Page(this.handleMessage.bind(this)) // 绑定回调函数的this指向

手写call,apply,bind

call的实现

  Function.prototype.myCall = function (context, ...args) {
    if (context === null || context === undefined) {
      context = window;
    } else {
      // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
      context = Object(context);
    }
    const specialPrototype = Symbol('特殊属性的Symbol');
    context[specialPrototype] = this; // 此时this指向调用该方法的函数本身,函数内部的this则指向context
    let result = context[specialPrototype](...args); // 通过隐式绑定执行函数并传递参数
    delete context[specialPrototype];
    return result;
  }

apply的实现,相比较于call,只是多了个传入参数的判断

  Function.prototype.myApply = function (context) {
    if (context === null || context === undefined) {
      context = window;
    } else {
      // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
      context = Object(context);
    }
    const isArrayLike = (o) => {
      if (o &&                                    // o不是null、undefined等
          typeof o === 'object' &&                // o是对象
          isFinite(o.length) &&                   // o.length是有限数值
          o.length >= 0 &&                        // o.length为非负值
          o.length === Math.floor(o.length) &&    // o.length是整数
          o.length < 4294967296)                  // o.length < 2^32
        return true
      else
        return false
    };
    const specialPrototype = Symbol('特殊属性的Symbol');
    context[specialPrototype] = this;
    let args = arguments[1]; // 获取参数数组
    let result;
    if (args) {
      if (!Array.isArray(args) && !isArrayLike(args)) {
        throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误');
      } else {
        args = Array.from(args);
        result = context[specialPrototype](...args);
      }
    } else {
      result = context[specialPrototype]();
    }
    delete context[specialPrototype];
    return result;
  }

bind的实现

  Function.prototype.myBind = function (thisObj, ...args) {
    const thisFn = this; // 存储源函数
    let fToBind = function (...secondArgs) {
      const isNew = this instanceof fToBind; // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
      const context = isNew ? this: Object(thisObj); // new调用就绑定到this上,否则就绑定到传入的thisObj上
      return thisFn.call(context, ...args, ...secondArgs); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
    };
    fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
    return fToBind;
  }

核心思想:返回一个新的函数,函数的最终执行调用call返回结果

其中this instanceof fToBind语句的作用是为了区别是否通过new来调用

  let obj = {
    name: 'ymhd'
  }
  function output(msg,other) {
    console.log(`Hello ${this.name}, ${msg} ${other}`);
  }
  let fuc = output.myBind(obj, 'welcome to');
  fuc('shenzhen'); // 正常调用

输出

Hello ymhd, welcome to Shenzhen

下面通过new来调用

new fuc('Shenzhen');

输出

Hello undefined, welcome to Shenzhen