本文正在参加「金石计划」
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
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一个个的传递