如何实现call和apply、bind?

179 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

call与apply 都是函数的方法,这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。(就是常说的改变this指向)

ECMAScript 5 出于同样的目的定义了一个新方法:bind()。bind()方法会创建一个新的函数实例, 其 this 值会被绑定到传给 bind()的对象

apply

apply()放接收两个参数,一个是函数内this的值,一个是参数数组(可以是arguments 对象,也可以是数组实例)

使用方式

var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2){
    console.log(co1 + '-' + co2)
    console.log(this)
    console.log(this.color)
}
readColor();
readColor.apply();
readColor.apply(obj, ['yellow', 'green']);

调用的结果👇🏻

image.png 上面代码,第一次调用,没有使用apply, 其this指向它调用时所处的上下文,也就是window;
第二次调用使用apply()方法,但是没有指定上下文对象,默认还是window;
第三次让其this指向obj。
ps:严格模式下,函数如果没有指定上下文对象,this值会变为undefined

"use strict";
var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2){
    console.log(this)
}
readColor()
readColor.apply();

image.png

实现一个apply

Function.prototype.bapply =  function (ctx, args){
    ctx = ctx ? Object(ctx) : window; //this指向传进来的ctx对象或window
    ctx.fn = this; // 相当于给obj添加一个属性fn,fn = readColor 
    let result = args ? ctx.fn(...args) : ctx.fn(); // 处理参数数组
    delete ctx.fn;
    return result;
}

//测试
var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2){
    console.log(this.color)
    console.log(co1 + '-' + co2)
}
readColor.bapply(obj, ['green', 'yellow']);//blue; green-yellow

call

call()方法同apply()的作用一样,区别就是传参形式不一样。
call()方法同样接收两个参数,第一个参数是相同的,都是this值;第二参数是函数接收的参数,函数接收的参数是被一个个传入call()方法里的,相当于把传给apply的参数打散一个个罗列出来传给call。

使用方式

var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2){
    console.log(co1 + '-' + co2)
    console.log(this)
    console.log(this.color)
}
readColor();
readColor.call(obj, 'yellow', 'green');

image.png

实现一个call

与apply的实现类似,区别在于参数的处理

Function.prototype.bcall =  function (ctx){
    ctx = ctx ? Object(ctx) : window; //this指向传进来的ctx对象或window
    ctx.fn = this; // 相当于给obj添加一个属性fn,fn = readColor 
    let args = Array.prototype.slice.call(arguments, 1); // 取传入的参数,从第二个到最后所有
    const result = ctx.fn(...args)
    delete ctx.fn
    return result
}

// 测试
var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2){
    console.log(this.color)
    console.log(co1 + '-' + co2)
}
readColor.bcall(obj, 'yellow', 'green');// blue; yellow-green

bind

bind()  方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

  • 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数
  • bind的第一个参数,就是这个新的函数实例运行时的this
  • 其余参数,当函数被调用时,被预置入绑定函数的参数列表中的参数,也就是说剩下的一序列参数将会在传递的实参前传入作为它的参数

使用方式

var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2,co3){
    console.log(co1 + '-' + co2 + '-' + co3)
    console.log(this)
    console.log(this.color)
}
let bco = readColor.bind(obj, 'yellow', 'green');
bco('ppp')

image.png

实现一个bind

  1. bind 返回的是一个函数
  2. 能够接受多个参数,也能够接受柯里化形式的传参
  3. bind 绑定 this 只会生效一次
  4. 获取到调用bind() 返回值后,若当作构造函数调用的话,bind() 传入的上下文失效
Function.prototype.bBind =  function (ctx){
    if (typeof(this) !== 'function') { // 调用bind的是否是函数
        throw new TypeError('The bound object needs to be a function');
    }
    let self = this;
    let args = Array.prototype.slice.call(arguments, 1); // bind时传的参数
    let fNOP = function() {};
    let fBound = function() {
        let runArgs = Array.prototype.slice.call(arguments); // 调用函数是传的参数
        return self.apply(this instanceof fNOP ? this : ctx, [...args,...runArgs]);
    }
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();
    return fBound;
}

// 测试
var color = 'red';
var obj = {
    color: 'blue'
};
function readColor(co1,co2,co3){
    console.log(this.color)
    console.log(co1 + '-' + co2 + '-' + co3)
    
}
let bco = readColor.bind(obj, 'yellow', 'green');
bco('white'); // blue;  yellow-green-white

小结

  • call,apply和bind,主要都是用来改变函数内部this指向的
  • call与apply区别在于它们接收的第二个参数(给被调用函数使用的参数列表),给apply的参数是数组或arguments对象,而给call的参数是一个一个列出来的。
  • call与apply选择哪个,取决于用哪个给被调用的函数传参方便。
  • call与apply直接调用函数;bind返回一个新的函数实例,不会直接调
  • 严格模式下,函数如果没有指定上下文对象,this值会变为undefined