call和apply的使用-扩展篇

一、回顾

在上篇文章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之后,返回的新函数会自动执行。


写在最后,文中总结,如有不全或错误,欢迎留言指出,谢谢支持……^ _ ^

猜你喜欢

转载自blog.csdn.net/weixin_41636483/article/details/115082774