一、回顾
在上篇文章call和apply的使用-基础篇中,我们已经提到了call和apply的功能和语法,这里稍作回顾:
介绍: call和apply都是函数的方法,需要加在函数体后执行。
功能: 都是用来修改函数的执行上下文(this)。
语法:
- call(thisObj,arg1,arg2,arg3,……)
- apply(thisObj,argArr)
说明:call和apply的主要区别就是:call可以接受一个或以上的参数,当接受多个参数时,从第二个参数开始,后面所有的参数都会改变原函数的参数;apply只能接受一个或两个参数,当接受两个参数时,第二个参数必须是一个数组或类数组,数组中的数据,会改变原函数arguments中的参数。
而call和apply的第一个参数,都是用来改变原函数的this指向。
所以,以下演示以call为主,如果只有一个参数,那么可以直接替换成apply,并无区别;如果存在两个以上参数,替换成apply时,需要把第二个及后面所有参数放在一个数组中。
二、使用
使用方式1:执行时一个对象可以使用另一个对象的方法
function Doctor(){
this.name = "Doctor";
this.say = function(){
console.log(this.name);
}
}
function Stephen(){
this.name = "Stephen Strange";
}
var doctor = new Doctor();
var stephen = new Stephen();
// 通过call将stephen对象传入doctor的say方法,此时say方法中的this被指向stephen对象
doctor.say.call(stephen); //Stephen Strange
使用方式2:实现继承
function Doctor(name){
this.name = name;
this.say = function(){
console.log(this.name)
}
}
function Stephen(name){
//当前函数内的this指向函数Son的实例化对象
//在执行Stephen时执行Doctor,同时将Doctor内的this改变成Stephen的this
Doctor.call(this,name)
}
var doctor = new Doctor("Doctor")
doctor.say(); //Doctor
var stephen = new Stephen("Stephen Strange")
// 在Stephen中并没有say方法,但是因为在new Stephen时,执行了Doctor,
// 并将Doctor中的this指向Stephen的this,
// 那么在new Stephen后,得到的实例,也具有了Doctor内的属性和方法
stephen.say(); //Stephen Strange
使用方式3:多继承
function People(){
this.say = function(){
console.log(`My name is ${
this.name}. I will have ${
this.attr} ${
this.skill}.`)
}
}
function Doctor(){
this.skill = "cure";
}
function Magic(){
this.attr = "Amazing";
}
function Stephen(name){
this.name = name;
// 执行其他函数的同时将原函数的this指向都改成Stephen的this,此时所有属性和方法可以互相访问
People.call(this);
Doctor.call(this);
Magic.call(this);
}
var stephen = new Stephen("Stephen Strange");
stephen.say(); //My name is Stephen Strange. I will have Amazing cure.
使用方式4:改变系统函数的this指向,实现伪数组转真数组
我们知道js中有很多类(伪)数组,伪数组虽然也按照索引存储数据,有length属性,但是却不具有数组的方法,如push,pop等。
如果我们想使用数组的方法来操作伪数组,那么需要先将伪数组转成真数组,伪转真的方法有很多种,这里我们只说使用call方法转换:
var ali = document.querySelectorAll("li");
// instanceof:查看一个实例是否指向某个构造函数的原型(查看一个实例是否属于某个类)
console.log(ali instanceof Array); //false
// ali.push("hello"); //报错:ali.push is not a function
var arr = new Array(4,5,6);
// instanceof:查看一个实例是否指向某个构造函数的原型(查看一个实例是否属于某个类)
console.log(arr instanceof Array); //true
arr.push("hello")
console.log(arr); //[4,5,6,"hello"]
//此处开始转换
var aliZ = Array.prototype.slice.call(ali)
console.log(aliZ)
console.log(aliZ instanceof Array); //true
// 此时aliZ就是一个真数组,可以使用数组的众多方法来操作
aliZ.push("world");
console.log(aliZ); //[li,li,li,...,"world"]
使用方式5:优化Math对象的方法
Math对象的min和max方法只能接受多个数据,而不能接受单个数组。但是我们知道,函数内的arguments保存所有传进来的实参,此处利用apply第二个参数是数组,并且会覆盖原函数arguments的特点,将数组由apply传进去,交给min或max处理,即可快速得到数组的最大或最小值
var arr = [4,6,2,7,1];
console.log(Math.min(arr)); //NaN
console.log(Math.max(arr)); //NaN
console.log(Math.min.apply(null,arr)) //1
console.log(Math.max.apply(null,arr)) //7
使用方式6:改造系统方法的调用方式
我们知道js中许多实例的方法都是定义在构造函数的原型对象上,如Array.prototype.push / String.prototype.match / Function.prototype.bind等,当我们通过实例调用这些方法时,调用方式为arr.push() / str.match() / fn.bind(),我们可以利用call或apply函数改变这些实例方法的调用方式。
改造之后的调用如:push(arr,“hello”)
let arr = [3,4,5];
Array.prototype.push.call(arr,"hello")
console.log(arr); //[3,4,5,"hello"]
// 通过执行Function原型上的call方法的bind方法,改变call中原本应指向Function实例的this为Array.prototype.push,
// 并保存bind的返回值--改造之后的新call函数,放在newPush
const newPush = Function.prototype.call.bind(Array.prototype.push);
// 此时,call方法中的this指向为Array原型上的push方法,
// 执行newPush,相当于执行了Function.prototype.call.call(Array.prototype.push),
// call的第一个参数用来改变原函数this的指向,后call将前call中的this改成Array.prototype.push
// 此时后call执行,得到改变之后的前call
// 也就相当于得到了Array.prototype.push.call()
// 最终执行newPush相当于执行了Array.prototype.push.call()
newPush(arr,"world");
console.log(arr); //[3,4,5,"hello","world"]
// 此类改造还有:
const slice = Function.prototype.call.bind(Array.prototype.slice);
console.log(slice(arr, 0, 2)); //[3,4]
const match = Function.prototype.call.bind(String.prototype.match);
console.log(match("a1ab12abc123", /\d+/g)); //["1", "12", "123"]
// 练习:使用相同方式尝试改造数组或字符的其他方法
三、总结
其实不管在任何地方,只要牢记call和apply方法的功能:修改原函数的this指向,并执行这个新函数。就可以轻松的驾驭函数的call和apply方法。
临近结尾顺便提一下函数的另一个方法bind,其实bing和call/apply的功能类似,只不过bind修改this指向之后,返回的新函数不会自动执行,如果有需要,需要手动执行;而call和apply改变this之后,返回的新函数会自动执行。
写在最后,文中总结,如有不全或错误,欢迎留言指出,谢谢支持……^ _ ^