call bind apply的区别以及源码的手写

218 阅读3分钟

call

call方法的第一个参数也是this的指向,后面传入的是一个参数列表改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

当第一个参数为nullundefined的时候,默认指向window(在浏览器中)

function fn(...args){
    console.log(`this的指向为${this}`,args);
}
let obj = {
    myName:"张三"
}

fn.call(obj,1,2); // this会变成传入的obj,传入的参数是一个参数列表;
fn(1,2) // this指向window
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window

apply

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

当第一个参数为nullundefined的时候,默认指向window(在浏览器中)

function fn(...args){
    console.log(`this的指向为${this}`,args);
}
let obj = {
    myName:"张三"
}

fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window

bind

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

function fn(...args){
    console.log(`this的指向为${this}`,args);
}
let obj = {
    myName:"张三"
}

const bindFn = fn.bind(obj,[1,2]); // this 会变成传入的obj ,bind不是立即执行
bindFn(1,2) // this指向obj
fn(1,2) // this指向window

手写apply

  • 判断是否为undefined或null,如果是,则this指向的是window
  • 给目标对象创建一个属性,将要调用的方法绑定到这个属性上
  • 用arguments拿到传入的参数
  • 直接运行刚刚创建的属性
  • 删除这个属性
Function.prototype.myApply = function(context){
    if(typeof context === "undefined" || context === null){
        context = window;
    }
    
    context.fn = this //this指向的是apply方法的函数
    let args = arguments[1]  //拿到第一个参数
    let result
    if(args){
        result = context.fn(...args) //运行一下
    }else{
        result = context.fn()
    }
    delete context.fn
    return result
}

手写call

思路同apply基本相同

不一样的是要通过arguments取出除第一位之外的所有参数放到一个数组里,然后通过展开运算符给要执行的函数或方法传参

//往Function的原型对象上加myCall方法   
Function.prototype.myCall = function(context){
    //判断是否为undefined或null
    if(typeof context === 'undefined' || context === null){
        context = window
    }
    context.fn = this //给目标对象新建一个属性,绑定这个函数
    let args = [...arguments].slice(1) //过arguments取出除第一位之外的所有参数放到一个数组
    let result = context.fn(...args) ////运行一下
    delete context.fn  //删除目标对象上的fn属性
    return result
}

手写bind

bind() 方法会创建一个新函数。 当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this, 之后的一序列参数将会在传递的实参前传入作为它的参数

要注意因为bind转换后的函数可以作为构造函数使用,此时this应该指向构造出的实例

Function.prototype.myBind = function(context){
    if(typeof this !== 'function'){
        throw new TypeError('error')
    }
    // 获取参数
    const args = [...arguments].slice(1)
    let _this = this;
    return function fn(){
        //判断是都被当做构造函数使用,根据调用方式,传入不同绑定值
        return _this.apply(this instanceof fn? new fn(...arguments):context,args.concat(...arguments))
    }
}

小结

  • 三者都可以改变函数的this指向
  • 三者的第一个参数都是this要指向的对象,如果没有这个参数,或者参数为undefined或null,则默认会指向全局window
  • 三者都可以传参,apply接受的是数组,call和bind接收的是参数列表,bind的参数列表可以分为多次传入
  • apply和call是立即执行,而bind是返回绑定后的那个this之后的函数

参考文章