js的call()、apply()、bind()解析
一、call()、apply()、bind()的异同call()、apply()、bind()都是用于改变this指向的方法,不同点参数定义不太相同call()是接收一个及其以上的参数,第一个参数表示this要指向的对象,其余参数表示Array.prototype.slice.call()实现将一个具有length属性的对象转化为数组Array:数组的构造函数(数组类)prototype: 原型对
目录
一、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 ;
};
有问题欢迎各位大佬指正哈
先这样吧,✿✿ヽ(°▽°)ノ✿
更多推荐










所有评论(0)