call、apply、bind 区别与实现

312 阅读2分钟

其实call、apply、bind大家都用过,这三个方法都可以改变函数整体内部的this指向,下面就简单说下这三个方法的使用以及不同点。

call

call 是属于所有Function的方法,类似于 Function.prototype.call


const obj = {
    a: 't'
}

function fun(b, c, d){
    this.b = b;
    this.c = c;
    this.d = d;
    return this.a + this.b + this.c + this.d;
}

const value = fun.call(obj, 'e', 's', 't');

console.log(value, 'value')

image.png

apply

apply 与 call的语法几乎完全相同,唯一区别在于传参。

const obj = {
    a: 't'
}

function fun(b, c, d){
    this.b = b;
    this.c = c;
    this.d = d;
    return this.a + this.b + this.c + this.d;
}

const value = fun.apply(obj, ['e', 's', 't']);

console.log(value, 'value')

image.png

bind

bind 返回的是一个新的函数

  • bind是es5新增的一个方法
  • 传参和call类似
  • 不会执行对应的函数,call或者apply会自动执行对应的函数
  • 返回对函数的引用

const obj = {
    a: 't'
}

function fun(b, c, d){
    this.b = b;
    this.c = c;
    this.d = d;
    return this.a + this.b + this.c + this.d;
}

const value = fun.bind(obj,'e', 's', 't');

const newValue = value();

console.log(newValue, 'value')

image.png

对比

  • call、apply会直接执行函数、bind会创建一个新的函数
  • call、bind的传参类似,apply第二个参数为一个数组

指定this 注意事项

  • 不传或者传null、undefined,函数中this指向window
  • 传递另一个函数的函数名,函数中this指向这个函数的引用,并不一定是该函数执行时真正的this值
  • 值为原始值的this会指向该原始值的自动包装对象
  • 传递一个对象,函数中的this指向这个对象

模拟实现call

首先我们先知道call方法做了什么事情

  • call 改变了this的指向
  • 执行函数
  • 不指定this的话指向window
  • 从第二个参数开始为函数的入参 我们来简单实现下
function testCall (testContext) {
    // 当传入为空时,默认使用window;
    const context = testContext || window;
    context.fn = this;
    // const args = [].slice.call(arguments, 1); // 不能使用call
    const args = [];
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn('+ args +')'); // evel 函数执行字符串
    delete context.fn
}


Function.prototype.testCall = testCall;

var a = 1;

const obj = {
    a:2
}

function testFun (name, age) {
    console.log(name, age, this.a, '测试')
}

testFun.testCall(obj, '你好', '18')
testFun.testCall(null, '你好', '18')

image.png

apply

实现了call之后,apply就很简单

function testApply (testContext, arr) {
    // 当传入为空时,默认使用window;
    const context = testContext || window;
    context.fn = this;
    // const args = [].slice.call(arguments, 1); // 不能使用call
    const args = [];
    for (let i = 0; i < arr.length; i++) {
        args.push('arr[' + i + ']');
    }
    eval('context.fn('+ args +')'); // evel 函数执行字符串
    delete context.fn
}


Function.prototype.testApply = testApply;

var a = 1;

const obj = {
    a:2
}

function testFun (name, age) {
    console.log(name, age, this.a, '测试')
}

testFun.testApply(obj, ['你好', '18'])
testFun.testApply(null, ['你好', '24'])

image.png

bind

bind的实现依靠call 和 apply 来完成,具体如下

function testBind (testContext) {
    // 当传入为空时,默认使用window;
    const that = this;
    // 第一次传参剩余参数;

    const args = [].slice.call(arguments, 1);
    return function () {
        // 第二次传参参数
        const argsTwo = [].slice.call(arguments);
        // 执行
        that.apply(testContext, args.concat(argsTwo))
    }
    
}


Function.prototype.testBind = testBind;

var a = 1;

const obj = {
    a:2
}

function testFun (name, age) {
    console.log(name, age, this.a, '测试')
}

const r = testFun.testBind(obj, '你好');
const p = testFun.testBind(null);


r(28);
p('我好', 28)

image.png