this与call apply bind

74 阅读5分钟

本文正在参加「金石计划」

this与call apply bind

这篇文章我们来讨论一下this与call apply bind之间的关系,按照惯例我们先来复习一下this关键字

一、复习this(函数执行的主体)

  • 全局上下文的this是window
  • 块级上下文没有自己的this,所用的this是继承上下文的this(箭头函数也是)

事件绑定

事件绑定的this,指定绑定的元素

普通函数执行

其他情况看点,点前面是谁,this就是谁;没点就是window

call、apply、bind强制改变this指向问题

  • 都是用来改变某一个函数中this关键字指向的

二、初步了解 call&bind& apply

1669090817443.png

call

改变函数执行的this,并且执行函数

语法: function.call(thisArg, arg1, arg2, ...)

参数:第一个this指向 第二个及后面的是实参

返回值:函数执行的返回值

bind

改变函数执行的this,但是不执行函数

语法:function.bind(thisArg[, arg1[, arg2[, ...]]])

参数:第一个this指向 第二个及后面的是实参

返回值:改变this指向的函数体

apply

改变函数执行的this,并且执行函数

语法 :func.apply(thisArg, [argsArray])

参数:第一个参数是this指向,第二个参数是数组

返回值:函数执行的返回值

  • apply与call的区别在于传参方式不同,我们需要把所有参数放在这个数组里,apply会把数组里的每一项一个一个传给函数

示例:

let a = 1;
let obj = {
    a: 2,
    fn: fn
};
function fn(b, c) {
    console.log(this.a);
    return b + c;
};
fn(4, 5); //正常执行fn,this指向的就是window,此时a为window里的属性,因为没有,输出undefined
fn.call(obj); //fn.call(obj)执行一次函数,会把this改成obj             不输出
fn.bind(obj);
console.log(fn.bind(obj));//bind改变后会输出函数体,但不执行,返回的是改变后的函数体,看不出来,但已经改变了
fn.apply(obj, [1, 2]); //apply的传参方式不同,必须以一个数组的方式传参,只接收两个参数:第一个参数是this指向,第二个参数值是数组,把所有需要传递的参数都放在数组里;返回值是函数结果,也会执行词义函数
console.log(fn.apply(obj, [1, 2]));

三、详细了解 call&bind& apply

call详解

1、[fn].call([this],[param]…)

  • fn.call:当前实例(函数fn)通过原型链的查找机制,找到function.prototype上的call方法
  • fn.call() : 把找到的call方法执行
  • 当call方法执行的时候,内部处理了一些事情
  • =>首先把要操作函数中的this关键字变为call方法第一个传递的实参值
  • =>把call方法第二个及第二个以后的实参获取到
  • =>把要操作的函数执行,并且把第二个以后的传递进来的实参传给函数
let sum=function(a,b){
    console.log(this,a+b);//=>opt
};
let opt={n:20};
sum.call(opt,20,30)//=>call执行 call中的this是sum  把this(call中的)中的“this关键字”改为opt 把this(call中的)执行,把20,30分别传递给它 //=>sum中this:opt  a=20 b=30
sum.call.call(opt)
//1.sum.call 找到Function.prototype上的call方法(也是一个函数,也是函数类的一个实例,也可以继续调用call/apply等方法)  =>A(函数)
//2.A.call(opt)  继续找到原型上的call方法,把call方法执行:把A中的this关键字修改为opt,然后把A执行

CALL中的细节

  • 1.非严格模式下,如果参数不传,或者第一个传递的是null/undefined,THIS都指向WINDOW
  • 2.在严格模式下(加 ‘use strict’ 字符串就是严格模式),第一个参数是谁,THIS就指向谁(包括null/undefined),不传THIS就是undefined
fn.call(obj, 10, 20);//=>this:obj a=10 b=20
 fn.call(10, 20);//=>this:10 a=20 b=undefined
 fn.call();//=>this:window a=undefined b=undefined
 fn.call(null);//=>this:window
 fn.call(undefined);//=>this:window

阿里call面试题

/*
Function.prototype.call = function (context, ...arg) {
    //=>my_call方法中的this就是我们要处理的那个函数(fn/sum...)
    this.toString().replace('this', context);
    this(...arg);
};
*/
function fn1() {
    console.log(1);
}
function fn2() {
    console.log(2);
}
fn1.call(fn2);//=>1
/*
 * 执行CALL方法
 *   1.CALL方法中的THIS:FN1
 *   2.CALL方法中的CONTEXT:FN2
 */
fn1.call.call(fn2);//=>2
/*
 * 执行的是最后一个CALL方法
 *   1.THIS:FN1.CALL
 *   2.CONTEXT:FN2
 */
//=>第一次执行最后面的CALL,代码执行第一步
//FN1.CALL.toString().replace('this', FN2);
// FN1.CALL=function (context, ...arg) {
//     FN2.toString().replace('this', context);
//     FN2(...arg);
// };
//=>第一次执行最后面的CALL,代码执行第二步
//FN1.CALL();
/*
 *  第二次执行CALL方法
 *    CONTEXT='UNDEFINED'
 *    FN2.toString().replace('this', undefined);
 *    FN2();
 */
//===============另一种思路
fn1.call(fn2);
/*
 * CALL中的THIS是FN1,把FN1中的THIS关键字修改为FN2,然后再把FN1执行  =>"CALL方法中的THIS是谁,最后执行的就是谁"
 */
fn1.call.call(fn2);
/*
 * 第一次执行最末尾的CALL,CALL中的THIS是FN1.CALL,先把FN1.CALL中的THIS改为FN2,然后让FN1.CALL执行
 * 第二次CALL执行,方法中的THIS已经被上一次修改为FN2了,所以参考“THIS是谁就执行谁”的标准,执行的是FN2
 */
fn.call.call(1)//报错

手写call

Function.prototype.call = function() {
    let [thisArg, ...args] = [...arguments];
    if (!thisArg) {
        //context为null或者试undefined
        thisArg = typeof window === 'undefined' ? global : window;
    }
    //this指向试当前函数func
    thisArg.func = this;
    //执行函数
    let result = thisArg.func(...args);
    delete thisArg.func;
    return result;
}

bind详解

bind预处理this,不执行函数

bind:语法和call一模一样,唯一的区别在于立即执行还是等待执行

  • fn.call(obj,10,20) 改变FN中的THIS,并且把FN立即执行,返回的是fn函数执行的结果
  • fn.bind(obj,10,20) 改变FN中的THIS,此时的FN并没有执行(不兼容IE6~8),把改变this后的函数体返回
let fn = function (a, b) {
    console.log(this);
};
let obj = {name: "OBJ"};
 document.onclick = fn;//=>把FN绑定给点击事件,点击的时候执行FN
 document.onclick = fn();//=>在绑定的时候,先把FN执行,把执行的返回值(UNDEFINED)绑定给事件,当点击的时候执行的是undefined
//=>需求:点击的时候执行FN,让FN中的THIS是OBJ
 document.onclick = fn;//=>this:document
 document.onclick = fn.call(obj);//=>虽然this确实改为obj了,但是绑定的时候就把fn执行了(call是立即执行函数),点击的时候执行的是fn的返回值undefined
 document.onclick = fn.bind(obj);//=>bind属于把fn中的this预处理为obj,此时fn没有执行,当点击的时候才会把fn执行
function fe(){
   console.log(this)
}
fe.call(1)//=> Number(1)  实例化后的1

apply

apply:和call基本上一模一样,唯一区别在于传参方式

  • fn.call(obj,10,20)
  • fn.apply(obj,[10,20]) APPLY把需要传递给FN的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给FN一个个的传递