JS学习笔记之call、apply的用法

4,432 阅读3分钟

1、call和apply的区别

call和apply唯一的区别是传入参数的形式不同。

apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,可以是数组,也可以是类数组,apply方法会把集合中的元素作为参数传递给被调用的函数。

 var func = function (a, b, c) {
    console.log ([a, b, c]); // 输出 [1, 2, 3]
 };
 func.apply(null, [1, 2, 3]); // 数组中的1,2,3分别对应参数列表中的a,b,c

call跟apply相同的是,第一个参数也是代表函数体内的this指向,第二个参数开始往后,参数依次被传入函数内,传入的参数数量不固定。

 var func = function (a, b, c) {
     console.log ([a, b, c]); //输出:[1, 2, 3]
 }
 func.call (null, 1, 2, 3);

当调用函数时,Javascript解释器并不会计较形参和实参的数量、类型及顺序,在Javascript内部统一都是用数组表示。所以call其实就是包装在apply上的一颗语法糖。 在使用call和apply的时候,如果传入的第一个参数是null,函数体内的this会默认指向宿主对象,在浏览器环境里,就是window。

var func = function (a, b, c) {
    console.log (this === window); //输出:true
};
func.apply(null, [1, 2, 3]);

如果是严格模式下,函数体内的this还是null

var func = function (a, b, c) {
    'use strict';
    console.log (this === null); //输出:true
};
func.apply(null, [1, 2, 3]);

有时候call和apply还可以借用其他对象的方法,我们可以传入null来替代某个具体对象:

Math.min.apply(null, [5, 2, 1, 3, 4]); //输出:1

2、call和apply的用途

(1) 可以改变this指向

call和apply最常见的用法就是用来改变函数内部的this指向。

var obj1 = {
    name: 'lq'
};
var obj2 = {
    name: 'xiaoming'
};
window.name = 'angelababy';
var getName = function () {
    console.log (this.name);
};
getName(); // 输出:angelababy
getName.call(obj1); // 输出:lq
getName.call(obj2); // 输出:xiaoming

(2) Function.prototype.bind

几乎所有的高级浏览器都内置了Function.prototype.bind方法用来指定函数内部this指向问题。如果不支持原生的Function.prototype.bind,我们可以自己实现一个:

Function.prototype.diyBind = function (context) {
    var _this = this; // 保存原函数
    return function () { // 返回一个新的函数
        return _this.apply(context, arguments); // 执行新的函数时,会将之前传入的context当作新函数体内的this
    }
};
var obj = {
    name: 'lq'
}
var func = function () {
    console.log (this.name);
}.diyBind(obj);

func();

上面是简化版的,下面是稍微复杂点的版本:

Function.prototype.binds = function(){
   var _this = this, // 保存原来的函数
   context = [].shift.call(arguments), //截取第一个参数,即是绑定的this上下文
   args = [].slice.call(arguments); //剩余的参数转成数组
   return function(){ //返回一个新的函数
       return _this.apply(context, [].concat.call(args,[].slice.call(arguments))); // 执行新函数的时候,会将之前传入的context最为新函数体内的this,在此例中就是obj。用concat合并两次传入的参数,最为新函数的参数
   }
}
var obj = {
   name: 'lq'
}
var func = function(a, b, c, d){
   console.log(this.name); // 输出:lq
   console.log([a, b, c, d]); // 输出:[1, 2, 3, 4]
}.binds(obj,1, 2);

func(3,4);

(3)借用其他对象的方法

借用的第一种场景是“借用构造函数”:

var A = function (name) {
    this.name = name;
}
var B = function () {
    A.apply(this, arguments);
}
B.prototype.getName = function () {
    return this.name;
}
var b = new B('xiaoming');
console.log (b.getName()); //输出:xiaoming

借用的第二种场景是“借用Array.prototype对象上的方法”,比如:

(function(){
    Array.prototype.push.call(arguments, 3);
    console.log (arguments); // 输出:[1, 2, 3]
})(1, 2)

在操作arguments时我们经常会借用Array.prototype对象上的各种方法。比如想把arguments转成真正数组时,可以借用Array.prototype.slice方法。想截去arguments列表中的第一个元素,可以借用Array.prototype.shift方法。