一、call()、apply()、bind()的异同

call()、apply()、bind()都是用于改变this指向的方法,不同点传参方式不太相同以及返回不同。

  • call( ) 是接收一个及其以上的参数,第一个参数表示this要指向的对象,其余参数表示调用函数需要传入的参数,返回调用函数的返回结果,属于立即执行函数;
  • apply( ) 是接收两个参数,第一个参数表示this要指向的对象,第二参数表示调用函数需要传入的参数所组成的数组,返回调用函数的返回结果,属于立即执行函数;
  • bind( ) 是接收一个及其以上的参数,和call()一致,但是其返回是一个函数,而不是调用函数的返回结果;

1. call( ) 和 apply( ) 改变this指向代码

//此处声明若用let,则第一个调用函数输出undefined,
//因为此处let声明的变量虽然也是全局变量但其不会成为全局对象window的属性,故say()直接调用时为undefined
var word = "我是window";
function say(params1,params2){
	console.log(params1+" "+params2+","+this.word)
}
let obj = {
	word: "我是obj"	
}
let newObj= {
	word: "我是newObj"	
}
say("Hi","friend"); //Hi friend,我是我是window //let声明 则输出:Hi friend,undefined
say.call(obj,"Hi","friend")  //Hi friend,我是obj
say.apply(newObj,["Hi","friend"])  //Hi friend,我是newObj

2. bind( ) 改变this指向代码

var word = "我是window";
function say(params1,params2){
	console.log(params1+" "+params2+","+this.word)
}
let Obj1= {
	word: "我是newObj1"	
}
let Obj2= {
	word: "我是newObj2"	
}
//返回一个新的函数
let newFunc = say.bind(Obj1,"hello","friend"); 
newFunc()   //hello friend,我是newObj1

//可将其改为立即执行函数,此时返回和call(),apply()相同
say.bind(Obj2,"hello","friend")();   //hello friend,我是newObj2

3.call( )的应用

实现函数的继承

function Person(name){
	this.name = name;
	this.say = function(){
		console.log("我是"+this.name)
	}
}		
function Student(sno,name){
	this.sno = sno;
	//此this指Student
	//通过改变this指向,使Person里的this指向Student
	//从而实现使Student拥有Person里的name属性以及say方法,即实现继承
	Person.call(this,name)
}
//实例化Student
let studen1 = new Student("20220518001","小明");	
studen1.say(); //我是小明
console.log("姓名:"+studen1.name+",学号:"+studen1.sno); //姓名:小明,学号:20220518001

4.applay( )的应用

数组合并

let arr1 = [1,2,3,4,5];
let arr2 = [7,8,9];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1) //[1, 2, 3, 4, 5, 7, 8, 9]

//也可使用es6...语法实现	
let newArr = [...arr1,...arr2];
console.log(newArr)

数组中的最大值

let arr = [9,2,13,15,5];	
let max = Math.max.apply(Math,arr);
console.log(max) //15

二、 Array.prototype.slice.call()

实现将一个具有length属性的对象转化为数组

Array:数组的构造函数(数组类)
prototype: 原型对象(构造函数的一个属性);
slice: 是一个数组方法,他用于截取数组。两个参数一个表示截取开始位置,第二个表示截取结束位置(可选);
call:可改变对象的this指向,即改变执行域,参数一新的this指向对象,剩余参数指需传递的单个元素参数;
//注意这里对象的属性名除了length属性其他的都只能是数字,否则值为空
//而且数字不能超过length属性值,否则超过length值的属性名对应的值将不显示
let obj1={
	0:"张三",
	1:18,
	length:2
}
console.log(Array.prototype.slice.call(obj1,1)) //["张三",18]
let obj2={
	0:"李四",
	1:18
}
console.log(Array.prototype.slice.call(obj2)) //[]

详解 Array.prototype.slice.call(arguments)

//实现将类数组arguments转化为数组(参数所组成的数组)
Array.prototype.slice.call(arguments)	

arguments:是一个类数组对象,并不是真正的数组,它缺少很多数组方法但其有length属性;

arguments类数组对象通过slice截取的方法将其变为数组,由于arguments并不是真正的数组因此它不可以直接使用slice方法,这就需要使用call来改变this指向,将原本this指向Array改变为指向arguments,这样就改变了slice的执行域,即arguments就可使用slice方法。从而实现将arguments类数组对象转变为数组。

三、call()、apply()、bind() 手写实现

1. call()

步骤:
1.将mycall绑定到函数原型上;
2.判断this是不是一个函数,不是函数则抛出错误;
3.判断传入的对象是否为null或undefined,是则将传入的对象指定为window对象,否则将传入的对象进行Object转换(Object可以将任意值转换为对象),这样做的目的是防止传入的是原始值;
4.给传入的对象增加一个属性(确保与对象原有的属性不重复,故需使用Symbol类型声明属性确保其独一无二);
5.给对象新增的属性绑定this,从而达到改变this指向的功能;
6.执行新增属性绑定的函数(this就是一个函数)并保存执行结果;
7.删除新增的属性;
8.返回6中保存的执行结果;

