码农

call和apply的使用-扩展篇

2019-01-17  本文已影响0人  潜水的旱鸭子

一、回顾

在上篇文章call和apply的使用-基础篇中,我们已经提到了call和apply的功能和语法,这里稍作回顾:

介绍: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之后,返回的新函数会自动执行。


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

上一篇 下一篇

猜你喜欢

热点阅读