JavaScript | 解析函数对象call、apply、bind区别以及面试题分析

146 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

简单介绍

call,apply,bind是Function对象原型对象上自带的三个方法,所有的函数都能调用,主要作用:改变函数执行时的上下文,或者改变函数调用时的this指向

call方法

call(thisArg,arg1,arg2.....) 执行调用call方法的函数,改变调用call方法的函数中的this指向 指向call方法的第一个参数

call方法参数

this被改变的指向的对象

  • 如果第一个参数是 null或者undefined 则改变this指向window
  • 如果是基本包装类型,指向其的包装对象
  • 如果是对象类型,则直接指向当前对象
  • 后边的参数都是被调用函数的实参,用逗号间隔开

代码示例

var obj = {
    name:'obj',
    say:function(){
        console.log(this);
        console.log(this.name);
    }
}
//正常调用
obj.say(); // obj对象、boj
var obj1 = {
    name:'obj1'
};
//改变this指向 obj.say.call(obj1)   obj.say里面的this指向了obj1
obj.say.call(obj1); //ob1对象,boj1

apply方法

apply(thisArg,[arg1,arg2...])apply和call基本一样,区别在于传参不同

apply方法参数

  • call给函数传参的时候,是作为call的第二个参数以后所有的参数 逗号隔开
  • apply 给函数传参的时候,函数的参数全部放在数组中,作为apply的第二个参数

代码示例

var obj = {
    name: 'aaa',
    say: function (t,f) {
        console.log(t,f); // bbb ccc
        console.log(this); // obj1对象
        console.log(this.name) // hello
    }
}
var obj1 = {
    name:'hello'
};
//改变this指向为obj1,通过数组的方式传递参数
obj.say.apply(obj1,['bbb','ccc'])

使用场景

apply和call工作方式相同,不同的地方在于传递给调用函数的参数的形式

  • 借用其他函数的方法,让本对象使用
  • 让其他的构造函数中的属性变成自己的

代码示例

function Demo(name){
    this.name = name;
}
var obj = {sex:'nan',age:18};
Demo.apply(obj,['lucky']); //执行的Demo,demo中的this指向了obj,这个时候就相当于obj.name= lucky  所以执行完成之后对象中有了name属性值为lucky
console.log(obj)
/* 
{
    "sex": "nan",
    "age": 18,
    "name": "lucky"
}*/

bind方法

和call的功能基本类似,传参方式也一样

区别

  • call和apply作用是改变this指向,并直接调用函数
  • bind作用是改变this指向,并返回一个改变this指向以后的函数(不会调用)

代码示例

因为我们知道setTimeout里面直接调用this,this的指向会直接指向window,只能在函数内部去定义var that = this的方式去拿到当前函数的this,但是我们通过bind方法能直接实现,但call和apply就不能,这也是bind自己的固有属性决定的。

 document.onclick = function () {
            //bind绑定与var that = this 同理
            setTimeout((function () {
                console.log(this) //document
            }).bind(this), 100)
        }

关于call的面试题

看代码,试着输出一下你的答案

function fn1() {
    console.log(1)
        }
function fn2() {
    console.log(2)
}
fn1.call(fn2); 
fn1.call.call(fn2); 

打印结果fn1.call(fn2)的输出为1,fn1.call.call(fn2)输出的结果为2

分析

首先call内部里面是这样运行的

 Function.prototype.call = function (context,...arg){
     //是否有参数,无则指向windon
 context = context? Object(context) : window;
      //1.改变this指向
 context.fn = this;
    //2.调用函数
  rturn context.fn(...args);
}

fn1.call(fn2)

改变fn1指向为fn2,实际上就是给fn2对象上添加函数fn1,然后执行fn1()

fn1.call.call(fn2)

运行了两次call,可以理解为fn1.call和call(fn2),fn1.call其实就是Functiuon。prototype.call实际上什么都没操作,而call(fn2)则改变了this指向,组合起来就是fn2.call(),但是没有传参所以默认指向window,就是默认调用fn2输出2.所以就算是fn1.call.call.call...(fn2)依旧会输出2。

好了,以上就是本篇文章的分享,感谢阅读!