// 1.call()属于函数原型上的方法,故要绑定在函数原型中
Function.prototype.mycall = function(thisObj,..args){	
	//调用mycall的对象类型为非函数,抛出异常(this指调用mycall的对象)
	if(typeof this !=="function") throw new TypeError("Not a function")
	
	// thisObj对象为 null 或 undefined 时thisObj指向全局对象window
	if (thisObj === null || thisObj === undefined) {   
        thisObj = window 
    } else {
       // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
       //如果参数是原始类型的值,Object 方法将其转为对应原始值的实例对象
       //如果 Object 方法的参数是一个对象,它总是返回该对象,即不用转换
        thisObj= Object(thisObj) 
    }
	//避免和thisObj对象上原本的属性重名(Symbol可以生成独一无二的值)
	let fn = Symbol();
	//在参数对象上添加属性fn,并将this对象绑定到该属性上,此处的this是指调用mycall的函数
	//用于下一步执行函数
	thisObj[fn] = this;
	//将第二个参数开始的所有参数以参数列表的方式传入,并保留执行的结果(相当于执行调用了mycall的函数)
	//由于此时的fn是thisObj的属性,故fn函数中的this指向是thisObj,即调用mycall传入的第一个参数的对象
	//从而达到改变this指向的功能(fn函数中this是指向thisObj而thisObj是自己传入的对象)
	let res = thisObj[fn](...args);
	//删除
	delete thisObj[fn];
	return res;
} 
1.1测试手写的call方法(附带保姆级解析)

ps:虽然上面的解释个人感觉已经很详细了,但是对于初次接触call方法的可能还不够直观,因此下面我们将代入测试实例对手写的call()方法进行一个应用中的解析,希望上面没看懂的小伙伴们能看懂哈在这里插入图片描述

Function.prototype.mycall = function(thisObj,...args){
	console.log("-----------------------------------myCall打印解析")
	
	if(typeof this !=="function") throw new TypeError("Not a function")
	//thisObj就是我们传入的obj
	console.log("====看看thisObj是啥")
	console.log(thisObj)
	
	if (thisObj === null || thisObj === undefined) {   
        thisObj = window 
    } else {
        thisObj= Object(thisObj) 
    }		
	let fn = Symbol();
	
	//this是调用mycall的函数,所以此时this是test这个函数
	console.log("====看看this是啥")
	console.log(this)
	//为obj添加fn属性,并将其赋值为test函数,此时fn将是test函数
	thisObj[fn] = this;
	//obj的fn是test函数
	console.log("===看看obj[fn]是啥")
	console.log(obj[fn])
	console.log("-------------------------------------打印解析结束")
	
	//执行obj的fn函数(即执行test函数,因为是obj调用故test中的this是obj对象),并将test函数的返回值结果保存下来
	let res = thisObj[fn](...args);
	//删除
	delete thisObj[fn];					
	return res;
}		
function test(params1,params2){
	console.log("========this是谁============");
	console.log(this)
		
	console.log(params1+params2);
	return this.name;
}		
let obj = {
	name: "张三"
}
var name = "李四";

let res1 = test(4,5)
console.log(res1) //  

//调用myCall方法
let res2 = test.mycall(obj,1,2)
console.log(res2)

打印解析

请添加图片描述

2.apply( )

与手写call类似,只是参数处理不同,因为apply接收的第二个参数是数组,故需将其转化为参数列表

Function.prototype.myApply = function(thisObj,args){
	if(typeof this !=="function") throw new TypeError("Not a function")	
	
	if (thisObj === null || thisObj === undefined) {   
        thisObj = window 
    } else {
        thisObj= Object(thisObj) 
    }	
    	
	let fn = Symbol();						
	thisObj[fn] = this;	
	//第二个参数是数组,需使用es6语法的...将数组转化为参数列表传入
	let res = thisObj[fn](...args);
	//删除
	delete thisObj[fn];					
	return res;
}

//测试myApply 		
function test(params1,params2){	
	console.log(params1+params2)
	return this.name;
}		
let obj = {
	name: "张三"
}
var name = "李四";

let res1 = test(1,2) //3
console.log(res1) //李四

let res2 = test.myApply(obj,[5,6]) //11
console.log(res2) // 张三

3.bind( )

bind返回的是新函数,而不是像call、apply那样立即执行调用它的函数
步骤:
1.绑定到函数原型对象上;
2.判断是不是函数调用;
3.包存原函数(调用myBind的函数);
4.定义新返回的函数;
5.判断返回的新函数是不是作为构造函数被使用,若是则this指向调用新函数的对象而非myBind传入的对象,若不是则this指向myBind传入的对象;
6-1. 执行, 原函数调用call去改变this指向并传入myBind的入参和新函数的入参;
6-2. 返回新函数的执行结果,其结果就是6-1;
7.原函数若有原型则将原型复制给新函数;
8.返回新函数;

以下代码是借鉴思否一位大佬的代码,链接,测试部分以后补充吧。。。。。。

Function.prototype.myBind = function (objThis, ...params) {
	if(typeof this !=="function") throw new TypeError("Not a function")	
	//将原函数进行保存(调用myBind的函数)
    const _this= this; 
    // 定义需返回的新函数(新函数和原函数一样,也可接收参数)
    let fBind = function (...secondParams) {
    	//判断返回的新函数fBind是不是作为构造函数被使用
        // this是否是fBind的实例 也就是返回的fBind是否通过new调用
        const isNew = this instanceof fBind ;
        // new调用就绑定到this上,否则就绑定到传入的objThis上
        const context = isNew ? this : Object(objThis);
        //新函数的返回结果要与原函数一致,因此原函数调用call,传入this指向对象,并传入myBind的入参和新函数的入参
        return _this.call(context, ...params, ...secondParams); 
    };
    //判断原函数有无prototype
    if (_this.prototype) {
        // 复制原函数的prototype给fBind
        fBind .prototype = Object.create(_this.prototype);
    }
    // 返回新的的函数
    return fBind ; 
};

有问题欢迎各位大佬指正哈
先这样吧,✿✿ヽ(°▽°)ノ✿
请添加图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